我一直想知道调试器是如何工作的?特别是可以“附加”到已经运行的可执行文件。我知道编译器将代码翻译成机器语言,但调试器如何“知道”它被附加到什么?
当前回答
我认为这里有两个主要问题需要回答:
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指令替换您的代码,这是一个软件中断。结果是程序被挂起并调用调试器。
其他回答
在Linux中,调试进程从ptrace(2)系统调用开始。本文提供了一个很好的教程,介绍如何使用ptrace实现一些简单的调试构造。
如果你使用的是Windows操作系统,John Robbins写的《调试。net和Windows应用程序》是一个很好的参考资料:
http://www.amazon.com/dp/0735615365
(甚至是旧版本:“调试应用程序”)
这本书有一章是关于调试器如何工作的,其中包括几个简单(但可以工作的)调试器的代码。
由于我不熟悉Unix/Linux调试的细节,这些东西可能根本不适用于其他操作系统。但我猜,作为一个非常复杂的主题的介绍,这些概念(如果不是细节和api)应该“移植”到大多数操作系统。
我认为这里有两个主要问题需要回答:
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指令替换您的代码,这是一个软件中断。结果是程序被挂起并调用调试器。
我的理解是,当你编译一个应用程序或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.
调试器如何工作的细节将取决于您正在调试的内容以及操作系统是什么。对于Windows上的本机调试,您可以在MSDN: Win32 debugging API中找到一些详细信息。
用户通过名称或进程ID告诉调试器要附加到哪个进程。如果是名称,那么调试器将查找进程ID,并通过系统调用启动调试会话;在Windows下,这将是DebugActiveProcess。
一旦附加,调试器将进入一个事件循环,就像任何UI一样,但事件不是来自窗口系统,而是操作系统将根据正在调试的进程中发生的事情生成事件——例如发生异常。看到WaitForDebugEvent。
调试器能够读写目标进程的虚拟内存,甚至通过操作系统提供的api调整其寄存器值。请参阅Windows的调试函数列表。
调试器能够使用符号文件中的信息将地址转换为源代码中的变量名和位置。符号文件信息是一组单独的api,并不是操作系统的核心部分。在Windows上,这是通过调试接口访问SDK实现的。
如果正在调试托管环境(。NET、Java等)的过程通常看起来类似,但细节不同,因为虚拟机环境提供的是调试API而不是底层操作系统。
推荐文章
- 调试器是如何工作的?
- 当有命令行参数时,如何使用GDB分析程序的核心转储文件?
- 如何在Visual Studio中找到堆栈跟踪?
- 有一个好的Valgrind Windows的替代品吗?
- Eclipse调试器总是阻塞在ThreadPoolExecutor上,没有任何明显的异常,为什么?
- Visual Studio:如何打破处理异常?
- 确保您的项目构建设置正在生成一个dSYM文件。对于所有配置,DEBUG_INFORMATION_FORMAT都应该设置为dwarf-with-dsym
- 如何获得GDB中所有线程的回溯?
- 如何检测IE11?
- 如何通过参数和重定向stdin从一个文件到程序运行在gdb?
- git索引到底包含什么?
- 我如何调试git/git-shell相关的问题?
- Visual Studio拒绝忘记断点?
- 如何在Python自己的调试器(PDB)中执行多行语句
- 我如何在Visual Studio中预处理后看到C/ c++源文件?