Введение.
Эта статья призвана описать программирование на ассемблере под Linux. В этой статье мы сравним AT&T и Intel синтаксисы ассемблера и рассмотрим, как использовать системные вызовы. В некоторых частях этой статьи содержится код, полученный экспериментальным путём, и поэтому в нём могут быть допущены ошибки.
Для прочтения этой статьи требуется только базовое знание ассемблера.
Intel и AT&T синтаксисы.
Intel и AT&T синтаксисы очень разные по «внешности», и это может привести путанице при переходе, допустим, с Intel’овского на AT&T’шный, и наоборот.
Префиксы.
В Intel синтаксисе не используются приставки не перед регистрами, не перед данными. В AT&T же, однако, перед регистрами ставится приставка «%», а перед данными «$». В Intel синтаксисе после шестнадцатеричных и двоичных данных ставятся суффиксы «h» и «b», соответственно. Также если шестнадцатеричное число начинается с буквы, то добавляется приставка «0»
Например:
+++++++++++++++++++++++++++++++++++++++++++++++++
+-Intel синтаксис:----+--AT&T синтаксис:--------+
+++++++++++++++++++++++++++++++++++++++++++++++++
+-mov------eax,1------+movl------$1,%eax--------+
+-mov------ebx,0FFh---+movl------$0xff,%ebx-----+
+-int------80h--------+int-------$0x80----------+
+++++++++++++++++++++++++++++++++++++++++++++++++
Порядок операндов.
В Intel и AT&T порядок операндов противоположен. В Intel первый операнд – приемник, а второй – источник. А в AT&T первый – источник, второй – приемник. Преимущество AT&T тут очевидно. Мы читаем слева направо, мы пишем слева направо, таким образом, этот порядок привычнее и естественнее.
Например:
+++++++++++++++++++++++++++++++
+-Intel синтаксис:--------+-AT&T синтаксис:-----+
+++++++++++++++++++++++++++++++
+ instr-----dest,source---+instr------source,dest--+
+ mov-----eax,[ecx]------+movl-----(%ecx),%eax+
+++++++++++++++++++++++++++++++
Операнды памяти.
Операнды памяти, как вы, наверное, уже заметили, тоже различаются. В Intel синтаксисе регистры, содержащие указатель на некоторую область памяти, заключаются в квадратные скобки: «[» и «]». А в AT&T в круглые: «(» и «)».
Например:
+++++++++++++++++++++++++++++++++
+-Intel синтаксис:--------+-AT&T синтаксис:---------+
+++++++++++++++++++++++++++++++++
+mov------eax, [ebx]-----+movl-------(%ebx),%eax-+
+mov------eax [ebx+3]---+movl------3(%ebx),%eax+
+++++++++++++++++++++++++++++++++
AT&T’шная форма инструкций, содержащих математические действия, очень сложна по сравнению с Intel’овской. Допустим, имеется инструкция на Intel синтаксисе: «segreg:[base+index*scale+disp]». На AT&T аналогичная инструкция будет выглядеть так: «%segreg:disp(base,index,scale)».
Index/scale/disp/segreg опциональны и могут быть опущены. Если index и/или scale не определены, то будут принимать дефолтное значение – «1».Segreg зависит от того выполняется ли приложение в реальном или защищенном режиме. В реальном режиме зависит от инструкции, а в защищенном нет.
Например:
+++++++++++++++++++++++++++++++++++++++++++++++++++++
+-Intel синтаксис:-------------------------------+-AT&T синтаксис:--------------------------+
+++++++++++++++++++++++++++++++++++++++++++++++++++++
+instr-foo,segreg:[base+index*scale+disp]+instr-%segreg:disp(base,index,scale),foo+
+mov-------eax,[ebx+20h]---------------------+movl------0x20,(%ebx),%eax-------------+
+add-------eax,[ebx+ecx*2h]-----------------+addl-------(%ebx,%ecx,0x2),%eax------+
+lea--------eax,[ebx+ecx]----------------------+leal--------(%ebx,%ecx),%eax-----------+
+sub-------eax,[ebx+ecx*4h-20h]------------+subl------ -0x20(%ebx,%ecx,0x4),%eax+
+++++++++++++++++++++++++++++++++++++++++++++++++++++
Как видите AT&T не очень понятен. «segreg:[base+index*scale+disp]» гораздо легче для восприятия, чем «%segreg:disp(base,index,scale)».
Суффиксы.
Как вы, возможно, заметили в AT&T’шной мнемонике используются суффиксы. Эти суффиксы указывают на размер операндов. AT&T’шный суффикс «l» соответствует Intel’овскому «DWORD», «w» - «WORD» и «b» - «byte».
Например:
+++++++++++++++++++++++++++++++++
+-Intel синтаксис:----------+-AT&T синтаксис:-------+
+++++++++++++++++++++++++++++++++
+mov------al,bl---------------+movb-----%bl,%al-------+
+mov------ax,bx-------------+movw-----%bx,%ax----+
+mov------eax,ebx----------+movl------%ebx,%eax--+
+mov------eax,dword [ebx]+movl------(%ebx),%eax+
+++++++++++++++++++++++++++++++++
Системные вызовы.
В этой части мы рассмотрим системные вызовы. Системные вызовы составляют все функции из второй части мануала расположенного в /usr/man/man2. Также их можно найти в /usr/include/sys/syscall.h. А также большой список расположен здесь: http://www.linuxassembly.org/syscall.html. Все эти функции могут быть вызваны прерыванием int $0x80.
Системные вызовы с < 6 аргументами.
Для всех системных вызовов номер функции ложится в %eax. Для функций количество аргументов, которых не превышает пяти аргументы ложатся в %ebx, %ecx, %edx, %esi, %edi соответственно. Результат выполнения функции возвращается в %eax.
Номера функций вы можете найти в /usr/include/sys/syscall.h. Выглядят она так: «SYS_имя функции». Например: SYS_exit, SYS_close, и т.д.
Например:
(Напишем Hello World, куда же без него)
Согласно описанию из man страницы, функция «write» объявлена так: «ssize_t write(int fd, const void *buf, size_t count);».
Следовательно «fd» ложем в %ebx, «buf» в %ecx, «count» в %edx и «SYS_write» в %eax. Далее вызываем прерывание «int $0x80», которое выполнит нашу функцию и вернет в %eax результат.
$ cat write.s
.include "defines.h"
.data
hello:
.string "hello worldn"
.globl main
main:
movl $SYS_write,%eax
movl $STDOUT,%ebx
movl $hello,%ecx
movl $12,%edx
int $0x80
ret
$
Аналогично вызываются все функции с количеством аргументов не превышающем пяти.
Системные вызовы с > 5 аргументами.
В этих функциях номер функции по-прежнему ложится в %eax, но аргументы уже записываются в структуру, и указатель на неё ложится в %ebx. Также можно положить аргументы в стек в обратном порядке и в %ebx поместить указатель на вершину стека.
Например:
$ cat mmap.s
.include "defines.h"
.data
file:
.string "mmap.s"
fd:
.long 0
filelen:
.long 0
mappedptr:
.long 0
.globl main
main:
push %ebp
movl %esp,%ebp
subl $24,%esp
// open($file, $O_RDONLY);
movl $fd,%ebx // save fd
movl %eax,(%ebx)
// lseek($fd,0,$SEEK_END);
movl $filelen,%ebx // save file length
movl %eax,(%ebx)
xorl %edx,%edx
// mmap(NULL,$filelen,PROT_READ,MAP_SHARED,$fd,0);
movl %edx,(%esp)
movl %eax,4(%esp) // file length still in %eax
movl $PROT_READ,8(%esp)
movl $MAP_SHARED,12(%esp)
movl $fd,%ebx // load file descriptor
movl (%ebx),%eax
movl %eax,16(%esp)
movl %edx,20(%esp)
movl $SYS_mmap,%eax
movl %esp,%ebx
int $0x80
movl $mappedptr,%ebx // save ptr
movl %eax,(%ebx)
// write($stdout, $mappedptr, $filelen);
// munmap($mappedptr, $filelen);
// close($fd);
movl %ebp,%esp
popl %ebp
ret
$
**Внимание: Этот исходник обрезан, для того чтобы показать как вызывать функции с кол-вом аргументов превышающим 5.
Работа с сокетами.
Функции сокетов вызываются несколько иначе, чем другие, в %eax всегда ложится одно число: «SYS_socketcall». А идентификатор самой функции ложится в %ebx. Список идентификаторов можно найти в /usr/include/linux/net.h. Аргументы ложатся в структурустек, и указатель на структурустек ложится в %ecx/
Например:
$ cat socket.s
.include "defines.h"
.globl _start
_start:
pushl %ebp
movl %esp,%ebp
sub $12,%esp
// socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
movl $AF_INET,(%esp)
movl $SOCK_STREAM,4(%esp)
movl $IPPROTO_TCP,8(%esp)
movl $SYS_socketcall,%eax
movl $SYS_socketcall_socket,%ebx
movl %esp,%ecx
int $0x80
movl $SYS_exit,%eax
xorl %ebx,%ebx
int $0x80
movl %ebp,%esp
popl %ebp
ret
$
Оригинал: http://asm.sourceforge.net/articles/linasm.html
Автор: Phillip
Перевод Xteen [Hackzona.Ru], 2007