汇编语言笔记

3 篇文章 0 订阅

第3章寄存器(内存访问)

第2章中,我们主要从CPU如何执行指令的角度讲解了8086CPU的逻辑结构、形成物理地址的方法、相关的寄存器以及一些指令。这一章中,我们从访问内存的角度继续学习几个寄存器。

3.1内存中字的存储

**CPU中,用16位寄存器来存储一个字。高8位存放高位字节,低8位存放低位字节。**在内存中存储时,由于内存单元是字节单元(一个单元存放一个字节),则一个字要用两个地址连续的内存单元来存放,这个字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。比如我们从0地址开始存放20000这种情况如图所示。
在图中,我们用0、1两个内存单元存放数据20000(4E20H)。0、1两个内存单元用来存储一个字,这两个单元可以看作一个起始地址为0的字单元(存放一个字的内存单元,由0、1两个字节单元组成)。对于这个字单元来说,0号单元是低地址单元,1号单元是高地址单元,则字型数据4E20H的低位字节存放在0号单元中,高位字节存放在号单元中。同理,将2、3号单元看作一个字单元,它的起始地址为2.在这个字单元中存放数据18(0012H),则在2号单元中存放低位字节12H,在3号单元中存放高位字节00H
在这里插入图片描述
我们提出字单元的概念:字单元,即存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成。高地址内存单元中存放字型数据的高位字节,低地址内存单元中存放字型数据的低位字节。
2地址单元中存放的字节型数据是12H
2地址字单元中存放的字型数据是多少?
0012H
解析:2地址子单元,即起始地址为2的字单元
它由2号单元和3号单元组成,用这两个单元存储一个字型数据,低位放在2号单元中,高位放在3号单元中

从上面的问题中我们看到,任何两个地址连续的内存单元,N号单元和N+1号单元,可以将它们看成两个内存单元,也可看成一个地址为N的字单元中的高位字节单元和低位字节单元。

3.2DS和[address]

CPU要读写一个内存单元的时候,必须先给出这个内存单元的地址,在8086CPU中,内存地址有段地址和偏移地址组成。
8086CPU中有一个DS寄存器,通常用来存放要访问数据的段地址。比如我们要读取100004单元的内容,可以用如下的程序段进行。
mov bx, 1000H
mov ds, bx
mov al, [0]
上面3条指令将10000H(1000:0)中的数据读到al中。
下面详细说明指令的含义。
mov al, [0]
前面我们使用mov指令,可完成两种传送:
1、将数据直接送入寄存器
2、将一个寄存器中的内容送入另一个寄存器

也可以使用mov指令将一个内存单元中的内容送入一个寄存器中。
从哪一个内存单元送到哪一个寄存器中呢?
在指令中必须指明。寄存器用寄存器名来指明,内存单元则需用内存单元的地址来指明。显然,此时mov指令的格式应该是:mov寄存器名,内存单元地址。
[…]”表示一个内存单元,“[…]”中的0表示内存单元的偏移地址。
我们知道,只有偏移地址是不能定位一个内存单元的,那么内存单元的段地址是多少呢?
执行指令时,8086CPU自动取ds中的数据作为内存单元的段地址。
mov bx, 1000H
mov ds, bx
执行之后,ds的值为1000H
mov ds, 1000H错误
ds是一个段寄存器
mov ds, 1000H非法
只能用一个寄存器中转
8086CPU硬件设计上不支持将数据直接送入段寄存器

将al中的数据送入内存单元10000H中
初步想法
mov [10000H], al
但是 8086CPU又是16位结构,在内部一次传输、处理、暂时存储的地址为16位。
mov bx, 1000H
mov ds, bx
mov [0], al

3.3字的传送

8086CPU是16位结构,在内部一次传输、处理、暂时存储的地址为16
只要在mov 指令中给出16位寄存器就可以进行16位数据的传送了
如:
mov bx, 1000H
mov ds, bx
mov ax, [0] ;1000:0处的字型数据送入ax
mov [0], cx ;cx中的 16位 数据送到 1000:0处

3.4mov、add、sub指令

3.5数据段

