接着上篇文章内容
我们继续构造两个函数的栈帧,GDB调试,当程序在main函数停下时,查看寄存器
从汇编代码可以看出,执行完foo函数后,回到main函数的返回地址是0x4011e4。
当前的寄存器状态如下
(gdb) info registers rbp
rbp 0x7fffe1de03f0 0x7fffe1de03f0
(gdb) info registers rsp
rsp 0x7fffe1de03e0 0x7fffe1de03e0
(gdb) info registers rip
rip 0x4011da 0x4011da <main+15>
(gdb)
继续执行程序,在foo函数停下,查看寄存器
从上篇文章,我们知道,BP寄存器存的地址里存的是main函数的BP地址,那返回地址呢?
在 x86 架构中,返回地址通常占用 4 个字节(32 位系统)或 8 个字节(64 位系统)。这个返回地址存储了函数执行完毕后要返回到的指令地址,用于指示程序继续执行的位置。
32 位系统(x86):返回地址通常占用 4 个字节,因为在 32 位系统中地址空间是 32 位长。
64 位系统(x86-64):返回地址通常占用 8 个字节,因为在 64 位系统中地址空间是 64 位长。
这个返回地址在函数调用时被压入栈中,用于在函数执行完毕后返回到调用位置。当函数执行结束时,程序将从这个返回地址取出地址值,跳转到该地址以继续执行程序。
我们打印rbp+8地址存储的内容,恰好是main函数的返回地址,0x4011e4 <main+25>
(gdb) info registers rbp
rbp 0x7fffe1de03d0 0x7fffe1de03d0
(gdb) x/a 0x7fffe1de03d8
0x7fffe1de03d8: 0x4011e4 <main+25>
(gdb)
由此,我们可以构造出两个函数的栈帧结构。
这里,我们也可以思考出,函数调用栈的解析过程:
因为当前函数的BP一定在寄存器里,是已知的,然后根据当前函数的BP前后数据可查到上个函数的返回地址和BP,依次类推,可以回溯出所有函数的调用栈。
High Address
+-----------------+
| 上个函数的BP |
+-----------------+ <--- BP (0x7fffe1de03f0)
| ... |
+-----------------+ <--- SP (0x7fffe1de03e0)
| main的返回地址 | 0x4011e4 <main+25>
+-----------------+ <--- BP + 8 (0x7fffe1de03d8)
| main函数的BP | 0x7fffe1de03f0
+-----------------+ <--- BP (0x7fffe1de03d0)
| b |
+-----------------+ <--- BP - 4
| c |
+-----------------+ <--- BP - 8
| a |
+-----------------+ <--- BP - c
| ... |
+-----------------+
| |
| 未使用的空间 |
| |
+-----------------+
Low Address