我一直想知道调试器如何工作?特别是可以“附加”到已运行的可执行文件的程序。我知道编译器会将代码翻译成机器语言,但是调试器如何“知道”它所附加的内容?
我一直想知道调试器如何工作?特别是可以“附加”到已运行的可执行文件的程序。我知道编译器会将代码翻译成机器语言,但是调试器如何“知道”它所附加的内容?
Answers:
调试器如何工作的详细信息取决于您要调试的内容以及操作系统是什么。对于Windows上的本机调试,您可以在MSDN上找到一些详细信息:Win32 Debugging API。
用户通过名称或进程ID告诉调试器要附加到哪个进程。如果是名称,则调试器将查找进程ID,并通过系统调用启动调试会话。在Windows下,这将是DebugActiveProcess。
附加后,调试器将进入事件循环,就像任何UI一样,但是操作系统将根据正在调试的过程中发生的事件(例如发生的异常)来生成事件,而不是窗口系统发出的事件。请参见WaitForDebugEvent。
调试器能够读取和写入目标进程的虚拟内存,甚至可以通过操作系统提供的API调整其寄存器值。请参阅Windows 调试功能列表。
调试器能够使用符号文件中的信息将地址从源代码转换为变量名称和位置。符号文件信息是一组单独的API,因此并不是操作系统的核心部分。在Windows上,这是通过Debug Interface Access SDK进行的。
如果要调试托管环境(.NET,Java等),则该过程通常看起来很相似,但是细节不同,因为虚拟机环境提供了调试API而不是底层操作系统。
据我了解:
对于x86上的软件断点,调试器将指令的第一个字节替换为CC
(int3
)。这是WriteProcessMemory
在Windows上完成的。当CPU到达该指令并执行时int3
,这将导致CPU生成调试异常。OS接收到此中断,意识到正在调试进程,并通知调试器进程已命中断点。
击中断点并停止过程后,调试器将在其断点列表中查找,并将替换为CC
原来存在的字节。调试器套TF
,所述陷阱标志中EFLAGS
(通过修改CONTEXT
),并且继续处理。陷阱标志使CPU INT 1
在下一条指令上自动生成一个单步异常()。
当正在调试的进程下次停止时,调试器再次将断点指令的第一个字节替换为CC
,然后进程继续。
我不确定这是否是所有调试器完全实现的方法,但是我编写了一个Win32程序,该程序使用此机制进行自我调试。完全没用,但是很有教育意义。
(2)
告诉我们更多(或小于)“ptrace的是一个系统调用”的东西吗?
(2)
是手动节号。有关手册部分的说明,请参见en.wikipedia.org/wiki/Man_page#Manual_sections。
ptrace
是一个系统调用。
(2)
告诉我们我们可以键入man 2 ptrace
并获取正确的联机帮助页-在这里并不重要,因为没有其他ptrace
歧义,但可以man printf
与man 3 printf
Linux 进行比较。
如果您使用的是Windows操作系统,John Robbins撰写的“调试Microsoft .NET和Microsoft Windows的应用程序”的最佳资源是:
(甚至旧版本:“调试应用程序”)
本书有一章介绍了调试器的工作方式,其中包括一些简单(但有效)的调试器的代码。
由于我不熟悉Unix / Linux调试的详细信息,因此这些内容可能根本不适用于其他OS。但是我想作为一个非常复杂的主题的介绍,这些概念(如果不是细节和API)应该“移植”到大多数操作系统。
我的理解是,当您编译应用程序或DLL文件时,其编译后的内容都会包含代表函数和变量的符号。
使用调试版本时,这些符号比发布版本时要详细得多,从而使调试器可以为您提供更多信息。将调试器附加到进程时,它会查看当前正在访问哪些函数,并从此处解析所有可用的调试符号(因为它知道编译文件的内部结构是什么样,因此可以确定内存中可能有什么内容) ,以及int,float,string等的内容)。就像第一个海报所说的那样,这些信息以及这些符号的工作方式很大程度上取决于环境和语言。