我一直想知道调试器是如何工作的?特别是可以"附加"到已经运行的可执行文件的那个.我理解编译器会将代码转换为机器语言,但是调试器如何"知道"它附加到什么?
调试器如何工作的细节取决于您正在调试的内容以及操作系统的内容.对于Windows上的本机调试,您可以在MSDN:Win32调试API上找到一些详细信息.
用户通过名称或进程ID告诉调试器要附加哪个进程.如果是名称,则调试器将查找进程ID,并通过系统调用启动调试会话; 在Windows下,这将是DebugActiveProcess.
一旦连接,调试器就会像任何UI一样进入事件循环,但是不是来自窗口系统的事件,操作系统将根据正在调试的进程中发生的事情生成事件 - 例如发生异常.请参阅WaitForDebugEvent.
调试器能够读取和写入目标进程的虚拟内存,甚至可以通过操作系统提供的API调整其寄存器值.请参阅Windows 的调试功能列表.
调试器能够使用符号文件中的信息将地址转换为源代码中的变量名称和位置.符号文件信息是一组独立的API,并不是操作系统的核心部分.在Windows上,这是通过调试接口访问SDK.
如果您正在调试托管环境(.NET,Java等),则该过程通常看起来类似,但细节不同,因为虚拟机环境提供调试API而不是底层操作系统.
据我了解:
对于x86上的软件断点,调试器用CC
(int3
)替换指令的第一个字节.这是WriteProcessMemory
在Windows上完成的.当CPU到达该指令并执行时int3
,这会导致CPU生成调试异常.操作系统收到此中断,意识到正在调试进程,并通知调试器进程断点被命中.
在命中断点并且进程停止后,调试器会在其断点列表中查找,并将其替换为CC
最初的字节.调试器套TF
,所述陷阱标志中EFLAGS
(通过修改CONTEXT
),并且继续处理.陷阱标志使CPU INT 1
在下一条指令上自动生成单步异常().
当正在调试的进程下次停止时,调试器再次用断言替换断点指令的第一个字节CC
,然后继续该过程.
我不确定这是否完全是由所有调试器实现的,但我编写了一个Win32程序,该程序设法使用此机制调试自身.完全无用,但有教育意义.
在Linux中,调试进程从ptrace(2)系统调用开始. 本文有一个很好的教程,介绍如何使用它ptrace
来实现一些简单的调试结构.
如果您使用的是Windows操作系统,那么很好的资源就是John Robbins的"调试Microsoft .NET和Microsoft Windows应用程序":
http://www.amazon.com/dp/0735615365
(甚至是旧版本:"调试应用程序")
本书有一章介绍调试器的工作原理,包括几个简单(但工作)调试器的代码.
由于我不熟悉Unix/Linux调试的细节,这些东西可能根本不适用于其他操作系统.但我猜想,作为一个非常复杂的主题的介绍,概念 - 如果不是细节和API - 应该"移植"到大多数操作系统.