对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。我们可以将一组长度为N(N≤64KB)、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。比如用123B0H
123B9H这段内存空间来存放数据,我们就可以认为,123B0H-123B9H这段内存是一个数据段,它的段地址为123BH,长度为10个字节。
如何访问数据段中的数据呢?
将一段内存当做数据段,是我们在编程时的一种安排,可以在具体操作的时候,用ds存放数据段的地址,再根据需要,用相关指令访问数据段中的具体单元。

检测点3.1

(1)在 Debug中,用“d0:01f”查看内存,结果如下
0000:0000 70 80 F0 30 EF 60 30 E2-00 80 80 12 66 20 22 60
0000:0010 62 26 E6 D6 CC 2E 3C 3B-AB BA 00 00 26 06 66 88
下面的程序执行前,AX=0,BX=0,写出每条汇编指令执行完后相关寄存器中的值
mov ax, 1
mov ds, ax
mov ax, [0000] ;此时的地址为10H+0000H; AX是16位,所以读取两个字节
AX=2662H
mov bx, [0001] BX=E626H
mov ax, bx AX=E626H
mov ax, [0000] AX=2662H
mov bx, [0002] BX=D6E6H
add ax, bx AX=FD48H
add ax, [0004] BX=2C14H
mov ax, 0 AX=0
mov al, [0002] AX=00E6H
mov bx, 0 BX=0
mov bl, [000C] BX=0026H
add al, bl AX=000CH

2662H E626H E626H 2662H D6E6H FD48H 2C14H 0 00E6H 0 0026H 000CH

(2)内存中的情况如图所示
各寄存器的初始值:CS=2000H,IP=0,DS=1000H,AX=0,BX=0;
①写出CPU执行的指令序列(用汇编指令写出)
②写出CPU执行每条指令后,CS、IP和相关寄存器中的数值
①再次体会:数据和程序有区别吗?如何确定内存中的信息哪些是数据,哪些是程序?
在这里插入图片描述
1)
mov ax, 6622H
jmp off0:0100
mov ax, 2000H
mov ds, ax
mov ax, [0008]
mov ax, [0002]
2)
CS=2000H IP=0003H AX=6622H
CS=1000H IP=0000H
CS=1000H IP=0003H AX=2000H
CS=1000H IP=0005H DS=2000H
CS=1000H IP=0008H AX=C389H
CS=1000H IP=000BH AX=EA66H
3)
体会:
数据和程序其实没有区别
CS:IP指向的是程序
[address]指向的是以 DS 为段地址,address为偏移地址的内存空间

程序和数据没有区别,本质上都是二进制01码,关键在于CPU如何解读。

3.6栈

在这里,我们对栈的研究仅限于这个角度:栈是一种具有特殊的访问方式的存储空间。它的特殊性就在于,最后进入这个空间的数据,最先出去。

3.7CPU提供的栈机制

现今的CPU中都有栈的设计,8086CPU也不例外。8086CPU提供相关的指令来以栈的方式访问内存空间。这意味着,在基于8086CPU编程的时候,可以将一段内存当作栈来使用。
8086CPU提供入栈和出栈指令,最基本的两个是PUSH(入栈)和POP(出栈)。比如, push ax表示将寄存器ax中的数据送入栈中, pop ax表示从栈顶取出数据送入ax。
8086CPU的入栈和出栈操作都是以字为单位进行的
例子:
在这里插入图片描述
mov ax, 0123H
push ax
mov bx, 2266H
push bx
mov cx, 1122H
push cx
pop ax
pop bx
pop cx
注意,字型数据用两个单元存放,高地址单元存放高8位,低地址单元存放低8位
描述的push和pop指令的执行过程,是否有一些疑惑?总结一下大概是这两个问题:
其一,我们将10000H-1000FH这段内存当作栈来使用,CPU执行push和pop指令时,将对这段空间按照栈的后进先出的规则进行访问。但是,一个重要的问题是,CPU如何知道10000H-1000FH这段空间被当作栈来使用?
其二, push ax等入栈指令执行时,要将寄存器中的内容放入当前栈顶单元的上方成为新的栈顶元素:pop ax等指令执行时,要从栈顶单元中取出数据,送入寄存器中。显然,push、pop在执行的时候,必须知道哪个单元是栈顶单元,可是,如何知道呢?

