栈帧
栈帧的概念:
每个线程都有自己的栈空间,对应线程的每一个执行的函数,都有一个相对应的栈帧,栈帧存有该函数的入参、出参、局部变量以及返回地址,一个完整的栈信息就是由N个栈帧组成的,栈空间的开辟是由高地址向低地址的。
写一个简单的函数调用
#include <cstdio> #include <string.h> void func2(int& a) { a+= 2; } int func1() { int a = 4; func2(a); return a; } int main() { func1(); }
反汇编可执行文件
objdump -d -C a.out > a.asm
00000000004004fd <func2(int&)>: 4004fd: 55 push %rbp //压栈,先存入上一栈帧的栈底地址 4004fe: 48 89 e5 mov %rsp,%rbp //把栈顶地址写入rbp寄存器,这样rbp就指向当前帧的栈底 400501: 48 89 7d f8 mov %rdi,-0x8(%rbp) 400505: 48 8b 45 f8 mov -0x8(%rbp),%rax 400509: 8b 00 mov (%rax),%eax 40050b: 8d 50 02 lea 0x2(%rax),%edx 40050e: 48 8b 45 f8 mov -0x8(%rbp),%rax 400512: 89 10 mov %edx,(%rax) 400514: 5d pop %rbp 400515: c3 retq 0000000000400516 <func1()>: 400516: 55 push %rbp 400517: 48 89 e5 mov %rsp,%rbp 40051a: 48 83 ec 10 sub $0x10,%rsp 40051e: c7 45 fc 04 00 00 00 movl $0x4,-0x4(%rbp) 400525: 48 8d 45 fc lea -0x4(%rbp),%rax 400529: 48 89 c7 mov %rax,%rdi //函数入参存入寄存器rdi 40052c: e8 cc ff ff ff callq 4004fd <func2(int&)> 400531: 8b 45 fc mov -0x4(%rbp),%eax 400534: c9 leaveq 400535: c3 retq 0000000000400536 <main>: 400536: 55 push %rbp 400537: 48 89 e5 mov %rsp,%rbp 40053a: e8 d7 ff ff ff callq 400516 <func1()> 40053f: b8 00 00 00 00 mov $0x0,%eax 400544: 5d pop %rbp 400545: c3 retq 400546: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 40054d: 00 00 00
func1调用func2,先将函数入参存入寄存器rdi,再执行callq,callq指令会将返回地址压入栈中,然后跳转到子程序或函数的入口地址,创建新栈帧
新栈帧第一件事就是压栈,存入上一栈帧的栈底地址
由此,我们可以得出:
通过寄存器rbp的地址解引用,我们可以得到上一帧的栈底,循环解引用,就可以得到所有栈帧的栈底, 栈底向上偏移一个指针,就是上一个函数的代码地址
接下来我们验证上述逻辑:
通过gdb直接查看堆栈
(gdb) bt #0 func2 (a=@0x7fffffffe3cc: 4) at main.cpp:6 #1 0x0000000000400531 in func1 () at main.cpp:12 #2 0x000000000040053f in main () at main.cpp:18
我们反推:
(gdb) i registers $rbp rbp 0x7fffffffe3b0 0x7fffffffe3b0 (gdb) p/x *(0x7fffffffe3b0+8) $10 = 0x400531 // 第1帧的代码段地址
(gdb) p/x *0x7fffffffe3b0 $16 = 0xffffe3d0 (gdb) p/x *(0x7fffffffe3d0+8) $20 = 0x40053f //第2帧代码地址
反推的代码地址和直接bt的一样
查看栈帧信息
(gdb) i frame 0 Stack frame at 0x7fffffffe3c0: rip = 0x400505 in func2 (main.cpp:6); saved rip 0x400531 called by frame at 0x7fffffffe3e0 source language c++. Arglist at 0x7fffffffe3b0, args: a=@0x7fffffffe3cc: 4 Locals at 0x7fffffffe3b0, Previous frame's sp is 0x7fffffffe3c0 Saved registers: rbp at 0x7fffffffe3b0, rip at 0x7fffffffe3b8
(gdb) i frame 1 Stack frame at 0x7fffffffe3e0: rip = 0x400531 in func1 (main.cpp:12); saved rip 0x40053f called by frame at 0x7fffffffe3f0, caller of frame at 0x7fffffffe3c0 source language c++. Arglist at 0x7fffffffe3d0, args: Locals at 0x7fffffffe3d0, Previous frame's sp is 0x7fffffffe3e0 Saved registers: rbp at 0x7fffffffe3d0, rip at 0x7fffffffe3d8
(gdb) i frame 2 Stack frame at 0x7fffffffe3f0: rip = 0x40053f in main (main.cpp:18); saved rip 0x7ffff72113d5 caller of frame at 0x7fffffffe3e0 source language c++. Arglist at 0x7fffffffe3e0, args: Locals at 0x7fffffffe3e0, Previous frame's sp is 0x7fffffffe3f0 Saved registers: rbp at 0x7fffffffe3e0, rip at 0x7fffffffe3e8
可以看出返回地址存在了寄存器rip中