# lazy-debugger **Repository Path**: lyj514328/lazy-debugger ## Basic Information - **Project Name**: lazy-debugger - **Description**: 实现一个类似于lldb的调试器,支持简单的调试命令 - **Primary Language**: C++ - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-08-07 - **Last Updated**: 2024-09-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: Cpp, debugger, GDB, lldb ## README # lazy-debugger ## 介绍 lazy-debugger是一个学习用的命令行调试器, 支持简单的调试命令 lazy-debugger的特性包括:源码级调试(包括单步执行,变量打印等) & 栈回溯 等 lazy-debugger没有基于框架来解析elf/dwarf以及实现调试器 采用llvm作为golden进行对比测试 ## 软件架构 ![lazy-debugger arch](./docs/.resouce/lazy-debugger-arch.png) ## 编译教程 **源码编译** ```bash mkdir build && cd build cmake .. make ``` **运行测试** ```bash mkdir build && cd build cmake .. make ctest ``` **调试器 `lazy-debugger`,位于tools目录下** ## 使用说明 针对你需要调试可执行文件a.out,执行: ```bash lazy-debugger ./a.out ``` 如果你需要给a.out传递命令行参数 1 2 3,执行: ```bash lazy-debugger --- ./a.out 1 2 3 ``` ## 支持的命令 | 命令 | 用途 | 完成 | | ------ | -------------------------------------- | ---- | | c | continue | Y | | bf | breakpoint in function | Y | | bl | breakpoint in line | Y | | exit | exit | Y | | p | print value(仅仅支持p a这种简单形式) | Y | | n | next line(step over) | Y | | s | step into | Y | | finish | finish current function | Y | | bt | stack trace | Y | ## 调试步骤例子 这里使用test目录下的 `11-a.exe`(由 `ll-a.c`生成)这个可执行文件作为例子: **step 1:** 命令行指定被调试的文件 ```bash yijli@ubuntu2204:~/Workspace/lazy-debugger/build$ ./tools/lazy-debugger ./test/11-a.exe [info] pid:136294 suspended by signal 'Trace/breakpoint trap' [info] lunch process: ./test/11-a.exe >> ``` 之后lazy-debugger便启动了,`>>`是提示符,输入命令的地方。 注意:这里和gdb不同的是,不需要run命令,lazy-debugger为了简单,一启动就运行了调试进程,并停在了第一条指令位置。 **step 2:** ```bash >> bf main breakpoint in address: 0x40178a, line: 16, func: main >> c [info] pid:136456 suspended by signal 'Trace/breakpoint trap' [info] hit breakpoint in address: 0x40178a 11 int c = a + b; 12 int d = c; 13 } 14 return c; 15 } --> int main() { 17 int c = func(); 18 printf("TEST5 addr = %p, func() return = %d\n", &TEST11, c); 19 return 0; 20 } ``` 通过 `bf`可以打断点到指令的函数。使用 `c`可以继续执行,直到遇到TRAP。 **step 3:** ```bash >> bl 17 breakpoint in address: 0x401796, line: 17 >> c [info] pid:136456 suspended by signal 'Trace/breakpoint trap' [info] hit breakpoint in address: 0x401796 12 int d = c; 13 } 14 return c; 15 } 16 int main() { --> int c = func(); 18 printf("TEST5 addr = %p, func() return = %d\n", &TEST11, c); 19 return 0; 20 } ``` 通过 `bl`指令可以在某一行打断点,当然现在不需要指定文件名,是因为lazy-debugger还不支持多个编译单元。毕竟他只是一个学习用品~。 **step 4:** ```bash >> s 1 #include 2 #include 3 uint8_t TEST11 = UINT8_C(0XFF); --> int func() { 5 int a = 10; 6 int b = 20; 7 int c = a + b; 8 { 9 int a = 10; ``` 通过 `s`指令,可以进入函数内 **step 5:** ```bash >> n 1 #include 2 #include 3 uint8_t TEST11 = UINT8_C(0XFF); 4 int func() { --> int a = 10; 6 int b = 20; 7 int c = a + b; 8 { 9 int a = 10; 10 int b = 20; >> n 1 #include 2 #include 3 uint8_t TEST11 = UINT8_C(0XFF); 4 int func() { 5 int a = 10; --> int b = 20; 7 int c = a + b; 8 { 9 int a = 10; 10 int b = 20; 11 int c = a + b; ``` 通过 `n`指令可以执行到下一行 **step 6:** ```bash >> n 7 int c = a + b; 8 { 9 int a = 10; 10 int b = 20; 11 int c = a + b; --> int d = c; 13 } 14 return c; 15 } 16 int main() { 17 int c = func(); >> p c type='int(aka int)' value='30' address='0x7fffffffd7b8' ``` 通过 `p`指令,可以打印某一个变量的值。目前似乎还不支持指针类型,也不支持表达式(:,但是局部变量和全局变量都已经支持了。 **step 7:** ```bash >> bt frame #0(0x40177f) : func+0x3a [info] X86 RSP not found in unwind info, fallback to x86 ABI (RSP = CFA + 0) frame #1(0x4017a0) : main+0x16 [info] X86 RSP not found in unwind info, fallback to x86 ABI (RSP = CFA + 0) frame #2(0x401bfa) : __libc_start_call_main+0x6a [info] X86 RSP not found in unwind info, fallback to x86 ABI (RSP = CFA + 0) frame #3(0x403497) : __libc_start_main_impl+0xa27 [info] X86 RSP not found in unwind info, fallback to x86 ABI (RSP = CFA + 0) frame #4(0x401645) : _start+0x25 [warning] resolve register: RIP unwind location failed. ``` 通过 `bt`命令,可以从当前位置开始执行backtrace,当然目前还不支持像GDB/LLDB一样的frame命令。 **step 8:** ```bash >> exit [info] pid:136456 aborted by signal 'Killed' ``` 执行 `exit`,会退出调试器,并kill掉被调试进程。 当然你可以继续执行 `c`命令,让子进程寿终正寝,而不是中道崩殂。 ```bash yijli@ubuntu2204:~/Workspace/lazy-debugger/build$ ./tools/lazy-debugger ./test/11-a.exe [info] pid:136941 suspended by signal 'Trace/breakpoint trap' [info] lunch process: ./test/11-a.exe >> c TEST5 addr = 0x4c50f0, func() return = 30 [info] pid:136941 exited with return value: 0 ``` ## commit规范 ### commit前缀 | prefix | meaning | | ------ | --------------------- | | NFC | no function change | | OPT | 命令行选项部分的修改 | | DOC | 更新文档或者README | | TEST | 测试相关内容 | | BIN | binary reader相关内容 | | SC | SubprocessControler | | ELF | ELF文件解析相关 | | MEM | memory reader相关内容 | | EH | eh frame解析 | | DW | dwarf 解析相关内容 | | DB | debugger | ## 测试方案 各个模块的测试方案如下: | 模块 | 方案 | 描述 | 完成 | | ---- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | | OPT | 自己构造测试用例 | 调用各个API接口定义命令行选项来解析 | Y | | BIN | 自己构造测试用例 | 从二进制文件中读取各种格式的数据 | Y | | SC | 自己构造测试用例 | 调用该模块,控制子进程,包括打断点,获取寄存器等 | Y | | ELF | 和llvm进行对比测试 | 和llvm解析相同的elf文件,得到相同的结果 | Y | | EH | 和llvm进行对比测试 | 和llvm解析相同的elf文件,得到相同的结果;包括CFI/FDE/CFI/DWARF-EAPRESSION等部分 | Y | | DW | 和llvm进行对比测试 | 解析部分和llvm进行对比测试(包含.debug_abbrev/debug_line/debug_info)
DWARF-EXPRESSION的【求解】可以参考dwarf5中的example测试,比较详细。
dwqrfv5 :
D.1.2 DWARF Stack Operation Examples
D.1.3 DWARF Location Description Examples | N
(lazy-debugger的dwarf解析和执行架构与llvm/lldb差距很大) | | DB | 手动验证+单元测试 | debugger的命令暂时先手动验证,自动测试比较麻烦
x86反汇编(call指令size求解)使用单元测试覆盖 | Y
(paritial, x86 disassembler unit test has done) | ## 文档 文档都位于 `./docs`目录下 | 文档 | 说明 | | ------------------------------------------------------- | ------------------------------------------------------------ | | [Linux-LSB-EhFrame.md](docs/Linux-LSB-EhFrame.md) | | | [DWARF-Expression.md](docs/DWARF-Expression.md) | | | [How-To-Backtrace.md](docs/How-To-Backtrace.md) | | | [Ptrace-Debugger-Demo.md](docs/Ptrace-Debugger-Demo.md) | 例子来自于:《Linux二进制分析》第三章,但是修复了检查ptrace返回值的BUG。 | | [DWARF-Debug-Line.md](docs/DWARF-Debug-Line.md) | | | [DWARF-Debug-Abbrev.md](docs/DWARF-Debug-Abbrev.md) | | | [DWARF-Debug-Info.md](docs/DWARF-Debug-Info.md) | | ## 外部参考 | 资料 | 说明 | | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | mindbg | 这是一个非常精简的调试器,位于GitHub:https://github.com/TartanLlama/minidbg,作者同时写了一个博客讲解如何实现一个调试器:https://blog.tartanllama.xyz/writing-a-linux-debugger-setup/,但是博客主要是利用外部库(https://github.com/aclements/libelfin)来实现调试命令,并不包含如何解析elf & eh_frame & dwarf。最为nice的一点是Linux.cn将这一系列文档给翻译了,参见:https://linux.cn/article-8936-1.html。作者还有一本新书在计划中(2025年出版)(https://github.com/TartanLlama/sdb)会讲解整套系统,包含调试信息。(:然而现在才2024年,hhhh。 | | 高效C/C++调试 | 一本非常nice的书籍。GitHub链接:https://github.com/Celthi/effective-debugging-zh。第一章可以作为参考([第一章 调试符号和调试器](https://celthi.github.io/effective-debugging-zh/chapter_1.html)),作为对DWARF了解的综述。书籍链接(https://book.douban.com/subject/36693242/) | | lldb官网 | 链接:https://lldb.llvm.org/,包含一些lldb的用法 | | dwarf官网 | 链接:https://dwarfstd.org/;可以下载dwarf各个version的标准文档 | | Introduction to the DWARF Debugging Format | DWARF组织出的一个小册子,包含对DWARF调试信息的综述:https://dwarfstd.org/doc/Debugging%20using%20DWARF-2012.pdf | | Linux LSB:Exception Frames | 链接:https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html。这是LinuxLSB5.0定义的.eh_frame这个段的格式,当然它和dwarf的.debug_frame非常类似,毕竟是借鉴的。 | | eh-frame CFI 指令 | 这个也是Linux LSB以dwarf的debug-frame CFI指令为基础定义的:https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/dwarfext.html。**注意LSB标准在不断演进中,目前最新的是LSB5.0,并且基于DWARF-V4拓展CFI。** | | ELF format | Linux上的ELF文档,可以参见:https://refspecs.linuxfoundation.org/。注意ELF是一个开放的格式,并没有一个像DWARF这样的组织来维护它,而且ELF格式也几乎不会更新。 | | CSDN博客:Unwind 栈回溯详解 | https://blog.csdn.net/pwl999/article/details/107569603;很详细的一个博客,带有对.eh_frame & .debug_frame的综述,以及如何计算Unwind Table。 | | golang-debugger-book | GITHUB:https://github.com/hitzhangjie/golang-debugger-book/;国内大佬写的一本书,讲解如何实现golang的调试器(golang也使用DWARF格式的调试信息),而且带有详细的例子和代码 | | Linux二进制分析 | 中文版:[Linux二进制分析 (豆瓣) (douban.com)](https://book.douban.com/subject/27592738/)。这书主要讲解Linux 二进制分析的;但是第三章主要讲解ptrace,还是值得一看,因为我们也会用到。不过可能是ptrace不属于POSIX标准的缘故,不同平台上ptrace实现比较混乱,因此书中的例子对ptrace的错误处理不适合Linux,在Linux是有可能跑不起来的,参考:[ptrace(2) - Linux manual page (man7.org)](https://www.man7.org/linux/man-pages/man2/ptrace.2.html) | | IBM 关于DWARF的简介 | https://developer.ibm.com/articles/au-dwarf-debug-format/;这是一个对DWARF的综述 | | 博客园:DWARF 中的 Debug Info 格式 | [DWARF 中的 Debug Info 格式 - twoon - 博客园 (cnblogs.com)](https://www.cnblogs.com/catch/p/3884271.html);主要讲解debug info如何存储 | | x86指令编码机制 | https://4ch12dy.github.io/2017/10/11/x86%E6%8C%87%E4%BB%A4%E7%BC%96%E7%A0%81%E7%AC%94%E8%AE%B0/X86%E6%8C%87%E4%BB%A4%E7%BC%96%E7%A0%81%E7%AC%94%E8%AE%B0/,注意该文档是x86的,因此在某些地方和x86-64有区别,比如opcode2这个域的mode为00,并且r/m为101的时候,x86的寻址模式为[disp32],而x64为[rip + disp32]; | | X86指令编码内幕 --- ModRM 寻址模式 | https://blog.csdn.net/xfcyhuang/article/details/6232303,里面关于mode r/m部分很详细,标题是x86但是x64也包含了 |