购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

6.12 本章程序的调试

6.12.1 调试命令“n”的使用

要调试本章的程序,可以利用上一章里介绍的方法,其中非常重要的一个调试命令是单步执行命令“s”。

单步执行有一个缺点,就是会陷入同一条指令的多次重复执行里,比如rep movsw 指令。如图6-7 所示,由于是在两个内存区域之间复制字符,rep movsw 指令要执行很多次,每当输入“s”命令后,执行的依然是movsw 指令,直到寄存器CX 的内容为零,复制过程结束后,才开始单步执行下一条指令。注意,在图中,Bochs 使用了rep movsw 指令的另一种形式“rep movsw word ptr es:[di],word ptr ds:[si]”,它们其实是一回事。

图6-7 单步执行rep movsw 指令时的情景

除了rep movsw 指令,本章中的loop 指令也会使单步执行陷入循环体中,直到循环条件不成立,退出循环时,才开始单步执行循环体外的下一条指令。如图6-8 所示,当单步执行循环指令loop .-9 时(本指令的物理内存地址是0x0000000000007C4A),下一条指令马上变成循环体内的第一条指令(xor dx,dx,物理内存地址为0x0000000000007C43)。只有当寄存器CX 的内容为零时,才开始单步执行循环体外的下一条指令。

图6-8 Bochs 单步执行loop 指令时的情景

在图中,loop 指令的目标地址是用标号“.-9”表示的,这条指令就是本章程序中的指令

但是,程序在编译后,所有标号都消失了。当Bochs 重现这些程序时,不可能知道这里原先是一个标号“digit”。因此,它用loop 指令的操作数作为标号。我们知道,loop 指令的操作数是一个相对量,是用目标处的汇编(偏移)地址减去当前指令的汇编(偏移)地址,再减去loop 指令的长度(2)得到的。因此,如图中所示,0x7c4a 减去0x7c43(循环体内的第一条指令xor dx,dx),再减去2,只保留一个字节,就是0xf7,即十进制数-9。

可以想象,如果循环的次数很多(有时候,循环成千上万次是很正常的),则我们就无法调试循环体后面的程序。在这种情况下,你应当在执行rep movsb、repmovsw 和loop 指令的时候,使用调试命令“n”。此时,Bochs 将自动完成循环过程,并在循环体外的下一条指令前停住。

6.12.2 调试命令“u”的使用

之所以能够使用调试命令“n”来越过循环体,是因为Bochs 知道控制循环次数的是寄存器CX,它可以自动监视整个循环过程。

但是,“n”命令对于下面的循环结构无效:

原因很简单,条件转移指令(在这里是jns)不是循环指令,转移到的目标位置一般位于前方(源程序的后面),而不是像这里一样,目标位置是先前已经执行过的指令,于是恰巧构成了一个特殊的循环。

因此,如图6-9 所示,当用“n”命令执行jns show(在图中显示的是jns .-15)后,下一条指令又变成物理地址为0x0000000000007c52 处的指令mov al,byte ptr ds:[bx+si](即mov al,[bx+si]),因为SF 标志为“0”。

如何越过条件转移指令构造的特殊循环体,往后调试执行呢?要解决这个问题,只需要知道循环体后面那条指令的物理地址即可,这可以使用反汇编命令“u”。

反汇编的意思是根据机器指令代码生成可读的汇编语言指令,正好与汇编过程相反。“u”命令可以使用两个参数,第一个参数是跟在“/”后面的数字,指定反汇编出多少条指令;第二个参数用于指定一个内存地址,Bochs 从这里开始反汇编操作。

图6-9 在Bochs 中用“n”命令执行jns 指令时的情景

如图6-10 所示,在jns .-15 指令执行前,用“u”命令反汇编。该命令指示从指令jns .-15 所在的地址处(0x0000000000007c5f)开始反汇编,而且只需得到2 条指令即可。注意,如果是从当前地址处开始反汇编,则地址参数可以省略。在这里,只需使用“u/2”即可。

命令下达后,Bochs 迅速做出回应,给出了两条指令,并显示了各自所在的物理地址。很显然,条件转移指令jns 之后的那条指令是mov word ptr es:[di],0x0744,也就是本章程序中的mov word [es:di],0x0744,其物理地址是0x7c61。

依然如图中所示,为了越过这个特殊的循环结构,首先使用“b”命令把0x7c61 设为断点,然后执行“c”命令来连续执行程序,直至发现已经处于断点位置。

图6-10 使用反汇编命令显示指定地址处的指令

6.12.3 用调试命令“info”察看标志位

为了察看标志寄存器FLAGS 的状态(各个标志位),可以在Bochs 中使用命令“info”。使用该命令可以显示多种类型的处理器信息,显示标志寄存器的状态只是其功能之一。

为了显示标志寄存器的状态,可以使用“eflags”参数,即“info eflags”。INTEL8086 的标志寄存器是16 位的,称做FLAGS;在32 位处理器上,该标志寄存器做了扩展,达到了32 位,称做EFLAGS。因此,在Bochs 中,应当输入“info eflags”而不是“info flags”。

要察看标志寄存器的状态,应当在调试本章程序的过程中进行。如图6-11 所示,我们在执行第33 行的xor dx,dx 指令之前,察看一下标志寄存器的状态。

图6-11 在Bochs 中察看标志寄存器的状态

如图中所示,当命令输入之后,Bochs 显示一行古怪的文字作为回应,请允许我来解释一下这些东西都是什么。

首先,像“id、vip、vif、ac、vm、rf、nt、IOPL”这些标志,是32 位处理器才有的,现在不用管它们。

然后,“of”是溢出标志;“df”是方向标志;“if”和“tf”是和中断有关标志,第9 章才能讲到;“sf”是符号标志;“zf”是零标志;“af”是辅助进位标志;“pf”是奇偶标志;“cf”是进位标志。

问题是,光显示标志的名称,怎么知道某个标志位是“0”还是“1”呢?很简单,如果显示的标志名称是小写的,那么,说明该标志为“0”;否则,该标志的状态为“1”。如图中所示,因为符号标志是大写的“SF”,因此,表明该标志当前的状态是“1”。

注意,我们现在关注的是当xor 指令执行后,标志寄存器的变化情况。接下来,我们单步执行xor dx,dx 指令,然后再显示一次标志寄存器的内容。

如图6-12 所示,该指令执行后,符号标志的名称变成小写,零标志和奇偶标志的名称变为大写。请你想一想,这是为什么?

图6-12 xor dx,dx 指令对标志寄存器的影响

检测点6.5

调试本章程序。要求:使用反汇编命令定位到源程序第53 行(jmp near $),然后在这里设置断点,并用“c”命令连续执行到该断点位置。注意,因为Bochs 会把非指令的数据也视为指令,这将有可能导致反汇编不正确。因此,要小心避开这些数据区,在Bochs 把物理地址0x7C00 之后的数据(一大堆零)也反汇编成指令时,不要感到惊讶。 F6bCfiVjRqKW1ey3wpNn7h5CBpmJ9EITQGgp6yGqa44CWQvMW5Q+Q23D+lWlJbYi

点击中间区域
呼出菜单
上一章
目录
下一章
×