所以有“int 3”这是一个用于调试器断点的中断指令。
但是还有“int 1”用于单步执行。但为什么需要呢?我已经读过,在EFLAGS寄存器中设置陷阱标志(TF)将启用单步执行,并将为每条指令捕获到操作系统。那么为什么需要单独的中断类型呢?
谢谢!
int 3
是一个特殊的1字节中断。调用它将进入调试器(如果存在),否则应用程序通常会崩溃。
当调试器设置陷阱标志时,这会导致处理器在每条指令后自动执行int 1
中断。这允许调试器通过指令单步执行,而无需插入int 3
指令。您不必显式调用此中断。
您将INT和INT 3指令与中断向量混淆,如果调用该指令,这些指令将通过这些向量调用。没有单步指令。
如果调试器存在,INT 3(或“断点指令”)将调用调试器(或者更确切地说,调试器将挂接INT 3向量,以便在INT 3发生时调用调试器)。
如果调试器设置TF(跟踪标志),则每条指令都将导致#1中断发生。这将导致调用该中断向量中的任何地址。希望这将是调试器的单步程序。最终,调试器将清除TF,导致单步中断停止。
其他人已经解释了中断向量1和int 3指令之间的区别。
现在,如果您想知道为什么在处理调试中断时涉及多个中断向量,我认为这只是因为原始的8086/8088电路旨在相对简单并且执行相对简单的软件。它只有很少的特殊中断向量,并且int向量1仅用于单步陷阱,并且通过中断向量数区分它与断点陷阱是微不足道的,也就是说,只需要有不同的处理程序即可。向量1和3.该设计被转移到随后的x86 CPU。较新的CPU基本上“快速”地将特殊中断向量集扩展到大约20,以处理新的异常并扩展调试功能,在原始单步陷阱之上添加几个其他有用的中断向量1触发器(例如,取指令,内存/端口I / O,任务切换等)。将大多数它们置于相同的中断向量下是合乎逻辑的,因为它们是相关的并且不消耗更多的向量。
int 3用于设置断点,以便代码可以自由执行,直到达到特定点(断点)。这加快了调试过程,因此没有必要陷入已知的良好代码。
int 1用于在每条指令后无条件停止。当执行条件分支指令并且状态标志的条件未知时,这可以很方便。否则,需要在分支地址和分支后面的指令地址设置断点。
当电路板硬件和启动是新的和未经测试时,int 1也可用于电路板启动。
以下是英特尔软件开发人员手册Vol。 3B,第17章:
Intel 64和IA-32架构专门用于处理调试异常的两个中断向量:向量1(调试异常,#DB)和向量3(断点异常,#BP)。
对于调试异常:
调试异常处理程序通常是调试程序或更大的软件系统的一部分。处理器为多种条件中的任何一种生成调试异常。调试器检查DR6和DR7寄存器中的标志,以确定导致异常的条件以及可能适用的其他条件。
对于断点异常:
断点异常(中断3)是由执行INT 3指令引起的。调试器使用断点异常...作为暂停程序执行以检查寄存器和存储器位置的机制。
使用Intel386和更高版本的IA-32处理器,使用断点地址寄存器(DR0到DR3)设置断点更为方便。但是,断点异常对断点调试器仍然有用,因为断点异常可以调用单独的异常处理程序。当需要设置比调试寄存器更多的断点或将断点放在正在开发的程序的源代码中时,断点异常也很有用。
我们可以看到,断点异常使您可以暂停程序执行,而调试异常会检查几个条件并以不同方式对待它们。
仅使用调试例外,您将无法在您想要的位置中断。只有在某个位置中断后,才可以将处理器配置为单步或其他内容,这些内容由调试异常使用。
INT 3是单字节操作码。因此它可以覆盖任何具有可控副作用的现有指令,以打入当前程序流的执行。没有它,你怎么能有机会在适当的时间在EFLAGS中设置单步标志而没有副作用?
因此,必须采用两步中断和后续调试机制。
整个流程是:
首先,将调试器连接为int 1(#DB)和int 3(#BP)的处理程序。
然后将int3放到要中断的位置。然后调试器有机会介入。
一旦调试器开始处理int3(#BP),如果你想要单步执行,告诉调试器在EFLAGS中设置陷阱标志(TF)。然后CPU将在每条指令后生成一个int 1(#DB)。由于调试器也连接到int 1(#DB),因此它也有机会介入。
(我和我的一个朋友讨论了调试器是如何工作的。他之前写了一个调试器。)
看来INT 3(#BP)是最重要的一个。您可以在要闯入的位置明确放置INT 3
指令。或者您可以让调试器为您执行此操作。
一旦命中INT 3
,CPU将保存损坏程序的上下文并切换到INT 3
处理程序,这通常是调试程序的一部分。现在,破坏的程序被暂停。调试器只是一个普通的Windows或任何桌面应用程序。它可以使用普通的桌面消息循环来等待用户的命令来决定如何处理正在调试的程序。因此,看起来debugee和调试器现在都在等待。但原因却截然不同。
然后调试器可以检查debugee的保存上下文。或者它可能只是恢复debugee保存的上下文并让它恢复。或者它可以在EFLAGS中设置TF
标志,以便处理器在每条指令之后生成#DB
。
但通常,用户可能不希望在指令级别单步执行。他们可能想要在C语句级别进行调试,这可以由许多指令组成。因此调试器可以使用调试信息(例如PDB文件)来查找位置信息。如果用户想要在C语句级别单步执行,调试器可以找到下一个C语句的开始指令,并用INT 3
重写它的第一个字节。然后一切都重新开始。