寫一段簡單的C代碼分析其背后與匯編指令的關系
最近在看hotspot的代碼,hotspot解釋器會將字節碼翻譯成匯編指令,所以要先復習下這個基礎
這篇講的太泛了,看 這篇吧,是一步一步有圖對應的
C代碼
#include <stdio.h>
int main(int args, char** argv){
printf("%d", add1(100, 200, 500, 600));
}
int add1(int i, int j, int k, int m){
return i + j + k + m;
}
gcc編譯驗證執行結果:
gcc -g2 FunctionInvokedAssembly.c -o FunctionInvokedAssembly
./FunctionInvokedAssembly
#1400
gcc編譯成匯編代碼
gcc -S -o FunctionInvokedAssembly.s FunctionInvokedAssembly.c
匯編代碼如下:
.file "FunctionInvokedAssembly.c"
.section .rodata
.LC0:
.string "%d"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl %edi, -4(%rbp)
movq %rsi, -16(%rbp)
movl $600, %ecx
movl $500, %edx
movl $200, %esi
movl $100, %edi
movl $0, %eax
call add1
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.globl add1
.type add1, @function
add1:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl %edx, -12(%rbp)
movl %ecx, -16(%rbp)
movl -8(%rbp), %eax
movl -4(%rbp), %edx
addl %eax, %edx
movl -12(%rbp), %eax
addl %eax, %edx
movl -16(%rbp), %eax
addl %edx, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size add1, .-add1
.ident "GCC: (Ubuntu 4.8.5-4ubuntu8) 4.8.5"
.section .note.GNU-stack,"",@progbits
匯編用到的一些寄存器及一些指令
- eax, ebx, ecx, edx, esi, edi, ebp(rbp), esp(rbp)等都是X86 匯編語言中CPU上的通用寄存器的名稱。
- rbp 調用函數的棧幀棧底地址
- rsp 被調函數的棧幀棧底地址
- eip:寄存器存放下一個CPU指令存放的內存地址,當CPU執行完當前的指令后,從EIP寄存器中讀取下一條指令的內存地址,然后繼續執行
- 減少esp(rsp)寄存器的值表示擴展棧幀
- X86-64中,所有寄存器都是64位,相對32位的x86來說,標識符發生了變化,比如:從原來的%ebp變成了%rbp。為了向后兼容性,%ebp依然可以使用,不過指向了%rbp的低32位。
- X86-64有16個64位寄存器,分別是:%rax,%rbx,%rcx,%rdx,%esi,%edi,%rbp,%rsp,%r8,%r9,%r10,%r11,%r12,%r13,%r14,%r15。%rax 作為函數返回值使用。%rsp 棧指針寄存器,指向棧頂。%rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函數參數,依次對應第1參數,第2參數...%rbx,%rbp,%r12,%r13,%14,%15 用作數據存儲,遵循被調用者使用規則,簡單說就是隨便用,調用子函數之前要備份它,以防他被修改。%r10,%r11 用作數據存儲,遵循調用者使用規則,簡單說就是使用之前要先保存原值
一條call指令,完成了兩個任務:
- 將調用函數(main)中的下一條指令入棧,被調函數返回后將取這條指令繼續執行,64位rsp寄存器的值減8
- 修改指令指針寄存器rip的值,使其指向被調函數的執行位置
寄存器圖示
63 31 0
+------------------------------+
|%rax |%eax | 返回值
+------------------------------+
|%rbx |%ebx | 被調用保護者
+------------------------------+
|%rcx |%ecx | 第四個參數
+------------------------------+
|%rdx |%edx | 第三個參數
+------------------------------+
|%rsi |%esi | 第二個參數
+------------------------------+
|%rdi |%edi | 第一個參數
+------------------------------+
|%rbp |%ebp | 被調用者保護
+------------------------------+
|%rsp |%esp | 堆棧指針
+------------------------------+
|%r8 |%r8d | 第五個參數
+------------------------------+
|%r9 |%r9d | 第六個參數
+------------------------------+
|%r10 |%r10d | 調用者保護
+------------------------------+
|%r11 |%r11d | 調用者保護
+------------------------------+
|%r12 |%r12d | 被調用者保護
+------------------------------+
|%r13 |%r13d | 被調用者保護
+------------------------------+
|%r14 |%r14d | 被調用者保護
+------------------------------+
|%r15 |%r15d | 被調用者保護
+------------------------------+
棧幀
+-------------------+
| |
| |
| other frames |
| |
| |
+-------------------+
| |
| |
| last frame |
| |
| |
+-------------------+
| argument 1 |
+-------------------+
| argument 2 |
+-------------------+
| return address |
+-------------------+
%ebp-> | last frame %ebp |
+-------------------+
| |
| |
| current frame |
| |
| |
+-------------------+
%esp-> | |
+-------------------+
入口函數是main,然后調用各個子函數。在對應機器語言中,GCC把過程轉化成棧幀(frame),簡單的說,每個棧幀對應一個過程。X86-32典型棧幀結構中,由%ebp指向棧幀開始,%esp指向棧頂。
gcc邊調試邊反編譯匯編代碼
gdb FunctionInvokedAssembly
> b main
> r
> disassemble /rm
Breakpoint 1, main (args=1, argv=0x7fffffffdf48) at FunctionInvokedAssembly.c:11
11 printf("%d", add1(100, 200, 500, 600));
(gdb) disassemble /rm
Dump of assembler code for function main:
9 int main(int args, char** argv){
0x00000000004004fd <+0>: 55 push %rbp
0x00000000004004fe <+1>: 48 89 e5 mov %rsp,%rbp
0x0000000000400501 <+4>: 48 83 ec 10 sub $0x10,%rsp
0x0000000000400505 <+8>: 89 7d fc mov %edi,-0x4(%rbp)
0x0000000000400508 <+11>: 48 89 75 f0 mov %rsi,-0x10(%rbp)
10 // printf("%d", add1(100, 200, 500, 600, 700, 800, 900, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13));
11 printf("%d", add1(100, 200, 500, 600));
=> 0x000000000040050c <+15>: b9 58 02 00 00 mov $0x258,%ecx
0x0000000000400511 <+20>: ba f4 01 00 00 mov $0x1f4,%edx
0x0000000000400516 <+25>: be c8 00 00 00 mov $0xc8,%esi
0x000000000040051b <+30>: bf 64 00 00 00 mov $0x64,%edi
0x0000000000400520 <+35>: b8 00 00 00 00 mov $0x0,%eax
0x0000000000400525 <+40>: e8 13 00 00 00 callq 0x40053d <add1>
0x000000000040052a <+45>: 89 c6 mov %eax,%esi
0x000000000040052c <+47>: bf f4 05 40 00 mov $0x4005f4,%edi
0x0000000000400531 <+52>: b8 00 00 00 00 mov $0x0,%eax
0x0000000000400536 <+57>: e8 b5 fe ff ff callq 0x4003f0 <printf@plt>
12 }
0x000000000040053b <+62>: c9 leaveq
0x000000000040053c <+63>: c3 retq
End of assembler dump.
> info register
rax 0x4004fd 4195581
rbx 0x0 0
rcx 0x400570 4195696
rdx 0x7fffffffdf58 140737488346968
rsi 0x7fffffffdf48 140737488346952
rdi 0x1 1
rbp 0x7fffffffde60 0x7fffffffde60
rsp 0x7fffffffde50 0x7fffffffde50
r8 0x7ffff7dd0d80 140737351847296
r9 0x7ffff7dd0d80 140737351847296
r10 0x0 0
r11 0x0 0
r12 0x400400 4195328
r13 0x7fffffffdf40 140737488346944
r14 0x0 0
r15 0x0 0
rip 0x40050c 0x40050c <main+15>
eflags 0x206 [ PF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
參考
X86-64寄存器和棧幀
函數調用過程探究
X86 Opcode and Instruction Reference
你會swap嗎,按值傳遞還是按引用?
寄存器理解 及 X86匯編入門