我不确定我是否完全理解该问题,但是我可以告诉您GDB如何实现其step命令。
一旦控件进入特定的编译单元,GDB就会读取该CU的调试信息。特别是,它读取.debug_line节的CU部分,并构建一个表,该表将指令地址映射到源代码位置。
当step开始,GDB查找当前PC的源位置。然后,它按照机器指令逐步执行,每次都查找新PC的源位置,直到源位置发生变化。当源位置更改时,step即完成。
在每个步骤之后,它还会计算帧ID(堆栈帧的基地址和函数的起始地址),并检查其是否已更改。如果有的话,这意味着我们已经进入或返回了递归调用,并且step已经完成。
要了解为什么需要检查帧ID和源位置,请考虑逐步调用以下函数:
int fact(n) { if (n > 0) { return n * fact(n-1); } else return 1; }
Run Code Online (Sandbox Code Playgroud)
由于此函数完全在同一源代码行上定义,因此逐步执行指令直到源代码行更改,将使您逐步完成所有递归调用,而不会停止。但是,当我们输入一个新的事实调用时,堆栈帧的基地址将已更改,表明我们应该停止。这给我们以下行为:
fact (n=10) at recurse.c:4
(gdb) step
fact (n=9) at recurse.c:4
(gdb) step
fact (n=8) at recurse.c:4
Run Code Online (Sandbox Code Playgroud)
GDB的next命令将这种一般行为与适当的逻辑相结合,以识别函数调用并使它们返回完成状态。和以前一样,必须使用帧ID来确定何时呼叫真正返回到原始帧。还有其他并发症。
值得思考一下如何处理内联的函数实例(DWARF确实描述了)。但这对于这个问题来说有点太多了。
并不是为了阻止实验,而是如果我正在开始一个调试器项目,我想看看苹果公司正在进行的调试器lldb,它是开源的。