开发一个 Linux 调试器(八):堆栈展开

2024年 7月 19日 50.7k 0

开发一个 Linux 调试器(八):堆栈展开-1

有时你需要知道的最重要的信息是什么,你当前的程序状态是如何到达那里的。有一个 backtrace 命令,它给你提供了程序当前的函数调用链。这篇文章将向你展示如何在 x86_64 上实现堆栈展开以生成这样的回溯。

系列索引

这些链接将会随着其他帖子的发布而上线。

  • 准备环境
  • 断点
  • 寄存器和内存
  • ELF 和 DWARF
  • 源码和信号
  • 源码级逐步执行
  • 源码级断点
  • 堆栈展开
  • 读取变量
  • 之后步骤
  • 用下面的程序作为例子:

    void a() {
        //stopped here
    }
    
    void b() {
         a();
    }
    
    void c() {
         a();
    }
    
    int main() {
        b();
        c();
    }
    

    如果调试器停在 //stopped here' 这行,那么有两种方法可以达到:main->b->amain->c->a`。如果我们用 LLDB 设置一个断点,继续执行并请求一个回溯,那么我们将得到以下内容:

    * frame #0: 0x00000000004004da a.out`a() + 4 at bt.cpp:3
      frame #1: 0x00000000004004e6 a.out`b() + 9 at bt.cpp:6
      frame #2: 0x00000000004004fe a.out`main + 9 at bt.cpp:14
      frame #3: 0x00007ffff7a2e830 libc.so.6`__libc_start_main + 240 at libc-start.c:291
      frame #4: 0x0000000000400409 a.out`_start + 41
    

    这说明我们目前在函数 a 中,a 从函数 b 中跳转,bmain 中跳转等等。最后两个帧是编译器如何引导 main 函数的。

    现在的问题是我们如何在 x86_64 上实现。最稳健的方法是解析 ELF 文件的 .eh_frame 部分,并解决如何从那里展开堆栈,但这会很痛苦。你可以使用 libunwind 或类似的来做,但这很无聊。相反,我们假设编译器以某种方式设置了堆栈,我们将手动遍历它。为了做到这一点,我们首先需要了解堆栈的布局。

                High
            |   ...   |
            +---------+
         +24|  Arg 1  |
            +---------+
         +16|  Arg 2  |
            +---------+
         + 8| Return  |
            +---------+
    EBP+--> |Saved EBP|
            +---------+
         - 8|  Var 1  |
            +---------+
    ESP+--> |  Var 2  |
            +---------+
            |   ...   |
                Low
    

    如你所见,最后一个堆栈帧的帧指针存储在当前堆栈帧的开始处,创建一个链接的指针列表。堆栈依据这个链表解开。我们可以通过查找 DWARF 信息中的返回地址来找出列表中下一帧的函数。一些编译器将忽略跟踪 EBP 的帧基址,因为这可以表示为 ESP 的偏移量,并可以释放一个额外的寄存器。即使启用了优化,传递 -fno-omit-frame-pointer 到 GCC 或 Clang 会强制它遵循我们依赖的约定。

    我们将在 print_backtrace 函数中完成所有的工作:

    void debugger::print_backtrace() {
    

    首先要决定的是使用什么格式打印出帧信息。我用了一个 lambda 来推出这个方法:

        auto output_frame = [frame_number = 0] (auto&& func) mutable {
    std::cout

    相关文章

    Linux 命令行的聊天工具 CenterIM
    Linux 桌面年仍未到来 但 Linux 移动之年已到来
    12 个在线学习 Linux 技能网站
    Linux Mint : 会是另一个新的 Ubuntu 吗?
    W3Conf 开发者大会将于下周召开
    Ubuntu 10.04 ARM 处理器上网本版本结束服务期

    发布评论