5.3 在Debug中跟踪用loop指令实现的循环程序
考虑这样一个问题,计算ffff:0006单元的数乘以3,结果存储在dx中。
我们分析一下。
(1)运算后的结果是否会超过dx所能存储的范围?
ffff:0006单元中的数是一个字节型的数据,范围在0~255之间,则用它和3相乘结果不会大于65535,可以在dx中存放下。
(2)用循环累加来实现乘法,用哪个寄存器进行累加?
将ffff:0006单元中的数赋值给ax,用dx进行累加。先设(dx)=0,然后做3次(dx)=(dx)+(ax)。
(3)ffff:6单元是一个字节单元,ax是一个16位寄存器,数据长度不一样,如何赋值?
注意,我们说的是“赋值”,就是说,让ax中的数据的值(数据的大小)和ffff:0006单元中的数据的值(数据的大小)相等。8位数据01H和16位数据0001H的数据长度不一样,但它们的值是相等的。
那么我们如何赋值?设ffff:0006单元中的数据是XXH,若要ax中的值和ffff:0006单元中的相等,ax中的数据应为00XXH。所以,若实现ffff:0006单元向ax赋值,应该令(ax)=0,(al)=(ffff6H)。
想清楚以上的3个问题之后,编写程序如下。
程序 5.3
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov bx,6 ;以上,设置ds:bx指向ffff:6
mov al,[bx]
mov ah,0 ;以上,设置(al)=((ds*16)+(bx)),(ah=0)
mov dx,0 ;累加寄存器清0
mov cx,3 ;循环3次
s: add dx,ax
loop s ;以上累加计算(bx)*3
mov ax,4c00h
int 21h ;程序返回
code ends
end
注意程序中的第一条指令 mov ax,offffh。我们知道,大于9FFFh的十六进制数据A000H、A001H...C000H、c001H...FFFEH、FFFFH等,在书写的时候都是以字母开头的。而在汇编源程序中,数据不能以字母开头,所以要在前面加0。比如,9138h在汇编源程序中可以直接写为“9138h”,而A000h在汇编源程序中要写为“0A000h”。
下面我们对程序的执行过程进行跟踪。首先,将它编辑为源程序文件,文件名定位p3.asm;对其进行编译连接后生成p3.exe;然后再用Debug对p3.exe中的程序进行跟踪。
用Debug加载p3.exe后,用r命令查看寄存器中的内容,如图5.3所示。
图5.3中(ds)=0B2DH,所以,程序在0B3D:0处(如果读者还不清楚这是什么,可以复习4.9节的内容)。我们看一下,(cs)=0B3DH,(IP)=0,CS:IP正指向程序的第一条指令。再用u命令看一下被Debug加载入内存的程序,如图5.4所示。
可以看到,从0B3D:0000~0B3D:001A是我们的程序,0B3D:0014处是源程序中的指令loop s,只是此处loop s中的标记s已经变为一个地址0012H。如果在执行“loop 0012”时,cx减1后不为0,“loop 0012”就把IP设置为0012H,从而使CS:IP指向0B3D:0012处的add dx,ax,实现转跳。
我们开始跟踪,如图5.5所示。
图5.5中,前3条指令执行后,(ds)=ffffh,(bx)=6,ds:bx指向ffff:6单元。Debug显示出当前要执行的指令“mov al,[bx]”,因为是读取内存的指令,所以Debug将要访问的内存单元中的内容也显示出来,可以看到屏幕最右边显示的“ds:0006=32”,由此,我们可以方便地知道目标单元(ffff6)中的内容是32H。
继续执行,如图5.6所示。
图5.6中,这两条指令执行后,(ax)=0032h,完成了从ffff:6单元向ax的赋值。
继续,如图5.7所示
图5.7中,这两条指令执行后,(dx)=0,完成对累加寄存器的初始化;(cx)=3,完成对循环计数寄存器的初始化。
下面将开始循环程序段的执行。我们继续,如图5.8所示。
图5.8中,CPU执行0B3D:0012处的指令“add dx,ax”后,(IP)=0014h,CS:IP指向0B3D:0014处的指令“loop 0012”。CPU执行“loop 0012”,第一步先将(cx)减1,(cx)=2;第二步因(cx)不等于0,将IP设为0012。指令“loop 0012”执行后,(IP)=0012,CS:IP再次指向0B3D:0012处的指令“add dx,ax”,这条指令将再次得到执行。注意,“loop 0012”执行后(cx)=2,也就是说,“loop 0012”还可以进行两次循环。
接着,将重复执行“add dx,ax”和“loop 0012”,直到(cx)=0位置,如图5.9所示。
注意图5.9中,最后一次执行“loop 0012”的结果。执行前(cx)=1,CPU执行“loop 0012”,第一步,(cx)=(cx)-1,(cx)=0;第二步,因为(cx)=0,所以loop指令不转跳,(IP)=0016h,CPU向下执行0B3D:0016处的指令“mov ax,4c00”。
在完成最后一次“add dx,ax”后,(dx)=96h,此时dx中为累计计算(ax)*3的最后结果。
我们继续,将程序执行完,如图5.10所示。
图5.10中,执行完最后两条指令后,程序返回到Debug中。注意“int 21”要用p命令执行。
上面,我们通过对一个循环程序的跟踪,更深入一步地讲解了loop指令实现循环的原理。下面,我们将程序5.3改一下,计算ffff:0006单元的数乘以123,结果存储在dx中。
这很容易完成,只要将循环的次数改成123就可以了。程序如下。
程序 5.4
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov bx,6 ;以上,设置ds:nx指向ffff:6
mov al,[bx]
mov ah,0 ;以上,设置(al)=((ds*16)+(bx)),(ah)=0
mov dx,0 ;累加寄存器清0
mov cx,123 ;循环123次
s: add dx,ax
loop s ;以上累计计算(ax)*123
mov ,ax,4c00h ;程序返回
int 21h
code ends
end
我们用Debug对这个程序的循环程序段进行跟踪,现在有这样一个问题:前面的7条指令,即标号s前的指令,已经确定在逻辑上完成正确,我们不想再一步步地跟踪了,只想跟踪循环的过程。所以希望可以一次执行完标号s前的指令。可以用一个新的(对我们来说是新的,因为以前没有用过)Debug命令g来达到目的。
下面来实际操作一下,我们用程序5.4生成最终的可执行文件“c:\masm\p4.exe”,用Debug加载p4.exe,然后看一下程序在内存中的情况,如图5.11所示。
在5.11中,循环程序段从CS:0012开始,CS:0012前面的指令,我们不想再一步步地跟着,希望能够一次执行完,然后从CS:0012处开始跟踪。可以这样来使用g命令,“g 0012”,它表示执行程序到当前代码段(段地址在CS中)的0012H。也就是说“g 0012”将使Debug从当前的CS:IP指向的指令执行,一直到(IP)=0012为止。具体情况如图5.12所示。
在图5.12中,Debug执行“g 0012”后,CS:0012前的程序段被执行,从各个相关的寄存器中的值,我们可以看出执行的结果。
下面我们对循环的过程进行跟踪,如图5.13所示。
图5.13中,我们跟踪了两次循环的过程。其次,通过这两次循环过程,已经可以确定循环程序段在逻辑上是正确的。我们不想再据需一步步地观察循环的过程了,怎样让程序向下执行呢?据需像从前那样使用t命令?显然这是不可行的,因为还要进行121((cx)=79h)次循环,如果像前两次那样使用t命令,我们得使用121*2=242次t命令才能循环出来。
这里的问题是,我们希望将循环一次执行完。可以使用p命令来达到目的。再次遇到loop指令时,使用p命令来执行,Debug就会自动重复执行循环中的指令,直到(cx)=0为止。具体情况如图5.14所示。
图5.14中,在遇到“loop 0012”时,用p命令执行,Debug自动重复执行“loop 0012”和“add dx,ax”两条指令,直到(cx)=0。最后一次执行“loop 0012”后,(cx)=0,(IP)=0016H,当前指令为CS:0016处的“mov ax,4c00”。
当然,也可以用g命令来达到目的,可以用“g 0016”直到执行到CS:0016处。具体情况如图5.15所示。