为什么在X86上使用单步执行指令?

时间:2011-10-29 22:03:50

标签: debugging gdb x86

因此存在“int 3”,它是用于调试器中的断点的中断指令。

但是还有“int 1”用于单步执行。但为什么需要呢?我已经读过,在EFLAGS寄存器中设置陷阱标志(TF)将启用单步执行,并将为每条指令捕获到操作系统。那么为什么需要单独的中断类型?

谢谢!

5 个答案:

答案 0 :(得分:11)

int 3是一个特殊的1字节中断。调用它将进入调试器(如果存在),否则应用程序通常会崩溃。

当调试器设置陷阱标志时,这会导致处理器在每条指令后自动执行int 1中断。这允许调试器通过指令单步执行,而无需插入int 3指令。您不必显式调用此中断。

答案 1 :(得分:8)

您将INT和INT 3指令与中断向量混淆,如果调用该指令,这些指令将通过这些向量调用。没有单步指令。

INT 3(或“断点指令”)将调用调试器(如果它存在(或者更确切地说,调试器将挂接INT 3向量,以便在INT 3发生时,将调用调试器)。

如果调试器设置TF(跟踪标志),则每条指令都会导致#1中断发生。这将导致调用该中断向量中的任何地址。希望这将是调试器的单步程序。最终,调试器将清除TF,导致单步中断停止。

答案 2 :(得分:3)

其他人已经解释了中断向量1和int 3指令之间的区别。

现在,如果您想知道为什么在处理调试中断时涉及多个中断向量,我认为这只是因为原始的8086/8088电路旨在相对简单并且执行相对简单的软件。它只有很少的特殊中断向量,并且int向量1仅用于单步陷阱,并且通过中断向量数区分它与断点陷阱是微不足道的,也就是说,只需要有不同的处理程序即可。向量1和3.该设计被转移到随后的x86 CPU。较新的CPU基本上“快速”地将特殊中断向量集扩展到大约20,以处理新的异常并扩展调试功能,在原始单步陷阱之上添加几个其他有用的中断向量1触发器(例如,取指令,内存/端口I / O,任务切换等)。将大多数它们置于相同的中断向量下是合乎逻辑的,因为它们是相关的而不是消耗更多的向量。

答案 3 :(得分:3)

int 3用于设置断点,以便代码可以自由执行,直到达到特定点(断点)。这样可以加快调试过程,因此无需捕获已知良好的代码。

int 1用于在每条指令后无条件停止。当执行条件分支指令并且状态标志的条件未知时,这可以很方便。否则,需要在分支地址和分支后面的指令地址设置断点。

int 1也适用于电路板启动,当电路板硬件和启动都是新的和未经测试时。

答案 4 :(得分:1)

以下是《英特尔软件开发人员手册第一卷》中的一些引用。 3B,第17章:

  

Intel 64和IA-32架构专用于两个中断   用于处理调试异常的向量:向量1( debug 异常,   #DB)和向量3(断点例外,#BP)。

对于调试例外

  

debug-exception 处理程序通常是调试程序或程序的一部分。   更大的软件系统。处理器为生成调试异常   以下任何条件。调试器检查DR6和DR7寄存器中的标志,以确定导致异常的条件   以及哪些其他条件可能适用。

对于断点例外:

  

断点异常(中断3)是由执行   INT 3指令。调试器使用断点异常...   暂停程序执行的机制以检查寄存器和   内存位置。

     

使用Intel386和更高版本的IA-32处理器,更方便   使用断点地址寄存器(DR0至   DR3)。但是,断点异常仍然有用   断点调试器,因为断点异常可以调用   单独的异常处理程序。断点异常也很有用   当需要设置比调试更多的断点时   寄存器或将断点放置在源代码中   正在开发程序。

因此我们可以看到,断点异常使您可以暂停程序执行,而调试异常可以检查几种情况并以不同的方式对待它们。

仅具有调试例外,您将不能在所需的位置中断。只有在某个位置中断后,您才能然后为调试异常所消耗的单步或其他操作配置处理器。

INT 3是一个单字节操作码。因此,它可以覆盖任何具有可控副作用的现有指令,以中断当前程序流的执行。没有它,您怎么有机会在适当的时间内在EFLAGS中设置单步标记而没有副作用?

因此需要两步式的 break-and-then-debug 机制。

整个流程是:

首先,将调试器作为处理程序连接到 int 和int 1(#DB)和int 3(#BP)。

然后将int3放到您要插入的位置。然后调试器就有机会介入。

一旦调试器开始处理int3(#BP),如果您要单步执行,请告诉调试器在EFLAGS中设置陷阱标志(TF)。然后,CPU将在每条指令后生成一个int 1(#DB)。由于调试器也连接到int 1(#DB),因此它也将有机会介入。

ADD 1-5/31/2019 PM 5:55

(我与一位朋友讨论了调试器的工作方式。他之前写过调试器。)

似乎INT 3(#BP)是最重要的一个。您可以将INT 3指令显式放置在您要插入的位置。或者,您也可以让调试器为您执行此操作。

一旦命中INT 3,CPU将保存已损坏程序的上下文并切换到INT 3处理程序,该处理程序通常是调试器的一部分。 现在,已损坏的程序已挂起。。调试器只是普通的Windows或任何桌面应用程序。它可以使用普通的桌面消息循环等待,以等待用户的命令来决定如何对待正在调试的程序。 因此似乎被调试者和调试器现在都在等待。但是原因却大不相同。

然后,调试器可以检查保存的被调试者上下文。或者,它可能只恢复被调试方的已保存上下文并让其恢复。或者可以在EFLAGS中设置TF标志,以便处理器在每条指令后生成#DB

但是 经常 ,用户可能不希望在指令级别上单步执行 。他们可能想在C语句级别上调试 可以 由许多指令组成。因此,调试器可以使用调试信息(例如PDB文件)来查找位置信息。如果用户想单步执行C语句级别,则调试器可以找到下一个C语句的开始指令,并用INT 3重写它的第一个字节。然后一切都重新开始。