栈帧

栈帧的概念:

每个线程都有自己的栈空间,对应线程的每一个执行的函数,都有一个相对应的栈帧,栈帧存有该函数的入参、出参、局部变量以及返回地址,一个完整的栈信息就是由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中

 

posted @ 2022-06-11 12:27  ho966  阅读(114)  评论(0)    收藏  举报