这不禁让我们想起另外一个讨论过的问题,就是,
CPU如何知道当前要执行的指令所在的位置?
我们现在知道答案,那就是CS、IP中存放着当前指令的段地址和偏移地址。现在的问题是:CPU如何知道栈顶的位置?显然,也应该有相应的寄存器来存放栈顶的地址,8086CPU中,有两个寄存器,段寄存器SS和寄存器SP,栈顶的段地址存放在SS中,偏移地址存放在SP中。任意时刻,SS:SP指向栈顶元素。push指令和pop指令执行时,CPU从SS和SP中得到栈顶的地址。

push ax的执行,由以下两步完成
(1)SP=SP2,SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶
(2)将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶

如果将100004-1000FH这段空间当作栈,初始状态栈是空的,此时,SS=1000H,SP=?
在这里插入图片描述
AX=2266H,高地址内存单元存放字的高位字节,低地址内存单元存放字的低位字节

将10000H-1000FH这段空间当作栈段,SS=1000H,栈空间大小为16字节,栈最底部的字单元地址为1000:000E.任意时刻,SS:SP指向栈顶,当栈中只有一个元素的时候,SS=1000H,SP=000EH。栈为空,就相当于栈中唯一的元素出栈,出栈后, SP=SP+2,SP原来为000EH,加2后SP=10H,所以,当栈为空的时候,SS=1000H, SP=10H

换一个角度看,任意时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地址+2,栈最底部字单元的地址为1000:000E,所以栈空时,SP=0010H

3.8栈顶超界的问题

我们现在知道,8086PU用SS和SP指示栈顶的地址,并提供push和pop指令实现入栈和出栈。
但是,还有一个问题需要讨论,就是SS和SP只是记录了栈顶的地址,依靠SS和SP可以保证在入栈和出栈时找到栈顶。可是,如何能够保证在入栈、出栈时,栈顶不会超出栈空间?

栈顶超界是危险的,因为我们既然将一段空间安排为栈,那么在栈空间之外的空间里很可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己程序中的,也可能是别的程序中的(毕竟一个计算机系统中并不是只有我们自己的程序在运行)。但是由于我们在入栈出栈时的不小心,而将这些数据、代码意外地改写,将会引发一连串的错误。
我们当然希望CPU可以帮我们解决这个问题,比如说在CPU中有记录栈顶上限和栈底的寄存器,我们可以通过填写这些寄存器来指定栈空间的范围,然后,CPU在执行push指令的时候靠检测栈顶上限寄存器、在执行pop指令的时候靠检测栈底寄存器保证不会超界。
不过,对于8086CPU,这只是我们的一个设想(我们当然可以这样设想,如果CPU是我们设计的话,这也就不仅仅是一个设想)。实际的情况是,8086CPU中并没有这样的寄存器。
8086CPU不保证我们对栈的操作不会超界

3.9push、pop指令

指令格式:
push 寄存器
pop 寄存器

push 段寄存器
pop 段寄存器

push 内存单元;将一个内存单元处的字入栈(注意:栈操作以字为单位)
pop 内存单元;出栈,用一个内存单元接收出栈的数据

指令执行时,CPU要知道内存单元的地址,可以在push、pop指令中只给出内存单元的偏移地址,段地址在指令执行时,CPU从ds中取得。
编程,将10000H-1000FH这段空间当作栈,初始状态栈是空的,将AX、BX、DS中的数据入栈
mov ax, 1000H
mov ss, ax ;设置栈顶的段地址,SS=1000H
;不能直接向段寄存器SS中送入数据,所以用ax中转
mov sp, 0010H; 设置栈顶的偏移地址
;编程中自己注意栈的大小
push ax
push bx
push ds

