调试器如何工作?

ya2*_*a23 160 debugging internals

我一直想知道调试器是如何工作的?特别是可以"附加"到已经运行的可执行文件的那个.我理解编译器会将代码转换为机器语言,但是调试器如何"知道"它附加到什么?

Rob*_*ker 92

调试器如何工作的细节取决于您正在调试的内容以及操作系统的内容.对于Windows上的本机调试,您可以在MSDN:Win32调试API上找到一些详细信息.

用户通过名称或进程ID告诉调试器要附加哪个进程.如果是名称,则调试器将查找进程ID,并通过系统调用启动调试会话; 在Windows下,这将是DebugActiveProcess.

一旦连接,调试器就会像任何UI一样进入事件循环,但是不是来自窗口系统的事件,操作系统将根据正在调试的进程中发生的事情生成事件 - 例如发生异常.请参阅WaitForDebugEvent.

调试器能够读取和写入目标进程的虚拟内存,甚至可以通过操作系统提供的API调整其寄存器值.请参阅Windows 的调试功能列表.

调试器能够使用符号文件中的信息将地址转换为源代码中的变量名称和位置.符号文件信息是一组独立的API,并不是操作系统的核心部分.在Windows上,这是通过调试接口访问SDK.

如果您正在调试托管环境(.NET,Java等),则该过程通常看起来类似,但细节不同,因为虚拟机环境提供调试API而不是底层操作系统.

  • 这个问题可能听起来很愚蠢,但如果达到程序中的特定地址,操作系统如何跟踪.例如,在地址0x7710cafe上设置了breackpoint.随着指令指针的改变,操作系统(或CPU)必须将指令指针与所有断点地址进行比较,还是我错了?这是如何运作的 ..? (5认同)
  • @StefanFalk我写了[答案](http://stackoverflow.com/a/21746853/119527),它解决了一些较低级别的细节(在x86上). (3认同)
  • 这个答案揭示了一些事情。但我认为 op 更感兴趣的是一些底层细节而不是一些 API 抽象。 (2认同)

Jon*_*art 59

据我了解:

对于x86上的软件断点,调试器用CC(int3)替换指令的第一个字节.这是WriteProcessMemory在Windows上完成的.当CPU到达该指令并执行时int3,这会导致CPU生成调试异常.操作系统收到此中断,意识到正在调试进程,并通知调试器进程断点被命中.

在命中断点并且进程停止后,调试器会在其断点列表中查找,并将其替换为CC最初的字节.调试器套TF,所述陷阱标志EFLAGS(通过修改CONTEXT),并且继续处理.陷阱标志使CPU INT 1在下一条指令上自动生成单步异常().

当正在调试的进程下次停止时,调试器再次用断言替换断点指令的第一个字节CC,然后继续该过程.

我不确定这是否完全是由所有调试器实现的,但我编写了一个Win32程序,该程序设法使用此机制调试自身.完全无用,但有教育意义.


Ada*_*eld 24

在Linux中,调试进程从ptrace(2)系统调用开始. 本文有一个很好的教程,介绍如何使用它ptrace来实现一些简单的调试结构.

  • @eSKay,不是不是真的.`(2)`是手动部分编号.有关手册部分的说明,请参见http://en.wikipedia.org/wiki/Man_page#Manual_sections. (5认同)
  • @AdamRosenfield除了第2节特别是"系统调用"这一事实.所以间接地,是的,它告诉我们`ptrace`是一个系统调用. (2认同)

Mic*_*urr 9

如果您使用的是Windows操作系统,那么很好的资源就是John Robbins的"调试Microsoft .NET和Microsoft Windows应用程序":

(甚至是旧版本:"调试应用程序")

本书有一章介绍调试器的工作原理,包括几个简单(但工作)调试器的代码.

由于我不熟悉Unix/Linux调试的细节,这些东西可能根本不适用于其他操作系统.但我猜想,作为一个非常复杂的主题的介绍,概念 - 如果不是细节和API - 应该"移植"到大多数操作系统.


WeG*_*ars 9

我认为这里有两个主要问题需要回答:

\n

1. 调试器如何知道发生了异常?

\n

当正在调试的进程中发生异常时,调试器会在目标进程中定义的任何用户异常处理程序有机会响应异常之前收到操作系统的通知。如果调试器选择不处理此(第一次机会)异常通知,则异常分派序列将进一步进行,然后目标线程将有机会处理该异常(如果它想要这样做)。如果目标进程未处理 SEH 异常,则会向调试器发送另一个调试事件(称为第二次机会通知),以通知它目标进程中发生了未处理的异常。来源

\n

在此输入图像描述

\n
\n

2. 调试器如何知道如何在断点处停止?

\n

简单的答案是:当您在程序中放置断点时,调试器会用 int3 指令(软件中断)替换该点的代码。结果,程序被挂起并调用调试器。

\n


Jia*_*ang 7

了解调试的另一个有价值的来源是 Intel CPU 手册(Intel\xc2\xae 64 和 IA-32 架构\nSoftware Developer\xe2\x80\x99s 手册)。在第3A卷第16章中,介绍了调试的硬件支持,例如特殊异常和硬件调试寄存器。以下摘自该章:

\n\n

T(陷阱)标志,TSS \xe2\x80\x94 当尝试切换到 TSS 中设置了 T 标志的任务时,\n生成调试异常 (#DB)。

\n\n

我不确定 Window 或 Linux 是否使用这个标志,但是阅读该章非常有趣。

\n\n

希望这对某人有帮助。

\n