返回

文章详情

从零开始编写调试器

Hacker News2026年7月24日 14:06

我曾两次离开微软调试器平台团队,每次都开始编写自己的调试器。我真的很喜欢调试器。这一次,我写新调试器有两个原因。第一个是因为我想更好地学习Rust,而编写我已经相当了解的东西似乎是个不错的学习方法。第二个原因是为了让人们更容易理解调试器的工作原理。使用Rust也有帮助,因为有很多库可以处理符号和反汇编等事情,这将使我们能够专注于编写调试器的核心思想。我是Rust的新手,如果你想更好地学习Rust,我建议从https://www.rust-lang.org/learn开始。我不会尝试过多解释Rust代码,但主要的概念和API在不同语言中是通用的。即使对Rust知之甚少,你也应该能跟上。那些读到这篇文章并比我了解Rust的人,请随时在GitHub仓库中创建问题。更好的是,提交拉取请求!这第一部分的代码在一个名为“part1”的分支上,你可以在这里查看完整代码。如果你想跟随,我建议在本地克隆整个仓库,并在好的IDE(如VS Code搭配rust-analyzer插件)中查看。什么是调试器?首先,什么是调试器?有很多工具被称为“调试工具”,但是当大多数人听到“调试器”时,他们通常想到可以分析正在运行的系统或静态快照(例如core文件、崩溃转储或VM快照)的工具。大多数调试器,如GDB、LLDB、Visual Studio和WinDbg,都可以执行这两项功能。我们将开始创建一个支持Windows上实时用户模式调试的调试器。核心概念适用于几乎任何操作系统,主要的区别在于调试进程的操作系统API和某些概念使用的术语。在Windows中,只有一些API提供了实现调试器所需的功能。大多数这些功能在MSDN的调试函数页面上有描述。请注意,这些是Visual Studio或WinDbg等调试器使用的API。WinDbg的核心引擎称为DbgEng,具有一套可以通过IDebugClient接口和相关API访问的API。这些高级API非常强大,具有内置堆栈展开、符号分析、流程控制、反汇编、脚本等许多其他功能。如果你想自动化某个调试任务,这更适合使用的API。但这不是我们在本系列文章中要做的事情。我们将使用操作系统提供的最基本API从头开始实现一个调试器。我们需要与运行中的进程交互的其他多个函数,但这些API并不是特定于调试的。我们将在遇到它们时进行介绍。基本结构在实时调试会话的中心是一个事件循环。调试器通过“附加”到目标进程来注册调试事件。对于每个发生的事件,操作系统将冻结目标进程,并向调试器通知该调试事件的信息。调试器随后有机会检查或操控目标进程的状态。然后,调试器从调试事件继续。这里嵌入了大量复杂性,但大多数复杂性在于检查和操控目标进程的状态。我们将开始创建一个调试器,附加到进程,监控调试事件,并继续从这些事件中,让进程正常执行。那部分相对简单,将为可以在其基础上建立的其他功能提供基础。在Windows上,附加到进程主要有两种方法。如果已经有一个我们想要附加的进程在运行,我们可以使用DebugActiveProcess API,它只需一个参数,即我们想调试的进程ID。如果调试结束,我们调用DebugActiveProcessStop,也只需一个进程ID参数。附加到进程的第二种方法是通过在附加状态下创建进程。为此,我们使用通常用于调试进程的CreateProcessW函数,并在dwCreationFlags参数中包含DEBUG_ONLY_THIS_PROCESS或DEBUG_PROCESS标志(作为进程创建标志的一部分进行了文档化)。这两个标志的区别在于你是想仅调试启动的特定进程,还是想调试该进程及其所有子进程。我们将使用CreateProcessW启动调试进程:105 let mut si: STARTUPINFOEXW = unsafe { std::mem::zeroed() }; 106 si.StartupInfo.cb = std::mem::size_of::<STARTU

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

请我喝杯咖啡