1、AT&T格式匯編
在 Unix 和 Linux 系統中,更多采用的還是 AT&T 格式,兩者在語法格式上有着很大的不同:
-
在 AT&T 匯編格式中,寄存器名要加上 '%' 作為前綴;而在 Intel 匯編格式中,寄存器名不需要加前綴。例如:
AT&T 格式 Intel 格式 pushl %eax push eax -
在 AT&T 匯編格式中,用 '$' 前綴表示一個立即操作數;而在 Intel 匯編格式中,立即數的表示不用帶任何前綴。例如:
AT&T 格式 Intel 格式 pushl $1 push 1 -
AT&T 和 Intel 格式中的源操作數和目標操作數的位置正好相反。在 Intel 匯編格式中,目標操作數在源操作數的左邊;而在 AT&T 匯編格式中,目標操作數在源操作數的右邊。例如:
AT&T 格式 Intel 格式 addl $1, %eax add eax, 1 -
在 AT&T 匯編格式中,操作數的字長由操作符的最后一個字母決定,后綴'b'、'w'、'l'分別表示操作數為字節(byte,8 比特)、字(word,16 比特)和長字(long,32比特);而在 Intel 匯編格式中,操作數的字長是用 "byte ptr" 和 "word ptr" 等前綴來表示的。例如:
AT&T 格式 Intel 格式 movb val, %al mov al, byte ptr val - 在 AT&T 匯編格式中,絕對轉移和調用指令(jump/call)的操作數前要加上'*'作為前綴,而在 Intel 格式中則不需要。
-
遠程轉移指令和遠程子調用指令的操作碼,在 AT&T 匯編格式中為 "ljump" 和 "lcall",而在 Intel 匯編格式中則為 "jmp far" 和 "call far",即:
AT&T 格式 Intel 格式 ljump $section, $offset jmp far section:offset lcall $section, $offset call far section:offset 與之相應的遠程返回指令則為:
AT&T 格式 Intel 格式 lret $stack_adjust ret far stack_adjust -
在 AT&T 匯編格式中,內存操作數的尋址方式是
section:disp(base, index, scale)
而在 Intel 匯編格式中,內存操作數的尋址方式為:
section:[base + index*scale + disp]
由於 Linux 工作在保護模式下,用的是 32 位線性地址,所以在計算地址時不用考慮段基址和偏移量,而是采用如下的地址計算方法:
disp + base + index * scale
下面是一些內存操作數的例子:
AT&T 格式 Intel 格式 movl -4(%ebp), %eax mov eax, [ebp - 4] movl array(, %eax, 4), %eax mov eax, [eax*4 + array] movw array(%ebx, %eax, 4), %cx mov cx, [ebx + 4*eax + array] movb $4, %fs:(%eax) mov fs:eax, 4
2、寄存器
X84中原有8個32位通用寄存器%eax,%ebx,%ecx,%edx,%esi,%edi,%ebp,%esp, 在X86_64中
分別被擴展為64位,並且多了8個寄存器。因此X86_64的寄存器如下:
- rax, eax, ax, ah, al;
- rbx, ebx, bx, bh, bl;
- rcx, ecx, cx, ch, cl;
- rdx, edx, dx, dh, dl;
- rsi, esi, si;
- rdi, edi, di;
- rbp, ebp;
- rsp, esp;
- r8-r15;
GCC中對這些寄存器的調用規則如下:
- %rax 作為函數返回值使用。
- %rsp 棧指針寄存器,指向棧頂
- %rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函數參數,依次對應第1參數,第2參數。。。
- %rbx,%rbp,%r12,%r13,%14,%15 用作數據存儲,遵循被調用者使用規則,簡單說就是隨便用,調用子函數之前要備份它,以防他被修改
- %r10,%r11 用作數據存儲,遵循調用者使用規則,簡單說就是使用之前要先保存原值
3、棧幀
以一個簡單的add函數為例:
1 #include <stdio.h> 2 3 int add(int a, int b) 4 { 5 return a + b; 6 } 7 8 int main(void) 9 { 10 int c, a = 1, b = 2; 11 12 c = add(a, b); 13 14 return c; 15 }
編譯並反匯編,
1 scripts$ gcc -g test.c -o test 2 scripts$ objdump -S -d test > test.s 3 scripts$ vim test.s
1 0000000000400474 <add>: 2 #include <stdio.h> 3 4 int add(int a, int b) 5 { 6 400474: 55 push %rbp 7 400475: 48 89 e5 mov %rsp,%rbp 8 400478: 89 7d fc mov %edi,-0x4(%rbp) 9 40047b: 89 75 f8 mov %esi,-0x8(%rbp) 10 return a + b; 11 40047e: 8b 45 f8 mov -0x8(%rbp),%eax 12 400481: 8b 55 fc mov -0x4(%rbp),%edx 13 400484: 8d 04 02 lea (%rdx,%rax,1),%eax 14 } 15 400487: c9 leaveq 16 400488: c3 retq 17 18 0000000000400489 <main>: 19 20 int main(void) 21 { 22 400489: 55 push %rbp 23 40048a: 48 89 e5 mov %rsp,%rbp 24 40048d: 48 83 ec 10 sub $0x10,%rsp 25 int c, a = 1, b = 2; 26 400491: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp) 27 400498: c7 45 fc 02 00 00 00 movl $0x2,-0x4(%rbp) 28 29 c = add(a, b); 30 40049f: 8b 55 fc mov -0x4(%rbp),%edx 31 4004a2: 8b 45 f8 mov -0x8(%rbp),%eax 32 4004a5: 89 d6 mov %edx,%esi 33 4004a7: 89 c7 mov %eax,%edi 34 4004a9: e8 c6 ff ff ff callq 400474 <add> 35 4004ae: 89 45 f4 mov %eax,-0xc(%rbp) 36 37 return c; 38 4004b1: 8b 45 f4 mov -0xc(%rbp),%eax 39 }
從32、33行可知,edi<- a, esi<- b, 然后 call add,此時main函數中的棧幀結構如下(某個寄存器為x,則xH表示高4位):
然后跳入add中執行:
從4-12行完畢,eax存儲b, edx存儲a, 此時整體的棧幀結構:
之后是指令:
lea (%rdx,%rax,1),%eax
lea是load effective address,加載有效地址,類似於C語言中的“&”取地址符的作用,本例中是將 rdx + rax * 1 ==> eax, 即 eax = a + b,
然后rax自動作為返回值。