栈的综述
(1)8086CPU提供了栈操作机制,方案如下。
(2)在SS、SP中存放栈顶的段地址和偏移地址:
提供入栈和出栈指令,它们根据SS:SP指示的地址,按照栈的方式访间内存单元
(2)push指令的执行步骤:SP=SP-2:②向SS:SP指向的字单元中送入数据
(3)pop指令的执行步骤:①从SS:SP指向的字单元中读取数据:②SP=SP+2
(4)任意时刻,SS:SP指向栈顶元素
(5)8086CPU只记录栈顶,栈空间的大小我们要自己管理
(6)用栈来暂存以后需要恢复的寄存器的内容时,寄存器出栈的顺序要和入栈的顺序相反
(7)push、pop实质上是一种内存传送指令,注意它们的灵活应用

栈是一种非常重要的机制,一定要深入理解,灵活掌握

3.10栈段

对于8086CPU机,在编程时,可以根据需要,将一组内存单元定义为一个段。

我们可以将长度为N(N<=64KB)的一组地址连续、起始地址为16的倍数的内存单元,当做栈空间来用,从而定义了一个栈段。
(段地址×16定位段的起始地址,所以一个段的起始地址一定是16的倍数;偏移地址为16位,16位的寻址能力为64KB,所以一个段的长度最大为64KB)
将一段内存当作栈段,仅仅是我们在编程时的一种安排,CPU并不会由于这种安排,就在执行push、pop等栈操作指令时自动地将我们定义的栈段当作栈空间来访问。
如何使得如push、pop等栈操作指令访问我们定义的栈段呢?前面我们已经讨论过,就是要将SS:SP指向我们定义的栈段。
在这里插入图片描述
思考:
10000H~1FFFFH看作栈段,段地址:1000H,大小64KB
栈最底部的地址为 1000:FFFF
FFFFH+2H=10001H
截取低4位,为0001H
如果栈是空的
SP中的值为0001H

栈的基本操作单位是字
所以要看最底部的字单元地址

如果将10000H-1FFFH这段空间当作栈段,SS=1000H,栈空间为64KB,栈最底部的字单元地址为1000:FFFE。任意时刻,SSSP指向栈顶单元,当栈中只有一个元素的时候,SS=1000H,SP= FFFEH。栈为空,就相当于栈中唯一的元素出栈,出栈后, SP:SP+2

段的综述:
我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。这完全是我们自己的安排。

我们可以用一个段存放数据,将它定义为“数据段”
我们可以用一个段存放代码,将它定义为“代码段”:
我们可以用一个段当作栈,将它定义为“栈段”。

我们可以这样安排,但若要让CPU按照我们的安排来访问这些段,就要:
对于数据段,将它的段地址放在DS中,
用mov、add、sab等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当作数据来访问。
对于代码段,将它的段地址放在CS中,
将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令。
对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中,
这样CPU在需要进行栈操的时候,比如执行push、pop指令等,就将我们定义的栈段当作栈空间来用

可见,不管我们如何安排,CPU将内存中的某段内容当作代码,是因CS:SP指向了那里:CPU将某段内存当作栈,是因为SS:SP指向了那里。我们一定要清楚,什么是我们的安排,以及如何让CPU按我们的安排行事。要非常清楚CPU的工作机理,才能在控制CPU按照我们的安排运行的时候做到游刃有余

比如我们将 10000H~1001FH安排为代码段,并在里面存储如下代码:
mov ax, 1000H
mov ss, ax
mov sp, 0020H;初始化栈顶
mov ax, cs
mov ds, ax;设置数据段地址
mov ax, [0]
add ax, [2]
mov bx, [4]
add bx, [6]
push ax
push bx
pop ax
pop bx

在这段代码中,我们又将10000H~1001FH安排为栈段和数据段。
10000H~1001FH这段内存,即使代码段,又是栈段和数据段

一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也不是。关键在于CPU中寄存器的设置,即CS、IP、SS、SP、DS的指向。

检测点3.2

在这里插入图片描述
在这里插入图片描述
mov ax, 2000H
mov ss, ax
mov sp, 0010H

在这里插入图片描述
mov ax, 1000H
mov ss, ax
// mov sp, 0010H 错误
mov sp, 0000H

pop 后 sp+2
pop [E] 首先是将复制到 2000E 和2000F
如果按照逆序复制,那么应该将 10000H和10001H中的字复制过去
如果使用 mov sp, 0010H
那么被复制过去的就是 10010H和10011H中的字

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值