研究一下调试器
1. 三大 Debugger
1.1 Linux 下的 GDB
1.1.1 原理
GDB(GNU Debugger)是在 Unix-like OS 上使用的开源 debugger,支持多种编程语言,如 C、C++ 和 Fortran。其原理是通过 Unix-like OS 上的 ptrace 系统调用来控制另一个进程的执行、获取和改变其内存中的内容以及其寄存器的状态。
伪代码用于说明 GDB 的工作原理:
int main(int argc, char **argv) {
Debugger gdb = initialize_debugger();
Program program = load_program(argv[1]);
set_breakpoint(program, line_number);
while (execute_next_instruction(program)) {
if (current_instruction(program) == breakpoint) {
pause_execution(program);
print_debug_info(program);
switch (wait_for_user_input()) {
case CONTINUE: continue_execution(program); break;
case STEP: step_into(program); break;
case QUIT: exit(0); break;
default: handle_other_commands(); break;
}
}
}
return 0;
}ptrace 是 POSIX 兼容的一个系统调用,涉及两个进程:一个是控制进程(GDB),另一个是被控制进程(被调试的程序),可以使得一个进程去观察和控制另一个进程的执行,并能改变其寄存器和内存值。ptrace 直接提供了很多请求代码,像是以下操作可以直接返回结果:
-
启动、停止子进程的执行
-
读写子进程的内存
-
读写子进程的寄存器
-
单步执行
-
信号处理
给出一个 ptrace 使用示例的伪代码:
pid_t child = fork();
if (child == 0) {
// 子进程: 被调试的进程
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
execvp(program, program_args); // 启动目标程序
} else {
// 父进程: GDB进程
int status;
wait(&status); // 等待子进程停止
ptrace(PTRACE_SETOPTIONS, child, NULL, PTRACE_O_EXITKILL);
while (WIFSTOPPED(status)) {
struct user_regs_struct regs;
ptrace(PTRACE_GETREGS, child, NULL, ®s); // 读取寄存器
// 分析或修改寄存器
ptrace(PTRACE_SETREGS, child, NULL, ®s); // 设置寄存器
ptrace(PTRACE_SINGLESTEP, child, NULL, NULL); // 单步执行
wait(&status); // 等待子进程下次停止
}
}不同的操作系统也都提供了类似 ptrace 的机制来允许进程之间的调试或者控制:
-
Windows 提供了调试 API,其中比如包括函数
DebugActiveProcess来附加到一个进程,以及WaitForDebugEvent和ContinueDebugEvent来处理调试事件。
1.1.2 使用方式
GDB 一般在命令行里使用,可以启动 GDB 附加到一个正在运行的进程,或者加载一个程序的 core dump 文件。
1.2 最强大的(之一)WinDbg
1.2.1 原理
WinDbg 是微软提供的一个 Windows 平台的 debugger,用于调试 Windows 内核、驱动程序以及其他程序。WinDbg 使用 Windows 的调试引擎(dbgeng.dll)作为核心,可通过各种协议(如 USB、1394、网络等)与目标系统进行通信。WinDbg 能够处理复杂的调试任务,包括内存泄漏检测、性能问题分析、代码执行跟踪等。
WinDbg 有着其他调试器很少具备的系统级调试能力,查阅文档发现,是通过与 Windows 操作系统内核的深度集成实现的。
-
内核模式和用户模式:可在内核模式下运行,可以直接与操作系统内核交互,提供对驱动程序和硬件级别事件的调试能力。
-
符号服务器:用微软的符号服务器来获取系统和应用程序的符号信息,而在 Linux 下一般需要编译器编译时指定
-g选项来产生本地的符号信息。微软的做法更方便,但也受其限制。 -
内核调试器协议:用专门的协议与目标系统通信,支持调试器在不同的机器上运行,从而调试另一台机器上的系统。
1.2.2 使用方式
WinDbg 提供了 GUI 和命令行,语法与 GDB 不同,但功能相似,还提供了脚本功能,以及与 Windows 深度集成的特性,如分析 kernel mode 和 dump 文件。
1.3 跨平台、没用过的 LLDB
1.3.1 原理
LLDB 是一个模块化的 debugger,支持 C、C++ 以及其他语言,并且跨平台。LLDB 使用 LLVM 的一些组件作为其后端,比如 JIT 编译的代码调试等。LLDB 的原理同样基于控制和检查进程的执行,但它基于 LLVM。
- 用 LLVM 的代码生成工具来优化调试信息的生成和使用。
- 提供了脚本接口,尤其是 Python,可编写脚本来扩展调试器。
1.3.2 使用方式
LLDB 与 GDB 相似,但有自己的一套扩展。LLDB 还可以集成到 IDE 中,如 Xcode 或 Visual Studio Code,提供 GUI 调试的功能。
2. Summary
-
都是 debugger,基本原理都是通过控制程序的执行,来提供查看和修改内存、寄存器等能力。
-
GDB 和 LLDB 是传统的调试模式,而 WinDbg 提供了更加深入的系统级调试。
-
LLDB 使用了 LLVM 的现代架构,在某些方面比 GDB 有更好一些的性能和现代的特性。