C源码
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main(int argc, char **argv)
{
int sum = add(10, 20);
return 0;
}
x86-64-Intel汇编分析
main:
0x000055555555460e <+0>: push rbp //压栈:两步走:1)%rsp-1 2)mov rsp, rbp
0x000055555555460f <+1>: mov rbp,rsp //保存rsp当前位置
0x0000555555554612 <+4>: sub rsp,0x20 //rsp分配0x20栈空间
0x0000555555554616 <+8>: mov DWORD PTR [rbp-0x14],edi //argv参数压栈保存
0x0000555555554619 <+11>: mov QWORD PTR [rbp-0x20],rsi //argc参数压栈保存
0x000055555555461d <+15>: mov esi,0x14 // 20 存进 $esi (32bit)
0x0000555555554622 <+20>: mov edi,0xa // 10 存进 $edi (32bit)
0x0000555555554627 <+25>: call 0x5555555545fa <add> //调用add 1)%rsp -1 2)%rip值(call指令下一条指令地址)压栈
0x000055555555462c <+30>: mov DWORD PTR [rbp-0x4],eax // %eax(值:30)写入main函数栈中
0x000055555555462f <+33>: mov eax,0x0
0x0000555555554634 <+38>: leave
0x0000555555554635 <+39>: ret
add:
0x00005555555545fa <+0>: push rbp //保存main函数栈帧寄存器$rbp值(保护现场)
0x00005555555545fb <+1>: mov rbp,rsp //保存main函数%rsp值,因为没动态局部变量,不需提前分配栈空间
0x00005555555545fe <+4>: mov DWORD PTR [rbp-0x4],edi //10 存进add函数栈中
0x0000555555554601 <+7>: mov DWORD PTR [rbp-0x8],esi //20 存进add函数栈中
0x0000555555554604 <+10>: mov edx,DWORD PTR [rbp-0x4] //从add函数栈中取出10保存在$edx寄存器
0x0000555555554607 <+13>: mov eax,DWORD PTR [rbp-0x8] // 从add函数栈中取出20保存在$eax寄存器
0x000055555555460a <+16>: add eax,edx // %eax = %eax + %edx = 10 + 10 = 20
0x000055555555460c <+18>: pop rbp //出栈:1)%rsp值(main函数之前栈顶位置) 存进%rbp 2)%rsp +1
0x000055555555460d <+19>: ret // 1)%rip 指向call 指令的下一条指令地址 2)%rsp + 1
函数调用栈图解
总结
1> gcc 编译器默认传参顺序“从右往左”依次压入寄存器%r9 %8 %rcx %rdx %rsi %rdi ,第7个(包含7)之后参数压入caller栈中。
2> push rbp :1) %rsp栈顶指针减8字节(64位CPU) 2)%rbp栈帧寄存器值压入栈中
3> call 指令 :1) %rsp-0x8 后,caller中call指令下一条指令地址(%rip)压入caller 栈中 2)%rip:指向callee函数第一条指令地址
4> pop rbp : 1) %rbp: 指向caller函数原来栈帧位置 2)%rsp + 0x8(64位CPU)
5> ret 指令 :1) %rip:指向caller函数call指令的下一条指令地址 2) %rsp + 0x8(64位CPU)
6> 由上可知,push call 指令用于保护现场,而 pop ret 指令用于恢复现场