还是看实际例子更直接,对于这样一份 C 代码:
1 2 3 4 5 6 7 8 9 10
| int add (int a, int b) { return a + b; }
int main (void) { int a = 10; int b = 20; int c = add(a, b); return c; }
|
先使用 gcc
编译
1
| [dou@localhost ~ 0 ]$ gcc -g -O0 hello.c -o hello
|
然后使用 objdump
生成 Intel
风格的汇编代码
只摘取其中结果中最重要的汇编代码,#
之后的内容为手动加的注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| [dou@localhost ~ 0 ]$ objdump -M intel -j .text -d hello
00000000004004ed <add>: 4004ed: 55 push rbp # 将 rbp 寄存器的值压入栈 4004ee: 48 89 e5 mov rbp,rsp # 将 rsp 寄存器的值 移动到 rbp 寄存器,栈底(rbp)移动到原来的栈顶的位置(rsp) 4004f1: 89 7d fc mov DWORD PTR [rbp-0x4],edi # 将 edi 寄存器的值(参数 a),移动到 -0x4(相对于 rbp 的 地址) 4004f4: 89 75 f8 mov DWORD PTR [rbp-0x8],esi # 将 esi 寄存器的值(参数 b),移动到 -0x8(相对于 rbp 的 地址) 4004f7: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] # 将 -0x8 的值移动到 eax 4004fa: 8b 55 fc mov edx,DWORD PTR [rbp-0x4] # 将 -0x4 的值移动到 edx 4004fd: 01 d0 add eax,edx # eax += edx // a + b; 4004ff: 5d pop rbp # 从栈顶弹出一个值,放到 rbp 里 400500: c3 ret # 从栈顶弹出一个值,放到 rip 里,也就是相当于 pop rip
0000000000400501 <main>: 400501: 55 push rbp # 将 rbp 压入栈 400502: 48 89 e5 mov rbp,rsp # 将 rsp 寄存器的值 移动到 rbp 寄存器,栈底(rbp)移动到原来的栈顶的位置(rsp) 400505: 48 83 ec 10 sub rsp,0x10 # rsp -= 0x10,栈顶向下生长高度 0x10 400509: c7 45 fc 0a 00 00 00 mov DWORD PTR [rbp-0x4],0xa # 将整数 0xa 移动到 -0x4(相对于 rbp) // a = 10 400510: c7 45 f8 14 00 00 00 mov DWORD PTR [rbp-0x8],0x14 # 将整数 0x14 移动到 -0x8(相对于 rbp) // b = 20 400517: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8] # 将 -0x8 移动到 edx 40051a: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] # 将 -0x4 移动到 eax 40051d: 89 d6 mov esi,edx # esi = edx; 为第一个参数寄存器赋值 40051f: 89 c7 mov edi,eax # edi = eax; 为第二个参数寄存器赋值 400521: e8 c7 ff ff ff call 4004ed <add> # 调用函数 add // add(a, b) 400526: 89 45 f4 mov DWORD PTR [rbp-0xc],eax # 将 eax 移动到 -0xc; 返回值入栈 400529: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc] # 将 -0xc 移动到 eax; 准备 main 函数返回值 40052c: c9 leave # 相当于mov rsp, rbp; + pop rbp; 将 rbp 和 rsp 回退到 上一帧 40052d: c3 ret # 从栈顶弹出一个值,放到 rip 里,也就是相当于 pop rip 40052e: 66 90 xchg ax,ax # nop
|
这里有几个知识点
0> 函数返回值寄存器
函数调用的返回值,会放入 rax
寄存器。
1> 函数参数寄存器
当函数参数少于 6 个的时候,参数从左到右依次放入:rdi
, rsi
, rdx
, rcx
, r8
, r9
。
当大于 6 个参数时,剩余的参数从右边往左一次压入栈(取参数的时候,依次弹出,就是自然从左到右的顺序)
2> 函数调用的栈操作
call
相当于 push rip; + jump [address];
ret
相当于 pop rip;
3> callee-saved vs caller-saved
r12
, r13
, r14
, r15
, rbx
, rbp
是 callee-saved 寄存器。
r10
, r11
,函数参数、返回值寄存器,都是 caller-saved 寄存器。
rsp
寄存器有一点点特殊,但是严格意义上也属于 callee-saved 寄存器。