我一直想知道调试器是如何工作的?特别是可以“附加”到已经运行的可执行文件的那个。我知道编译器会将代码转换为机器语言,但调试器如何“知道”它附加到什么位置?
答案 0 :(得分:89)
调试器如何工作的细节取决于您正在调试的内容以及操作系统的内容。对于Windows上的本机调试,您可以在MSDN上找到一些详细信息:Win32 Debugging API。
用户通过名称或进程ID告诉调试器要连接的进程。如果是名称,则调试器将查找进程ID,并通过系统调用启动调试会话;在Windows下,这将是DebugActiveProcess。
一旦附加,调试器将进入一个事件循环,就像任何UI一样,但是不是来自窗口系统的事件,操作系统将根据正在调试的进程中发生的事情生成事件 - 例如发生异常。请参阅WaitForDebugEvent。
调试器能够读取和写入目标进程的虚拟内存,甚至可以通过操作系统提供的API调整其寄存器值。请参阅Windows debugging functions列表。
调试器能够使用符号文件中的信息将地址转换为源代码中的变量名称和位置。符号文件信息是一组独立的API,并不是操作系统的核心部分。在Windows上,这是通过Debug Interface Access SDK。
如果您正在调试托管环境(.NET,Java等),则该过程通常看起来类似,但细节不同,因为虚拟机环境提供调试API而不是底层操作系统。
答案 1 :(得分:54)
据我所知:
对于x86上的软件断点,调试器用CC
(int3
)替换指令的第一个字节。这是在Windows上使用WriteProcessMemory
完成的。当CPU到达该指令并执行int3
时,这会导致CPU生成调试异常。操作系统收到此中断,意识到正在调试进程,并通知调试器进程断点被命中。
在命中断点并且进程停止后,调试器会在其断点列表中查找,并将CC
替换为最初的字节。调试器在TF
, the Trap Flag中设置EFLAGS
(通过修改CONTEXT
),然后继续该过程。陷阱标志会导致CPU在下一条指令上自动生成单步异常(INT 1
)。
当正在调试的进程下次停止时,调试器再次用CC
替换断点指令的第一个字节,然后继续该过程。
我不确定这是否完全是由所有调试器实现的,但是我写了一个Win32程序,它设法使用这种机制调试自己。完全没用,但很有教育意义。
答案 2 :(得分:24)
在Linux中,调试流程从ptrace(2)系统调用开始。 This article有一个很棒的教程,介绍如何使用ptrace
来实现一些简单的调试结构。
答案 3 :(得分:10)
如果您使用的是Windows操作系统,那么一个很好的资源就是John Robbins的“调试Microsoft .NET和Microsoft Windows应用程序”:
(甚至旧版本:"Debugging Applications")
本书有一章介绍调试器的工作原理,包括几个简单(但工作)调试器的代码。
由于我不熟悉Unix / Linux调试的细节,这些东西可能根本不适用于其他操作系统。但我猜想,作为一个非常复杂的主题的介绍,概念 - 如果不是细节和API - 应该“移植”到大多数操作系统。
答案 4 :(得分:3)
了解调试的另一个有价值的来源是英特尔CPU手册(英特尔®64和IA-32架构 软件开发人员手册)。在第3A卷第16章中,它介绍了调试的硬件支持,例如特殊异常和硬件调试寄存器。以下是该章:
T(陷阱)标志,TSS - 尝试时生成调试异常(#DB) 切换到在TSS中设置了T标志的任务。
我不确定Window或Linux是否使用此标志,但阅读该章节非常有趣。
希望这有助于某人。
答案 5 :(得分:1)
我的理解是,当你编译一个应用程序或DLL文件时,它编译的任何内容都包含代表函数和变量的符号。
当您进行调试构建时,这些符号比发布版本更详细,因此允许调试器为您提供更多信息。当您将调试器附加到进程时,它会查看当前正在访问哪些函数并从此处解析所有可用的调试符号(因为它知道编译文件的内部结构是什么样的,它可以确定内存中可能存在的内容) ,包含整数,浮点数,字符串等内容。就像第一张海报所说,这些信息以及这些符号的工作方式在很大程度上取决于环境和语言。
答案 6 :(得分:0)
我想在这里要回答两个主要问题:
1。调试器如何知道发生了异常?
当正在调试的进程中发生异常时,操作系统会通知调试器,然后才有机会在目标进程中定义的任何用户异常处理程序对异常进行响应。如果调试器选择不处理此(优先机会)异常通知,则异常分发序列会继续进行,然后如果目标线程愿意,则为目标线程提供处理异常的机会。如果目标进程未处理SEH异常,则将向调试器发送另一个调试事件,称为第二次机会通知,以通知它目标进程中发生未处理的异常。 Source
2。调试器如何知道如何在断点处停止?
简化的answer是:当您在程序中放置一个断点时,调试器将用software interrupt的int3指令替换该点的代码。结果是程序被挂起并调用调试器。