【汇编语言】[BX]和loop指令(二)——在Debug中跟踪用loop指令实现的循环程序
文章目录
- 前言
- 1. 题目引入
- 1.1 问题一
- 1.2 问题二
- 1.3 问题三
- 1.4 代码实现
- 2. 程序跟踪
- 2.1 加载程序,用r命令查看寄存器内容
- 2.2 用U命令查看内存中的程序
- 2.3 用T命令进行程序跟踪
- 2.4 用P命令使得程序返回
- 3. 循环次数更多的程序
- 3.1 代码实现
- 3.2 一个新的命令——g
- 3.3 如何使用g命令
- 3.4 循环次数太多了!!!
- 3.5 P命令的又一个功能——一次执行完循环
- 3.6 g命令也可以快速出循环
- 结语
前言
📌
汇编语言是很多相关课程(如数据结构、操作系统、微机原理)的重要基础。但仅仅从课程的角度出发就太片面了,其实学习汇编语言可以深入理解计算机底层工作原理,提升代码效率,尤其在嵌入式系统和性能优化方面有重要作用。此外,它在逆向工程和安全领域不可或缺,帮助分析软件运行机制并增强漏洞修复能力。
本专栏的汇编语言学习章节主要是依据王爽老师的《汇编语言》来写的,和书中一样为了使学习的过程容易展开,我们采用以8086CPU为中央处理器的PC机来进行学习。
1. 题目引入
考虑这样一个问题,计算ffff:0006单元中的数乘以3,结果存储在dx中。
我们分析一下,可能会遇到以下三个问题:
1.1 问题一
(1)运算后的结果是否会超出dx所能存储的范围?
ffff:0006 单元中的数是一个字节型的数据,范围在0-255之间,则用它和3相乘结果不会大于65535,可以在dx中存放下。
1.2 问题二
(2)我们用循环累加来实现乘法,用哪个寄存器进行累加?
我们将ffff:0006单元中的数赋值给ax,用dx进行累加。先设(dx)=0,然后做3次(dx)=(dx)+(ax)。
1.3 问题三
(3) ffff:0006单元是一个字节单元,ax是一个16位寄存器,数据长度不一样,如何赋值?
注意,我们说的是“赋值”,就是说,让 ax 中的数据的值(数据的大小)和ffff:0006 单元中的数据的值(数据的大小)相等。
例如:8 位数据01H和16位数据0001H的数据长度不一样,但它们的值是相等的。
那么我们如何赋值?
设ffff:0006单元中的数据是XXH,若要ax中的值和ffff:0006单元中的相等,ax中的数据应为00XXH。所以,若实现ffff:0006单元向ax赋值,我们应该令(ah)=0,(al)=(ffff6H)。
1.4 代码实现
想清楚以上的3个问题之后,编写程序如下。
assume cs:code
code segmentmov ax,0ffffhmov ds axmov bx,6 ;以上,设置ds:bx指向ffff:6mov al,[bx]mov ah,0 ;以上,设置(al)=((ds*16)+(bx)),(ah)=0mov dx,0 ;累加寄存器清0mov cx,3 ;循环3次
s: add dx,axloop s ;以上累加计算(ax)*3mov ax,4c00hint 21h ;程序返回code ends
end
注意程序中的第一条指令mov ax,0ffffh
。
我们知道大于9FFFH的十六进制数据A000H、A001H、…… 、C000H、C001H、……、FFFEH、FFFFH等,在书写的时候都是以字母开头的。而在汇编源程序中,数据不能以字母开头,所以要在前面加0。(因为我们使用的MASM编译器对于字母开头的数据不认识,会报错)
2. 程序跟踪
下面我们对程序的执行过程进行跟踪。
首先 ,我们将它编辑为源程序文件,文件名定为 p3.asm ;对其进行编译连接后生成p3.exe;(这些知识在【汇编语言】第一个程序(三)—— 深度剖析汇编程序的执行流程:编辑、编译、连接与运行_汇编语言程序 运行这一章的内容中已经学习过了);然后再用Debug对p3.exe中的程序进行跟踪。
2.1 加载程序,用r命令查看寄存器内容
用Debug加载 p3.exe 后,用r命令查看寄存器中的内容,如下图所示。(不同的电脑呈现出来的初始内容可能不同)
上图中(ds)=0B2DH,所以,程序在0B3D:0处。(这个我们在【汇编语言】第一个程序(四)—— 谁在幕后启动程序 : 探讨可执行文件的装载与执行这篇文章中讲过,是因为在程序加载到内存时会有一个程序段前缀psp在我们的程序的前面,而这个psp的大小为256个字节)
2.2 用U命令查看内存中的程序
我们看一下,(cs)=0B3DH,(IP)=0,CS:IP 正指向程序的第一条指令。再用u命令看一下被 Debug 加载入内存的程序,如图下所示。
可以看到,从0B3D:0000-0B3D:001A是我们的程序,0B3D:0014处是源程序中的指令loops,只是此处loops中的标号s已经变为一个地址0012h。如果在执行“loop 0012
”时,cx减1后不为0,“loop 0012
”就把IP设置为0012h,从而使 CS:IP 指向0B3D:0012处的 add dx,ax,实现转跳。
2.3 用T命令进行程序跟踪
我们开始进行跟踪,如下图所示。
上图中,前3条指令执行后,(ds)=fffh,(bx)=6,ds:bx指向 ffff:6单元。Debug 显示出当前要执行的指令“mov al,[bx]
”,因为是读取内存的指令,所以 Debug 将要访问的内存单元中的内容也显示出来,可以看到屏幕最右边显示的“ds:0006=32”,由此,我们可以方便地知道目标单元(ffff6)中的内容是32h。
继续执行,如下图所示。
上图中,这两条指令执行后,(ax)=0032h,完成了从:6单元向ax的赋值
继续,如下图所示。
上图中,这两条指令执行后,(dx)=0,完成对累加寄存器的初始化;(cx)=3,完成对循环计数寄存器的初始化。
下面,将开始循环程序段的执行,继续,如下图所示。
上图中,CPU 执行0B3D:0012处的指令“add dx,ax
”后,(IP)=0014h,CS:IP 指向0B3D:0014处的指令“loop 0012
”。
CPU 执行“loop 0012
”,
第一步先将(cx)减1,(cx)=2;
第二步因(cx)不等于0,将IP设为0012h。
指令“loop 0012
”执行后,(IP)=0012h,CS:IP再次指向0B3D:0012处的指令“add dx,ax
”,这条指令将再次得到执行。
❗注意,“loop 0012”执行后(cx)=2,也就是说,“loop 0012
”还可以进行两次循环。
接着,将重复执行“add dx,ax”和“loop 0012”,直到(cx)=0为止,如下图所示。
注意上图中,最后一次执行“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的最后结果。
2.4 用P命令使得程序返回
我们继续,将程序执行完,如下图所示。
上图中,执行完最后两条指令后,程序返回到Debug中。注意“int21”要用p命令执行。
3. 循环次数更多的程序
3.1 代码实现
上面,我们通过对一个循环程序的跟踪,更深入一步地讲解了loop指令实现循环的原理。下面,我们将之前的程序改一下,计算ffff:0006单元中的数乘以123,结果存储在dx 中。
这很容易完成,只要将循环的次数改为123就可以了。程序如下。
assume cs:code
code segmentmov ax,0ffffhmov ds axmov bx,6 ;以上,设置ds:bx指向ffff:6mov al,[bx]mov ah,0 ;以上,设置(al)=((ds*16)+(bx)),(ah)=0mov dx,0 ;累加寄存器清0mov cx,123 ;循环123次
s: add dx,axloop s ;以上累加计算(ax)*3mov ax,4c00hint 21h ;程序返回code ends
end
3.2 一个新的命令——g
我们用 Debug对这个程序的循环程序段进行跟踪,现在有这样一个问题:前面的条指令,即标号s前的指令,已经确定在逻辑上完全正确,我们不想再一步步地跟踪了,只想跟踪循环的过程。所以希望可以一次执行完标号s前的指令。可以用一个新的(对我们来说是新的,因为以前没用过)Debug命令g来达到目的。
3.3 如何使用g命令
下面来实际操作一下,我们用刚才的程序生成最终的可执行文件“c:masm\p4.exe”,用Debug 加载 p4.exe,然后看一下程序在内存中的情况,如下图所示。
上图中,循环程序段从CS:0012开始,CS:0012前面的指令,我们不想再一步步地跟踪,希望能够一次执行完,然后从CS:0012处开始跟踪。可以这样来使用g命令,“g 0012”,它表示执行程序到当前代码段(段地址在CS中)的0012h处。
也就是说“g 0012”将使 Debug从当前的CS:IP指向的指令执行,一直到(IP)=0012h为止。具体的情况如下图所示。
上图中,Debug执行“g 0012”后,CS:0012前的程序段被执行,从各个相关的寄存器中的值,我们可以看出执行的结果。
3.4 循环次数太多了!!!
下面我们对循环的过程进行跟踪,如下图所示。
上图中,我们跟踪了两次循环的过程。其实,通过这两次循环过程,已经可以确定循环程序段在逻辑上是正确的。
我们不想再继续一步步地观察循环的过程了,怎样让程序向下执行呢?继续像从前那样使用t命令?
显然这是不可行的,因为还要进行121((cx)=79h)次循环,如果像前两次那样使用t命令,我们得使用121*2=242次t命令才能从循环中出来。
3.5 P命令的又一个功能——一次执行完循环
这里的问题是,我们希望将循环一次执行完。可以使用p命令来达到目的。再次遇到loop 指令时,使用p命令来执行,Debug 就会自动重复执行循环中的指令,直到(cx)=0为止。具体情况如下图所示。
上图中,在遇到“loop 0012
”时,用p命令执行,Debug 自动重复执行“loop 0012
”和“add dx,ax
”两条指令,直到(cx)=0。最后一次执行“loop 0012
”后,(cx)=0,(IP)=0016h,当前指令为CS:0016 处的“mov ax,4c00
”。
3.6 g命令也可以快速出循环
当然,也可以用g命令来达到目的,可以用“g 0016”直接执行到CS:0016处。具体情况如下图所示。
结语
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下。
也可以点点关注,避免以后找不到我哦!
Crossoads主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的动力!