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


当前回答

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

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

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

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

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

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

其他回答

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

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

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

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

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

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

在Linux中,调试进程从ptrace(2)系统调用开始。本文提供了一个很好的教程,介绍如何使用ptrace实现一些简单的调试构造。

我的理解是,当你编译一个应用程序或DLL文件时,无论它编译到什么,都包含表示函数和变量的符号。

When you have a debug build, these symbols are far more detailed than when it's a release build, thus allowing the debugger to give you more information. When you attach the debugger to a process, it looks at which functions are currently being accessed and resolves all the available debugging symbols from here (since it knows what the internals of the compiled file looks like, it can acertain what might be in the memory, with contents of ints, floats, strings, etc.). Like the first poster said, this information and how these symbols work greatly depends on the environment and the language.

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

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

When an exception occurs in a process that’s being debugged, the debugger gets notified by the OS before any user exception handlers defined in the target process are given a chance to respond to the exception. If the debugger chooses not to handle this (first-chance) exception notification, the exception dispatching sequence proceeds further and the target thread is then given a chance to handle the exception if it wants to do so. If the SEH exception is not handled by the target process, the debugger is then sent another debug event, called a second-chance notification, to inform it that an unhandled exception occurred in the target process. Source


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

简单的回答是:当您在程序中放入断点时,调试器将在该点用int3指令替换您的代码,这是一个软件中断。结果是程序被挂起并调用调试器。

了解调试的另一个有价值的来源是英特尔CPU手册(英特尔®64和IA-32架构) 软件开发者手册)。在第3A卷第16章中,介绍了硬件对调试的支持,如特殊异常和硬件调试寄存器。以下是该章的节选:

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

我不确定windows或Linux是否使用这个标志,但读那一章非常有趣。

希望这能帮助到一些人。