这是汇编第五篇文章了昂,前四篇如下
爱了爱了,这篇寄存器讲的有点意思
没错!cxuan 对汇编下手了
手把手教你汇编 Debug
手撕汇编。。。
之前的文章中介绍过 [0] 表示的是偏移地址为 0 的内存单元 。比如下面的指令
mov ax,[0]
就是将一个内存单元的内容送入 ax,这个内存单元的长度为 2 个字节,是一个字型数据,偏移地址为 0 ,段地址在 ds 中,也就是这个内存单元的地址是 ds:0 ,它的物理地址是 (ds * 16 + 0)H。
除了可以传输字型数据,还可以传输字节型数据,比如下面代码
mov al,[0]
就是将一个内存单元的地址送入 al 中,这个内存单元的长度是 1 字节,存放字节型数据,偏移地址为 0 ,段地址在 ds 中,大白话说也就是将 ds:0 处的一个字节传入到 al 中。
从上面两个例子可以看出,假如要描述一个完整的一个内存单元,应该需要两种信息:即内存单元的地址和内存单元的长度。
比如我们要读取一个 10000H 的数据,你可能会需要下面这段代码。
mov bx,1000H #将 1000H 放入 bx 寄存器中
mov ds,bx #将段寄存器 ds 的值设为 1000H
mov al,[0] #从内存单元 1000:0 处读出1字节内容放入 al 中。
但是表示内存地址的方式除了能够直接指定其内存地址之外,还可以用一种间接寻址的方式,这就是 [bx],它表示的是一种寄存器间接寻址,也是一种偏移地址,同样的,比如我们要读取一个物理地址为 10001H 处的数据,使用 [bx] 这种方式的代码如下
mov ax,1000H
mov ds,ax
mov bx,1
mov ax,[bx]
这样计算机就会寻找段地址为 1000H,偏移地址为 0001H 的数据放入到 ax 中。
它的中文解释就是 把 [bx] 指向的地址中的内容,送入 ax 寄存器中。
比如下面这段代码
mov ax,[bx]
它表示的就是将偏移地址为 bx 的数据,送入到 ax 中,送入的是 2 个字节,也就是字型数据。
又比如下面这段代码
mov al,[bx]
它表示的就是将偏移地址为 bx 的数据,送入到 al 中,送入的内存单元地址是 1 个字节的字节型数据。
[bx] 这种间接寻址的好处就是每次偏移地址不是固定的,这为我们接下来的循环指令奠定了基础。
( ) 表示法
为了更方便的描述后面,我们使用 ()
来表示一个寄存器或者内存单元中的内容。
这里需要注意一下,( ) 内的能够表示的内容一般有三种类型:
- 寄存器名,比如 (ax) 就表示 ax 中的内容,(al) 就表示 al 中的内容。
- 段寄存器名,比如 (ds) 就表示段寄存器 ds 中的内容。
- 内存单元的物理地址,比如 ((ds) * 16 + (bx)),一个 20 位的数据。
我们知道,寄存器存储的数据类型有两种,字型和字节型,字型数据一般用 ax 这类寄存器来存储,字节型数据一般用 ah 、al 这种寄存器来存储。
同样的,( ) 内的数据类型也有两种,字型和字节型。比如 (al)、(bl)、(cl) 这种表示的数据就是字节型,而 (ax)、(bx)、(cx) 表示的数据就是字型。下面是几类 ( ) 的一些用法:
- ax 中的内容为 0020H:(ax) = 0020H;
- 2000:1000 处的内容为 0010H:(21000H) = 0010H;
- mov ax,[2] 则表示为:ax = ((ds * 16) + 2);
- mov [2],ax 则表示为:((ds) * 16 + 2) = (ax);
- add ax,2 表示为:(ax) = (ax) + 2;
- push ax 表示为:(sp) = (sp) – 2, ((ss * 16) + sp) = (ax);
- pop ax 表示为:(ax) = ((ss) * 16) + sp), (sp) = (sp) + 2;
idata
idata 表示的就是立即数,这个概念就更简单了,立即数顾名思义就是直接的数字,也就是常量。比如 mov ax,[0] ,其中的 0 就是立即数,即 idata = 0 ,所以 [立即数] = [idata],所以以后我们通常使用 idata 来表示常量,比如下面几个例子
- mov ax,[idata] 可以表示为 mov ax,[1] mov ax,[2] mov ax,[3]
- mov ax,idata 可以表示为 mov ax,1 mov ax,2 mov ax,3
知道上面是 啥意思了吧?
[BX]
再来啰嗦一下 [bx] 的寻址方式,比如下面代码
mov ax,[bx]
bx 中存放的数据作为一个偏移地址,这里用 EA 表示(没有其他意思,只是单纯地表示偏移地址),段地址在 ds 中,用 SA 表示(同 EA 的解释),将 SA:EA 处的数据送入 ax 中,即 (ax) = ((ds) * 16 + (bx))。
可以将内存单元送入寄存器中,也可以将寄存器的数据送入到内存单元中,如下代码所示
mov [bx],ax
就是将 ax 中的数据送入到 SA:EA 处,即 ((ds) * 16 + (bx)) = (ax)。
为了让大家加深对 [bx] 的认识,我们通过一些汇编指令来认识一下程序的执行过程,代码如下
mov ax,2000H
mov ds,ax
mov bx,1000H
mov ax,[bx]
inc bx
inc bx
mov [bx],ax
inc bx
inc bx
mov [bx],ax
inc bx
mov [bx],al
inc bx
mov [bx],al
初始内存示意图:
下面我们就按照每一行指令来分析一下
首先,mov ax,2000H 就是将 2000 送入 ax 中,mov ds,ax 就是将设置段地址为 2000 H,mov bx,1000H 就是将 1000 送入 bx 中,mov ax,[bx] 就是将 2000:1000 处的地址送入到 ax 中(因为段基址为 2000,偏移地址 dx 为 1000),2000H:1000H 处的指令是 00be,所以 ax = 00BEH ,存储字型数据。
inc bx 就是将寄存器 bx 的值加 1,此处有两条 inc 指令,所以执行完成后 bx = 1002H,此处段基址:偏移地址为 2000H:1002H。
然后下面 (第七行指令)mov [bx],ax 就是将 ax 中的数据送入到 [bx] 中,也就是 1002H 处,指令执行后,2000:1002 单元的内容为 BE,2000:1003 单元的内容为 00,存放字型数据,执行完成后的示意图如下
继续执行第 8、9 行的指令,inc bx ,执行完成后 bx = 1004H,然后执行第 10 行指令 mov [bx],ax ,指令执行前:ds = 2000H,bx = 1004H,mov [bx],ax 相当于是把 ax 中的数据送到 2000:1004 处,指令执行完成后,2000:1004 的单元内容为 BE ,如下示意图所示
接下来执行第 11 行指令,inc bx,执行完成后 bx = 1005H,mov [bx],al 是把 al 中的数据送入内存 2000:1005 处,指令执行完成后,2000:1005 处的单元内容为 BE,如下示意图所示
继续执行指令,第13、14 行指令和 11 、12 行指令一样,它的意思就是将 bx 的值加一之后,将 al 的值送入到指定地址处,执行完成后的 ds = 2000H,bx = 1006H,所以 2000:1006 处的内容是 BE(al 存储的数据),示意图如下
想必大家跟完上面的流程后,应该对 [bx] 这个间接寻址方式有了比较深刻的认识。
下面想个问题,使用汇编编程计算 2 * 2 ,并将结果存储在 ax 寄存器中。
这个思路还是比较简单的,直接将 2 放在 ax 寄存器中,然后执行 ax 的 add 操作就可以了,下面是汇编代码
assume cs:codesg
codesg segment
mov ax,2
add ax,ax
mov ax,4c00h
int 21h
codesg ends
end
上面这段代码中的计算量还比较低,但是如果要让你计算 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 呢,你难道要写 n 个 add ax,ax 吗?
assume cs:codesg
codesg segment
mov ax,2
add ax,ax
add ax,ax
add ax,ax
add ax,ax
。。。
mov ax,4c00h
int 21h
codesg ends
end
这就很繁琐啊,所以不能这么玩,那该怎么搞呢?这里就需要一种能够循环之星 add ax,ax 的指令了,这个指令就是 Loop
。
Loop 指令
Loop 指令能够循环判断是否执行指定的指令,它的执行流程就相当于我们 Java 中的 for 循环。
我们先来使用 Loop 改写一下上面 n 个 2 相乘的代码,然后再讲解一下 Loop 的使用。
assume cs:codesg
codesg segment
mov ax,2
mov cx,8
s: add ax,ax
loop s
mov ax,4c00h
int 21h
codesg ends
end
可以看到,我们使用 8 个 2 相乘的代码被优化的这么简单,这就是 loop 指令的精髓所在。
其实关键代码就是三条指令,即
- mov cx,8
- s: add ax,ax
- loop s
翻译过来的意思就是将 8 放在 cx 中,然后给 add ax,ax 处设置一个标号,然后执行 s 循环。
loop 指令的格式是:loop 标号,CPU 执行 loop 指令的时候,要进行两步操作,第一步:(cx) = (cx) – 1,第二步:判断 cx 的值,不为 0 则转至标号(上面代码是 s)处继续执行指令,如果为 0 则向下执行(上面代码中乡下继续执行就是 mov ax,4c00h)。上面代码中,我们把 8 送入了 cx 中,也就是说,cx 中存储的就是执行次数。
下面我们详细介绍一下上面这段程序的执行过程,从中体会一下 cx 和 loop s 是如何配合实现循环的。
(1) 执行 cx,8 ,设置 cx = 8
(2) 执行 add ax,ax(第 1 次)
(3) 执行 loop s 将 cx 的值 – 1,此时 (cx) = 7,(cx) != 0 ,所以转至 s 处
(4) 执行 add ax,ax(第 2 次)
(5) 执行 loop s 将 cx 的值 – 1,此时 (cx) = 6,(cx) != 0 ,所以转至 s 处
(6) 执行 add ax,ax(第 3 次)
(7) 执行 loop s 将 cx 的值 – 1,此时 (cx) = 5,(cx) != 0 ,所以转至 s 处
(8) 执行 add ax,ax(第 4 次)
(9) 执行 loop s 将 cx 的值 – 1,此时 (cx) = 4,(cx) != 0 ,所以转至 s 处
(10) 执行 add ax,ax(第 5 次)
(11) 执行 loop s 将 cx 的值 – 1,此时 (cx) = 3,(cx) != 0 ,所以转至 s 处
(12) 执行 add ax,ax(第 6 次)
(13) 执行 loop s 将 cx 的值 – 1,此时 (cx) = 2,(cx) != 0 ,所以转至 s 处
(14) 执行 add ax,ax(第 7 次)
(15) 执行 loop s 将 cx 的值 – 1,此时 (cx) = 1,(cx) != 0 ,所以转至 s 处
(16) 执行 add ax,ax(第 8 次)
(15) 执行 loop s 将 cx 的值 – 1,此时 (cx) = 0,(cx) == 0 ,所以转至 s 处
(16) 执行 mov ax,4c00h(循环结束)
从上面这个过程中,我们可以总结处用 cx 和 loop 指令相配合实现循环功能的 3 点注意事项:
- 在 cx 中存放循环次数。
- loop 指令中的标号所标识的地址要在前面
- 要循环执行的程序段,要写在标号和 loop 指令的中间。
所以综上所述,使用 Loop 和 cx 相配合实现的循环功能的结构如下:
mov cx,循环次数
s:
循环执行的程序段
loop s
比如我们想用 Loop 循环计算出 123 * 456 这个值,就可以使用这种方式
assume cs:codesg
codesg segment
mov ax,0
mov cx,456
s:add ax,123
loop s
mov ax,4c00h
int 21h
codesg ends
end
预告
能滑到这里的都是真爱!!
最近我正在汇总一个汇编语言的超清超全脑图,到时候等文章完结之后会免费给大家放出来可供学习,先来个预告版本:
各位小伙伴们敬请期待下~!!!!
声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/103354.html