函数调用过程
- ax(accumulator): 可用于存放函数返回值
- bp(base pointer): 用于存放执行中的函数对应的栈帧的栈底地址
- sp(stack poinger): 用于存放执行中的函数对应的栈帧的栈顶地址
- ip(instruction pointer): 指向当前执行指令的下一条指令
调用子程序时先保存堆栈信息(某些堆栈相关的寄存器),待子程序返回后将堆栈恢复到调用前的状态(堆栈用于保存局部变量、函数参数等重要信息),以保证程序能够继续正确运行,是为堆栈平衡。
这里以一个简单的C语言代码为例,来分析函数调用过程
代码:
1 #include <stdio.h> 2 3 int func(int param1 ,int param2,int param3) 4 { 5 int var1 = param1; 6 int var2 = param2; 7 int var3 = param3; 8 9 printf("var1=%d,var2=%d,var3=%d",var1,var2,var3); 10 return var1; 11 } 12 13 int main(int argc, char* argv[]) 14 { 15 int result = func(1,2,3); 16 17 return 0; 18 }
首先说明,在堆栈中变量分布是从高地址到低地址分布,EBP是指向栈底的指针,在过程调用中不变,又称为帧指针。ESP指向栈顶,程序执行时移动,ESP减小分配空间,ESP增大释放空间,ESP又称为栈指针。
下面来逐步分析函数的调用过程
1.函数main执行,main各个参数从右向左逐步压入栈中,最后压入返回地址
2.执行第15行,3个参数以从右向左的顺序压入堆栈,及从param3到param1,栈内分布如下图:
3.然后是返回地址入栈:此时的栈内分布如下:
4.第3行函数调用时,通过跳转指令进入函数后,函数地址入栈后,EBP入栈,然后把当前ESP的值给EBP,对应的汇编指令:
push ebp mov ebp esp
此时栈顶和栈底指向同一位置,栈内分布如下:
5.第5行开始执行, int var1 = param1; int var2 = param2; int var3 = param3;按申明顺序依次存储。对应的汇编:
mov 0x8(%ebp),%eax mov %eax,-0x4(%ebp)
其中将[EBP+0x8]地址里的内容赋给EAX,即把param的值赋给EAX,然后把EAX的中的值放到[EBP-4]这个地址里,即把EAX值赋给var1,完成C代码 int var1 = param1,其他变量雷同。
6.第9行,输出结果,第10行执行 对应的汇编代码:
mov -0x4(%ebp),%eax
最后通过eax寄存器保存函数的返回值;
一条call指令,完成了两个任务:
- 将调用函数中的下一条指令入栈,被调函数返回后将取这条指令继续执行,64位rsp寄存器的值减8
- 修改指令指针寄存器rip的值,使其指向被调函数的执行位置
leave指令等价于以下两条指令:
mov %rbp, %rsp
pop %rbp