Ассемблер в Unix

Статья посвящена программированию на языке Assembler для *nix

[marlyn (adain@mail.ru)]

Ассемблер в Unix

Введение

Так исторически сложилось, что программирование на ассемблере под unix почти не востребовано, и занимаются им только кодеры-маньяки, дзен-буддисты и прочие настоящие ассемблерщики.

Настоящий ассемблерщик - зверь крайне редкий, практически нигде и не встретишь его, разве что в заповеднике - http://wasm.ru. Unix-ассемблерщик еще более редкий подвид, практически вымерший, если не считать, западный, http://linuxassembly.org.

Для исправления такой плачевной ситуации, и была написана эта статья, а точнее цикл статей, которые по задумке автора, должны привлечь в ряды адептов-юникс-дзена множество новых членов.

В первой части (которую вы сейчас читаете) я имею честь познакомить вас с прекрасным миром unix-программирования, что выльется в написание простейшего helloworld. В следующей части - мы разберем несколько, более сложных примеров. И под конец, наверное, будет программирование под x-windows.

Инструменты

Для нормально функционирования нам понадобятся следующие вещи:

  1. Собственно какая-либо unix-совместимая ось. (например linux, или лучше FreeBSD ),

  2. Компилятор fasm. ( http://www.flatassembler.net )

  3. Линкер ld ( есть почти в любом дистрибутиве unix ),

  4. Особый склад ума, причем последнее - самое главное. Если у вас этого нет, то ни один, даже самый последний RedHat на пару со свежим fasm'ом вам не поможет.
И еще, о компиляторах - в unix обычно используются AS с AT&T синтаксисом, который для многих людей, выросших на tasm'е и masm'е, кажется полной абракадаброй. Поэтому, для начала, мы будем использовать привычные компиляторы с Intel'овским синтаксисом (fasm или nasm). Хотя позже, если найдутся желающие, можно будет рассмотреть и AT&T asm.

Общие сведения

Unix, который мы будем использовать - 32 битная система, работающая в защищенном режиме, и использующая плоскую модель памяти.

Как и большинство операционных систем, Unix предоставляет программе набор различных функций (по другому - Api). Но, в отличие от, например, WinAPI, где вызовы производятся с помощью call'ов, в unix - больше свободы: можно вызывать функция ядра напрямую, а можно использовать многочисленные библиотеки. Рассмотрим для начала первый способ.

Системный вызов производится с помощью прерывания 0x80 (чаще всего). К сожалению, (а может и к счастью) существует несколько конвенций вызова, что приводит к несовместимости кода между многими unix-like осями. Я рассмотрю только две, самые популярные платформы: Linux и *BSD.

FreeBSD (а также OpenBSD и NetBSD)

Эта система использует традиционную unix конвенцию вызова: номер функции помещается в eax, параметры в стек, вызов производится с помощью функции содержащей int 0x80, а результат возвращается в eax. Наверное, понятнее будет, если рассмотреть это на примере:

 sys_call:
             int  0x80
             ret
       start:
         push msg_len   ; размер строки
         push msg       ; адрес строки
         push 1         ; stdout
         mov  eax,4     ; номер системной функции - sys_write
         call sys_call
         add  esp,4*3   ; очищаем за собой стек
Впрочем, от функции sys_call можно отказаться, достаточно просто помещать в стек лишний dword:
start:
              push msg_len   ; размер строки
              push msg       ; адрес строки
              push 1         ; stdout
              mov  eax,4     ; номер системной функции - sys_write
              push eax       ; все что угодно
              int  0x80
              add  esp,4*3   ; очищаем за собой стек
Также FreeBSD поддерживает конвенцию вызова, применяемую в linux. Для это необходимо включить linux emulation. Еще эта эмуляция потребуется для запуска fasm. А еще нужна утилита brandelf (наверняка она у вас есть). Дело в том, что пока не существует версии fasm'а конкретно для BSD систем. Но это легко исправить, вот так:
Brandelf -t Linux fasm
Если это не сработает (а такое возможно из-за не совместимости форматов), придется перекомпилировать fasm, заменив формат файла "format PE executable" на простой "format ELF", а потом слинковать ld.

Linux

В линуксе используется fastcall конвенция. Номер функции, все так же, помещается в eax, а вот параметры, вместо стека, помещаются в регистры. Пример:
              mov  edx,msg_len
              mov  ecx,msg
              mov  ebx,1
              mov  eax,4
              int  0x80
Порядок размещения параметров такой:
                           No. параметра Регистры
                                     1
                                           ebx
                                 2         ecx
                                 3         edx
                                     4
                                           esi
                                     5
                                           edi
                                 6         ebp
Как видите максимальное количество параметров - 6. Если их больше, приходиться помещать все параметры в структуру и передавать ее адрес в ebx.

Описание системных функций

После того как вы разобрались с вызовом функций, будет логичным вопрос: "А где взять описание этих самых функций?".

Ничего похожего на msdn, в unix среде к сожалению не существует, но не нужно забывать: unix - система с открытым исходном кодом и все нужное, можно найти там.

Для linux:

        arch/i386/kernel/entry.S
        include/asm-i386/unistd.h
        include/linux/sys.h
Для FreeBSD:
        i386/i386/exception.s
        i386/i386/trap.c
        sys/syscall.h
Для каждой функции можно посмотреть описание, используя man(2).

Пример программы "Hello world"

Пришло время написать, тот самый, жутко всем надоевший - HelloWorld.

Я приведу пример только FreeBSD версии, переписать это под linux - будет вашим домашним заданием. (для самых ленивых - см. примеры к статье)

format ELF
section '.text' executable
public _start
_start:
              push msg_len   ; size of message
              push msg       ; offset of message
              push 1         ; stdout
              mov  eax,4     ; 4 =  sys_write
              push eax
              int  0x80
              add  esp,4*3   ; очищаем за собой стэк

              xor  eax,eax
              push eax       ; код выхода
              inc  eax       ; 1 = sys_exit
              int  0x80

section '.data' writeable

              msg db "Hello world",0
              msg_len = $-msg

Сборка

Сначала скомпилируем файл, вот так:
  fasm hello.asm hello.o
А потом слинкуем:
  ld -o hello hello.o
А теперь посмотрите на размер. 600 байт, впечатляет?! ( размер можно еще очень сильно уменьшить, но об этом как-нибудь в другой раз)

Использование библиотеки libc

Некрасивый и совсем не дзенский способ, но все же мы его рассмотрим - для полноты картины.

Итак, libc (c library) - это стандартная библиотека с для UNIX. Она содержит в себе кучу полезных функций, типа printf, и используется почти во всех обычных программах (кстати сказать, многие функции этой библиотеки - простые обертки над вызовами ядра).

В FASMе существуют удобные макросы, для вызова си функций..., но я не буду их использовать, отдав предпочтение чистому ассемблеру.

Пример:

format ELF
section '.text' executable

extrn printf

public main
main:
        push msg
        call printf
        add  esp,4
        ret

section '.data' writeable

        msg db "Hello world!\n",0
Компилируется это дело так:
   fasm hellolib.asm hellolib.o
   gcc -o hellolib hellolib.o

Заключение

Ну вот вы и написали свою первую программу на ассемблере под UNIX.

Все на много проще чем кажется, неправда ли?

Eсли у вас возникнут какие-либо вопросы, пишите мне на adain@mail.ru, или на форум http://wasm.ru/forum/

До встречи.

Примеры к статье: http://www.opennet.ru/soft/asminunix.zip (http://www.wasm.ru/pub/28/files/asminunix.zip)

© marlyn

© 2002 wasm.ru - all rights reserved and reversed

Статья взята с сайта OpenNet. Оригинал расположен по адресу: http://www.wasm.ru/print.php?article=asminunix.

[ опубликовано 29/12/2003 ]

marlyn (adain@mail.ru) - Ассемблер в Unix   Версия для печати