让我们写一段简单的代码来分析
#include <stdio.h>
void foo() {
int a = 1;
int b = 2;
int c = 3;
printf("%p\n", &a);
}
int main(int argc, char const *argv[])
{
foo();
return 0;
}
通过GDB调试,我们可以查看寄存器和汇编代码,方便我们理解
这里我们在main,foo函数加上断点
(gdb) b main
Breakpoint 1 at 0x4011da: file test_rbp.c, line 13.
(gdb) b foo
Breakpoint 2 at 0x40119d: file test_rbp.c, line 4.
通过layout split命令同时查看源码和汇编代码
通过run命令,首先进入main函数,查看此时的rbp寄存器存储的地址为0x7ffe5bd314a0
通过continue命令,继续执行
进入foo函数停下,查看此时的rbp为0x7ffe5bd31480
查看rbp这个地址存储的内容为0x7ffe5bd314a0,正好是main函数的基地址,由此我们可以得出,栈帧的基地址里存储的是上一个函数的基地址,以便在函数返回时恢复bp寄存器。
我们从汇编代码还可以看出,a的地址为%rbp-0xc,b的地址为%rbp-4,c的地址为%rbp-8
因为栈帧的地址由高地址向低地址扩展,所以这里是减法。这里我们也可以发现,栈帧里的参数顺序与代码中的顺序是不一致的。
由此,我们可以构造出foo函数的栈帧结构。
High Address
+-----------------+
| 上一个函数的返回地址 |
+-----------------+ <--- BP + 8
| 上一个函数的BP |
+-----------------+ <--- BP (Base Pointer)
| b |
+-----------------+ <--- BP - 4
| c |
+-----------------+ <--- BP - 8
| a |
+-----------------+ <--- BP - c
| ... |
+-----------------+
| |
| 未使用的空间 |
| |
+-----------------+
Low Address