[Введення.]
Ця стаття покликана описати програмування на асемблері під Linux. У цій статті ми порівняємо AT&T і Intel синтаксисы асемблера і розглянемо, як використовувати системні виклики.
У деяких частинах цієї статті міститься код, отриманий експериментальним шляхом, і тому в ньому можуть бути допущені помилки.
Для прочитання цієї статті потрібно тільки базове знання асемблера.
[Intel і AT&T синтаксисы.]
Intel і AT&T синтаксисы дуже різні за «зовнішності», і це може призвести до плутанини при переході, припустимо, з Intelовского на AT&Тшный, і навпаки.
[Префікси.]
В 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&Тшная форма інструкцій, що містять математичні дії, дуже складна у порівнянні з 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&Тшной мнемонике використовуються суфікси. Ці суфікси вказують на розмір операндів. AT&Тшный суфікс «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. А також великий список розташований тут: www.linuxassembly.org/syscall.html. Всі ці функції можуть бути викликані перериванням int $0x80.
[Системні виклики з < 6 аргументами.]
Для всіх системних викликів номер функції лягає в %eax. Для функцій кількість аргументів, яких не перевищує п’яти аргументи лягають в %ebx, %ecx, %edx, %esi, %edi відповідно. Результат виконання функції повертається в %eax.
Номери функцій ви можете знайти в /usr/include/sys/syscall.h. Виглядають вона так: «ЅУЅ_имя функції». Наприклад: 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
$

The End…

Оригінал: asm.sourceforge.net/articles/linasm.html
Автор: Phillip