在之前的博文中,我们了解了DWARF信息以及它如何让我们将机器码与上层源码链接起来。这一次,我们通过向调试器添加源代码级逐步调试,将这些知识付诸实践。系列文章索引这些链接会随着后续文章的发布而逐渐生效。准备环境断点寄存器和内存精灵和矮人源代码和信号源代码级步进源级断点调用堆栈展开读取变量后续步骤揭秘指令级步进我们正在超越自己。首先让我们通过用户界面揭开指令级单步执行的神秘面纱。我决定把它拆分成两个函数:single_step_instruction可以供代码的其他部分使用,single_step_instruction_with_breakpoint_check确保某个断点是否启用。voiddebugger::single_step_instruction(){ptrace(PTRACE_SINGLESTEP,m_pid,nullptr,nullptr);wait_for_signal();}voiddebugger::single_step_instruction_with_breakpoint_check(){//首先,检查我们是否需要禁用或启用断点if(m_breakpoints.count(get_pc())){step_over_breakpoint();}else{single_step_instruction();}}和以前一样,另一个命令被集成到我们的handle_command函数中:elseif(is_prefix(command,"stepi")){single_step_instruction_with_breakpoint_check();autoline_entry=get_line_entry_from_pc(get_pc());print_source(line_entry->file->path,line_entry->line);}有了这些新函数,我们就可以开始实现我们的源码级分步执行函数了。实现步进我们打算编写这些函数的非常简单的版本,但真正的调试器有一个线程计划的概念,它封装了所有步进信息。例如,调试器可能有一些复杂的逻辑来确定断点在哪里,然后有一些回调来确定单步执行是否完成。涉及很多基础设施,我们只是采取了一种幼稚的方法。我们可能会不小心越过断点,但如果需要,您可以花一些时间来了解所有细节。对于step_out,我们只是在函数的返回地址处设置一个断点,然后继续执行。我现在不想深入探讨调用堆栈展开的细节——这些将在后面的部分中介绍——但足以说明返回地址存储在堆栈帧开头的最后8个字节中。所以我们会读取栈指针,然后读取内存对应地址处的值:错误的;如果(!m_breakpoints.count(return_address)){set_breakpoint_at_address(return_address);should_remove_breakpoint=true;}continue_execution();if(should_remove_breakpoint){remove_breakpoint(return_address);}}remove_breakpoint(id::debuggerreakstd::intptr_taddr){if(m_breakpoints.at(addr).is_enabled()){m_breakpoints.at(addr).disable();}m_breakpoints.erase(addr);}接下来是跳入step_in。一个简单的算法是继续逐步执行指令,直到到达新的一行。voiddebugger::step_in(){autoline=get_line_entry_from_pc(get_pc())->line;while(get_line_entry_from_pc(get_pc())->line==line){single_step_instruction_with_breakpoint_check();}autoline_entry=get_line_entry_from_pc(get_pcsource_print());(line_entry->file->path,line_entry->line);跳过step_over对我们来说是三者中最难的。理论上解决办法是在下一行源码处打断点,但是下一行源码是什么?它可能不是当前行之后的行,因为我们可能处于循环中,或者某种条件结构中。真正的调试器通常会检查当前正在执行的指令并计算所有可能的分支目标,然后在所有分支目标上设置断点。对于一个小项目,我不打算实现或集成一个x86指令模拟器,所以让我们找到一个更简单的解决方案。有几个可怕的选择,一个是一直单步执行直到当前函数的新行,或者在当前函数的每一行设置一个断点。如果我们要跳过一个函数调用,前者会相当低效,因为我们需要逐步执行该调用图中的每条指令,所以我会选择第二种方法。voiddebugger::step_over(){autofunc=get_function_from_pc(get_pc());autofunc_entry=at_low_pc(func);autofunc_end=at_high_pc(func);autoline=get_line_entry_from_pc(func_entry);autostart_line=get_line_entry_from_pc(get_pc());std::vector
