首页 > 代码库 > 软件调试之INT 3讲解

软件调试之INT 3讲解

第4章断点和单步执行

断点和单步执行是两个经常使用的调试功能,也是调试器的核心功能。本章我们将介绍IA-32 CPU是如何支持断点和单步执行功能的。前两节将分别介绍软件断点和硬件断点,第4.3节介绍用于实现单步执行功能的陷阱标志。在前三节的基础上,第 4.4节将分析一个真实的调试器程序,看它是如何实现断点和单步执行功能的。

4.1  软件断点

x86系列处理器从其第一代产品英特尔8086开始就提供了一条专门用来支持调试的指令,即INT 3。简单地说,这条指令的目的就是使CPU中断(break)到调试器,以供调试者对执行现场进行各种分析。当我们调试程序时,可以在可能有问题的地方插 入一条INT 3指令,使CPU执行到这一点时停下来。这便是软件调试中经常用到的断点(breakpoint)功能,因此INT 3指令又被称为断点指令。

4.1.1  感受INT 3

下面通过一个小实验来感受一下INT 3指令的工作原理。在Visual C++ Studio 6.0(以下简称为VC6)中创建一个简单的HelloWorld控制台程序HiInt3,然后在main()函数的开头通过嵌入式汇编插入一条INT 3指令:

int main(INT argc, char* argv[])
{
// manual breakpoint
_asm INT 3;
printf("Hello INT 3!\n");
return 0;
}

当在VC环境中执行以上程序时,会得到图4-1所示的对话框。点OK按钮后,程序便会停在INT 3指令所在的位置。由此看来,我们刚刚插入的一行(_asm INT 3)相当于在那里设置了一个断点。实际上,这也正是通过注入代码手工设置断点的方法,这种方法在调试某些特殊的程序时还非常有用。

 
图4-1  CPU遇到INT 3指令时会把执行权移交给调试器

此时打开反汇编窗口,可以看到内存地址00401028处确实是INT 3指令:

10:       _asm INT 3;
00401028   int         3

打开寄存器窗口,可以看到程序指针寄存器的值也是00401028。

EAX = CCCCCCCC EBX = 7FFDE000 ECX = 00000000 EDX = 00371588
ESI = 00000000 EDI = 0012FF80
EIP = 00401028 ESP = 0012FF34 EBP = 0012FF80 ……

根据我们在第3章中的介绍,断点异常(INT 3)属于陷阱类异常,当CPU产生异常时,其程序指针是指向导致异常的下一条指令的。但是,现在我们观察到的结果却是指向导致异常的这条指令的。这是为什 么呢?简单地说,是操作系统为了支持调试对程序指针做了调整。我们将在后面揭晓答案。