我一直想知道调试器是如何工作的?特别是可以“附加”到已经运行的可执行文件。我知道编译器将代码翻译成机器语言,但调试器如何“知道”它被附加到什么?
当前回答
我的理解是,当你编译一个应用程序或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.
其他回答
在Linux中,调试进程从ptrace(2)系统调用开始。本文提供了一个很好的教程,介绍如何使用ptrace实现一些简单的调试构造。
我认为这里有两个主要问题需要回答:
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.
我的理解是:
对于x86上的软件断点,调试器将指令的第一个字节替换为CC (int3)。这是通过Windows上的WriteProcessMemory完成的。当CPU到达该指令并执行int3时,这将导致CPU生成一个调试异常。操作系统接收到这个中断,意识到进程正在调试,并通知调试器进程已命中断点。
在命中断点并停止进程之后,调试器查看它的断点列表,并用原来的字节替换CC。调试器设置TF,即EFLAGS中的Trap标志(通过修改CONTEXT),并继续该过程。Trap标志导致CPU在下一条指令上自动生成一个单步异常(INT 1)。
当被调试的进程下一次停止时,调试器再次用CC替换断点指令的第一个字节,进程继续。
我不确定这是否是所有调试器都实现的方式,但我已经编写了一个Win32程序,它使用这种机制来调试自己。完全没用,但有教育意义。
如果你使用的是Windows操作系统,John Robbins写的《调试。net和Windows应用程序》是一个很好的参考资料:
http://www.amazon.com/dp/0735615365
(甚至是旧版本:“调试应用程序”)
这本书有一章是关于调试器如何工作的,其中包括几个简单(但可以工作的)调试器的代码。
由于我不熟悉Unix/Linux调试的细节,这些东西可能根本不适用于其他操作系统。但我猜,作为一个非常复杂的主题的介绍,这些概念(如果不是细节和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++源文件?