макет программы, определяющей номера системных вызовов в Linux и BSD
Теперь лезем в man ("man 2 write") и смотрим какие параметры этот вызов принимает. Ага: write(int d, const void *buf, size_t n_bytes). То есть, мы должны занести #4 в eax, файловый дескриптор – в ebx, указатель на выводимую строку – в ecx и количество выводимых байт – в edx, после чего вызвать прерывание INT 80h.
BSD-системы используют гибридный механизм: прерывание INT 80h и FAR CALL 0007h:00000000h. Номера системных вызовов так же как и в Linux помещаются в регистр eax, а вот параметры передаются через стек по Си-подобному соглашению (то есть, первым заносится крайний правый параметр, последним в стек ложится фиктивный dword, стек чистит за собой вызывающий код). Поскольку, номера базовых системных вызовов в обоих системах совпадают, можно исхитриться и написать программу, работающую под обоими операционными системами: Linux не обращает внимание на стек, а BSD — на регистры, что позволяет нам продублировать параметры и там, и там. Естественно, это увеличивает размер программы, но, к нашему счастью, BSD позволяет эмулировать Linux-интерфейс, достаточно дать команду "brandelf ?t Linux имя_файла", после чего нам останется только запустить его! А Linux в свою очередь умеет эмулировать BSD, SunOS и еще много чего!
Но довольно слов, переходим к делу! Перепишем нашу программу, чтобы она выводила приветствие через системный вызов write без использования libc. Стартовый код в этом случае исчезает и точкой входа в программу становится метка _start, объявленная как global. Ну а сама программа выглядит так:
.text
.globl _start
_start:
movl $4,%eax ; // системный вызов #4 "write"
movl $1,%ebx ; // 1 -- stdout (xorl %ebx,%ebx/incl %ebx)
movl $msg,%ecx ; // смещение выводимой строки
movl $len,%edx ; // длина строки
int $0x80 ; // write(1, msg, len);
movl $1, %eax ; // системный вызов #1 "exit"
xorl %ebx,%ebx ; // код возврата
int $0x80 ; // exit(0);
.data
msg: .ascii "hello,elf\n"
len
= . - msg