Ассемблер в Unix

#Введение

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

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

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

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

#Инструменты

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

  1. Собственно какая-либо unix-совместимая ось. (например linux, или лучше FreeBSD ),
  2. Компилятор fasm. ( 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

Порядок размещения параметров такой:

№ параметраРегистры
1
 
ebx
2ecx
3edx
4
 
esi
5
 
edi
6ebp

Как видите максимальное количество параметров - 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 - будет вашим домашним заданием. (для самых ленивых - см. примеры к статье)

------------------[cut]-----------------------------------

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

------------------[end cut]-------------------------------
  

Сборка.
Сначала скомпилируем файл, вот так:

  fasm hello.asm hello.o

А потом слинкуем:

  ld -o hello hello.o

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

 

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

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

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

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

Пример:

------------------[cut]-----------------------------------
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 
------------------[end cut]-------------------------------
  

Компилируется это дело так:

   fasm hellolib.asm hellolib.o
   gcc -o hellolib hellolib.o

Заключение.

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

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

До встречи.

No comments yet! Why don't you be the first?

Post Comment

Your email address will not be published. Required fields are marked *