Debugging 调试器是如何工作的?

Debugging 调试器是如何工作的?,debugging,internals,Debugging,Internals,我一直在想调试器是如何工作的?特别是可以“附加”到已经运行的可执行文件的。我知道编译器会将代码翻译成机器语言,但调试器如何“知道”它所附加的内容呢?调试器如何工作的细节将取决于您正在调试的内容以及操作系统是什么。对于Windows上的本机调试,您可以在MSDN上找到一些详细信息: 用户通过名称或进程ID告诉调试器要附加到哪个进程。如果是名称,调试器将查找进程ID,并通过系统调用启动调试会话;在Windows下,这将是 一旦连接,调试器将进入一个事件循环,就像任何UI一样,但操作系统将根据正在调试

我一直在想调试器是如何工作的?特别是可以“附加”到已经运行的可执行文件的。我知道编译器会将代码翻译成机器语言,但调试器如何“知道”它所附加的内容呢?

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

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

一旦连接,调试器将进入一个事件循环,就像任何UI一样,但操作系统将根据正在调试的进程中发生的情况生成事件,而不是来自窗口系统的事件,例如发生的异常。看

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

调试器能够使用符号文件中的信息将源代码中的地址转换为变量名和位置。符号文件信息是一组单独的API,因此不是操作系统的核心部分。在Windows上,这是通过


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

我的理解是,在编译应用程序或DLL文件时,它编译成的任何东西都包含表示函数和变量的符号


当您有一个调试版本时,这些符号比发布版本时要详细得多,因此允许调试器向您提供更多信息。当您将调试器附加到进程时,它会查看当前正在访问哪些函数,并从此处解析所有可用的调试符号(因为它知道编译文件的内部结构,所以可以获取内存中可能存在的内容,包括int、float、string等)。正如第一张海报所说,这些信息以及这些符号的工作方式在很大程度上取决于环境和语言。

如果您使用的是Windows操作系统,John Robbins的“调试Microsoft.NET和Microsoft Windows应用程序”将是一个很好的资源:

(甚至是旧版本:)

这本书有一章介绍了调试器的工作原理,其中包括两个简单(但可以工作)调试器的代码


因为我不熟悉Unix/Linux调试的细节,所以这些东西可能根本不适用于其他操作系统。但我想,作为对一个非常复杂主题的介绍,这些概念(如果不是细节和API的话)应该“移植”到大多数操作系统上。

在Linux中,调试进程从系统调用开始。关于如何使用
ptrace
实现一些简单的调试结构,有一个很好的教程。

了解调试的另一个有价值的来源是《英特尔CPU手册》(英特尔®64和IA-32体系结构) 软件开发人员手册)。在第16章第3A卷中,介绍了调试的硬件支持,如特殊异常和硬件调试寄存器。该章内容如下:

T(trap)标志,TSS-在尝试时生成调试异常(#DB) 切换到TSS中设置了T标志的任务

我不确定Windows或Linux是否使用此标志,但阅读这一章非常有趣

希望这对别人有帮助

据我所知:

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

命中断点并停止进程后,调试器会查看其断点列表,并用原来的字节替换
CC
。调试器设置为(通过修改),并继续该过程。陷阱标志使CPU在下一条指令上自动生成单步异常()

当正在调试的进程下次停止时,调试器再次将断点指令的第一个字节替换为
CC
,进程继续


我不确定所有调试器是否都是这样实现的,但我已经编写了一个Win32程序,它可以使用这种机制进行自我调试。完全没用,但很有教育意义。

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

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

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


2。调试器如何知道如何在断点上停止?

简化的方法是:当您在程序中放入断点时,调试器将在该断点处用int3指令替换您的代码,int3指令是一条中断指令。作为对该计划的影响