一、Linux 函数调用过程解析
在x86的计算机系统中,内存空间中的栈主要用于保存函数的参数,返回值,返回地址,本地变量等。一切的函数调用都要将不同的数据、地址压入或者弹出栈。因此,为了更好地理解函数的调用,需要先来看看栈是怎么工作的。
1、什么是栈帧?
栈帧,也就是stack frame,其本质就是一种栈,只是这种栈专门用于保存函数调用过程中的各种信息(参数,返回地址,本地变量等)。栈帧有栈顶和栈底之分,其中栈顶的地址最低,栈底的地址最高,SP(栈指针)就是一直指向栈顶的。在x86-64bit中,用 %rbp 指向栈底,也就是基址指针;用 %rsp 指向栈顶,也就是栈指针。下面是一个栈帧的示意图:
2、函数调用过程解析
下面是对应的C语言代码:
#include <stdio.h>
int add(int n1, int n2)
{
int n = n1 + n2;
return n;
}
int test(int a, int b)
{
int n1 = 30;
int n2 = 40;
return add(n1, n2);
}
int main()
{
printf("ret=%d\n", test(10, 20));
return 0;
}
下面是main与test函数对应的反汇编指令
main函数调用test函数,所有函数开头的两行指令都是一样的,例如:
先压入main栈帧的栈基址,然后移动栈基址使它指向和栈指针同样的位置。此时产生了test的栈
移动test函数的栈顶指针,为test分配24个字节的栈空间,操作之后的栈空间如下:
test函数执行完返回,函数执行完毕一般会执行下面两条指令
执行完leaveq指令,恢复函数调用者对应的堆栈,下面是执行完leave对应的栈空间,如下:
执行完leave指令后会执行ret指令,将栈顶的返回地址弹出到EIP,然后按照EIP此时指示的指令地址继续执行程序,执行完ret指令后的栈帧如下: