函数栈帧与IP寄存器

145 阅读2分钟

接着上篇文章内容

函数栈帧与BP寄存器

我们继续构造两个函数的栈帧,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