通过《从汇编层看64位程序运行——栈帧(Stack Frame)的边界》、《从汇编层看64位程序运行——参数传递的底层实现》、《从汇编层看64位程序运行——栈上变量的rbp表达》和《从汇编层看64位程序运行——函数的调用和栈平衡》这四篇的讲解,栈帧的组成基本清晰了。
首先栈帧的起始地址是:调用者函数调用子函数(call指令)前的RSP。
这就意味着call指令压栈的Next RIP也属于子函数的栈帧,而通过栈向子函数传递参数的空间则属于上一个栈帧。
以下面代码为例
void foo10(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) {
a = a + 5;
b = b + 5;
c = c + 5;
d = d + 5;
e = e + 5;
f = f + 5;
g = g + 5;
h = h + 5;
i = i + 5;
j = j + 5;
int sum = a + b + c + d + e + f + g + h + i + j;
sum = sum + 5;
}
int main() {
int a = 10;
int b = 20;
int c = 30;
int d = 40;
int e = 50;
int f = 60;
int g = 70;
int h = 80;
int i = 90;
int j = 100;
foo10(a, b, c, d, e, f, g, h, i, j);
return 0;
}
main函数的反汇编如下
foo10函数的栈帧起始地址就是代码执行到+120处(+118行执行完,但是+120行没执行)时的rsp寄存器的值。
之前+105,+109,+113和+117处的压栈空间,都是属于main函数的栈帧。
所以一个栈帧依次包含:
- 本函数执行完毕后,跳转会的指令地址(Caller‘s ’Next RIP)。见《从汇编层看64位程序运行——函数的调用和栈平衡》
- 上一个栈帧的EBP。见《从汇编层看64位程序运行——栈上变量的rbp表达》
- 本函数的局部非静态变量。
- 待调用的子函数的,需要用栈传递的参数。见《从汇编层看64位程序运行——参数传递的底层实现》
抛出一个问题:一个函数是否可以访问不是自己栈帧上的数据?
答案是可以的。
因为我们之前提过,栈帧是一个虚拟概念。人们只是借用它来方便表述函数在运行过程中的栈的变化。
比如在调用超过6个参数的函数时,子函数内部就会访问到上个栈帧中的数据,以获取第6个以外的其他参数。