有时您需要知道的最重要的信息是您当前的程序状态到达那里。有一个backtrace命令可以为您提供程序的当前函数调用链。这篇文章将向您展示如何在x86_64上实现堆栈展开以生成这样的回溯。系列索引这些链接将在其他帖子发布时生效。准备环境断点寄存器和内存ELF和DWARF源码和信号源码级逐步执行源码级断点Stackunwind读取变量Aftersteps以下程序为例:voida(){//stoppedhere}voidb(){a();}voidc(){a();}intmain(){b();c();}如果调试器停在//stoppedhere'行,有两种方法可以到达它:main->b->a或main->c->a`。如果我们使用LLDB设置断点,恢复执行并请求回溯,我们会得到以下内容:*frame#0:0x00000000004004daa.out`a()+4atbt.cpp:3frame#1:0x000000000004004e6a.out`b()+9atbt.cpp:6frame#2:0x00000000004004fea.out`main+9atbt.cpp:14frame#3:0x00007ffff7a2e830libc.so.6`__libc_start_main+240atlibc-start.c:291frame#4:0x000000000040out_starta`说明我们目前在函数a,a从函数b跳转,b从main跳转,依此类推。***这两个框架是编译器引导main函数的方式。现在的问题是我们如何在x86_64上做到这一点。最稳健的方法是解析ELF文件的.eh_frame部分并弄清楚如何从那里展开堆栈,但这会很痛苦。你可以用libunwind或类似的东西来做,但这很无聊。相反,我们假设编译器已经以某种方式设置了堆栈,我们将手动遍历它。为此,我们首先需要了解栈的布局。高|...|+--------+|Arg1|+--------+|Arg2|+--------+|返回|+-------+|SavedEBP|+--------+|Var1|+--------+|Var2|+--------+|...|Low如您所见,堆栈帧的帧指针存储在当前堆栈帧的开头,创建了一个指针链表。堆栈根据此链表展开。我们可以通过查找DWARF消息中的返回地址来找出列表中的下一帧是哪个函数。一些编译器会忽略跟踪EBP的帧基地址,因为这可以表示为ESP的偏移量,并且可以释放额外的寄存器。即使启用了优化,将-fno-omit-frame-pointer传递给GCC或Clang也会强制它遵循我们所依赖的约定。我们将在print_backtrace函数中完成所有工作:voiddebugger::print_backtrace(){首先要决定的是使用什么格式来打印帧信息。我使用lambda启动此方法:autooutput_frame=[frame_number=0](auto&&func)mutable{std::cout<<"frame#"<
