Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #51, ФЕВРАЛЬ 2005 г.

Как *nix-системы потеряли портируемость

j1m (j1m@list.ru)

Спецвыпуск: Хакер, номер #051, стр. 051-092-4


Создать объектный файл:

$ as prog.s -o prog.o

И слинковать его:

$ ld prog.o -o prog

Так из исходника prog.s ты получишь бинарник prog.

Если же программа использует Libc, нужно выполнить только одну команду:

$ gcc prog.s -o prog

Компилятор языка C сам перенаправит программу ассемблеру и подключит необходимые объектные модули, иначе это пришлось бы делать вручную.

Практические занятия

Для иллюстрации приведу пару примеров. Первый продемонстрирует работу с системными вызовами, а второй - с функциями Libc. Я не имею ничего против программистов, отдающих предпочтение BSD, но так как популярность Linux намного выше BSD, то и примеры будут под Linux.

.globl _start # делаем метку _start экспортируемой

.data # начало секции данных

mes1: .string "Сообщение от родителя\n"

mes1_len = .-mes1

mes2: .string "Сообщение от потомка\n"

mes2_len = .-mes2

.text # начало секции кода

_start:

movl $2,%eax # системный вызов fork (2)

int $0x80

test %eax,%eax # если fork вернул 0, значит мы в потомке

jz child # прыгаем на код потомка

parent:

movl $4,%eax # системный вызов write (4)

movl $1,%ebx # пишем в STDOUT (1)

movl $mes1,%ecx # адрес сообщения в ecx

movl $mes1_len,%edx # длина сообщения в edx

int $0x80

jmp exit # на выход...

child:

movl $4,%eax # системный вызов write (4)

movl $1,%ebx # пишем в STDOUT (1)

movl $mes2,%ecx # адрес сообщения в ecx

movl $mes2_len,%edx # длина сообщения в edx

int $0x80

exit:

xor %eax,%eax

inc %eax # системный вызов exit (1)

xor %ebx,%ebx # возвратим 0 (мол все нормально)

int $0x80

Это классический пример разветвления процесса с помощью Fork. Процесс создает потомка и выводит сообщение о том, что сам он - родитель. Потомок же в свою очередь тоже идентифицирует себя. После вывода сообщения и родитель, и потомок завершаются.

Разберем часть исходного кода. В первой строке при помощи директивы .globl сообщаем о том, что метка _start является экспортируемой (глобальной). Метка _start должна присутствовать всегда, так как с этого адреса будет начинаться выполнение программы, а если не сделать ее глобальной, то линкер просто не увидит ее. Далее с помощью директивы .data объявляешь начало секции данных (в этой секции должны находится все статические данные, в нашем случае это строки). В этой секции по адресу mes1 находится строка. После нее - константа mes1_len, содержащая длину строки, которая вычисляется вычитанием адреса начала строки (метка mes1) из текущего адреса (директива '.'). Остальную часть секции данных занимает еще одна строка. После секции данных начинается секция кода (директива .text), в которой должны находиться все команды, выполняемые процессором. Остальная часть текста должна быть понятной. Описывать системные вызовы я не буду, так как они очень подробно описаны в документации aka man’ах.

А вот и пример с использованием Libc:

.globl main

.data

mes1: .string "File name:"

mes2: .string "New file name:"

buf1: .space 256,0

buf2: .space 256,0

.text

Назад на стр. 051-092-3  Содержание  Вперед на стр. 051-092-5