当前位置: 首页 > 科技观察

开发一个Linux调试器(5):源码和信号

时间:2023-03-16 20:59:59 科技观察

上一节我们学习了DWARF以及如何用它来读取变量和机器码,结合我们的高级语言源码站起来执行.在这一部分中,我们将开始实践并实现我们的调试器稍后将使用的一些DWARF原语。我们还将利用这个机会,让我们的调试器在遇到断点时打印出当前的源代码上下文。系列文章索引这些链接会随着后续文章的发布而逐渐生效。准备环境断点寄存器和内存Elves和dwarves源和信号源级步进源级断点调用堆栈展开读取变量下一步设置我们的DWARF解析器正如我在本系列开头提到的,我们将使用libelfin来处理我们的DWARF消息.希望您已经在***部分进行了设置,如果没有,请立即进行并确保您使用的是我存储库的fbreg分支。一旦构建了libelfin,就可以将它添加到我们的调试器中了。第一步是解析我们的ELF可执行文件并从中提取DWARF信息。使用libelfin很容易实现,只需要对调试器做如下改动:打开(m_prog_name.c_str(),O_RDONLY);m_elf=elf::elf{elf::create_mmap_loader(fd)};m_dwarf=dwarf::dwarf{dwarf::elf::create_loader(m_elf)};}//...private://...dwarf::dwarfm_dwarf;elf::elfm_elf;};我们使用open而不是std::ifstream,因为elf加载器需要传递一个UNIX文件描述符给mmap,这样就可以将文件映射到内存,而不是一次读取一部分。调试信息原语接下来我们可以实现从程序计数器的值中提取行条目和函数DWARF信息条目(函数DIE)的函数。我们从get_function_from_pc开始:dwarf::diedebugger::get_function_from_pc(uint64_tpc){for(auto&cu:m_dwarf.compilation_units()){if(die_pc_range(cu.root()).contains(pc)){for(constauto&die:cu.root()){if(die.tag==dwarf::DW_TAG::subprogram){if(die_pc_range(die).contains(pc)){returndie;}}}}}throwstd::out_of_range{"Cannotfindfunction"};}这里我采用了天真的方法,遍历编译单元直到找到一个包含程序计数器的单元,然后遍历它的子单元直到找到相关函数(DW_TAG_subprogram)。正如我在上一篇文章中提到的,如果需要,您可以处理成员函数或内联之类的事情。接下来是get_line_entry_from_pc:dwarf::line_table::iteratordebugger::get_line_entry_from_pc(uint64_tpc){for(auto&cu:m_dwarf.compilation_units()){if(die_pc_range(cu.root()).contains(pc)){auto<=cu。get_line_table();autoit=lt.find_address(pc);if(it==lt.end()){throwstd::out_of_range{"Cannotfindlineentry"};}else{returnit;}}}throwstd::out_of_range{"Cannotfindlineentry"};}同样,我们可以简单地找到正确的编译单元,然后查询相关条目的行表。打印源代码当我们遇到断点或单步执行我们的代码时,我们想知道我们在源代码中的什么位置。voiddebugger::print_source(conststd::string&file_name,unsignedline,unsignedn_lines_context){std::ifstreamfile{file_name};//在想要的行附近获取窗口autostart_line=line<=n_lines_context?1:line-n_lines_context;autoend_line=line+n_lines_context+(line":"");//输出行直到end_linewhile(current_line<=end_line&&file.get(c)){std::cout<":"");}}//输出换行符以确保流被正确清空std::cout<file->path,line_entry->line);return;}//如果信号是step-by发送的-stepexecution是的,这将被设置你可以处理。有关更多信息,请参见mansigaction。由于我们在收到SIGTRAP信号时固定了程序计数器的值,因此我们可以从step_over_breakpoint中删除这段代码,现在它看起来像:voiddebugger::step_over_breakpoint(){if(m_breakpoints.count(get_pc())){auto&bp=m_breakpoints[get_pc()];if(bp.is_enabled()){bp.disable();ptrace(PTRACE_SINGLESTEP,m_pid,nullptr,nullptr);wait_for_signal();bp.enable();}}}现在测试你应该能够在某个地址设置断点,运行程序并查看打印的源代码,其中正在执行的行由光标标记。稍后我们将添加设置源代码级断点的功能。同时,您可以在此处获取该博文的代码。