【8086汇编】2.访问寄存器与内存

8086

一、寄存器及数据存储

寄存器是CPU内部的信息存储单元,在8086CPU中有14个寄存器:

  • 通用寄存器:AX、BX、CX、DX
  • 变址寄存器:SI、DI
  • 指针寄存器:SP、BP
  • 指令指针寄存器:IP
  • 段寄存器:CS、SS、DS、ES
  • 标志寄存器:PSW

共性:8086CPU中所有的寄存器都是16位的,可以存放两个字节

1.1 通用寄存器(以AX为例)

1.1.1 对数据的存储

一个16位寄存器存储一个16位的数据,能存储的数据的最大值就是寄存器所有位都取1的时候,此时值为2 ^ 16 - 1,即65535D;转成十六进制也就是FFFFH。如下图所示:

AX

例:在AX中存储18D,十六进制为12H,二进制为10010B,如下图所示


AX中存储20000D,十六进制为4E20H,二进制为0100111000100000B,如下图所示!!]!

1.1.2 保证兼容性

8086CPU的上一代CPU8088的寄存器都是8位的,为保证8086的程序在原有8088的平台上能兼容,在设计16位通用寄存器时,会保证通用寄存器均可以分为两个独立的8位寄存器使用。

AX可以分为AHAL,如下图所示

同样的:

  • BX可以分为BHBL
  • CX可以分为CHCL
  • DX可以分为DHDL

在描述存储的数据时,用十六进制可以直观地看出高八位和低八位的组成,如下表所示:

寄存器寄存器中的数据所表示的值
AX010011100010000020000(4E20H)
AH0100111078(4EH)
AL0010000032(20H)

1.2 “字”在寄存器中的存储

808616位的CPU,其字长(Word size)16bit,一个字是由16位构成的,占2字节(1字节永远是8bitNCPU的字长为N bit,字由N位构成)

一个字(Word)可以存在一个16位的寄存器中,这个字的高位字节存在这个寄存器的高8位寄存器,这个字的低位字节存在这个寄存器的低8位寄存器

二、mov和add指令

2.1 指令简述

  • mov 赋值操作
  • add 相加运算

这两个指令很简单,如下表所示(汇编指令不区分大小写):

在这里插入图片描述

2.2 溢出问题

8066里面,溢出的位是会被寄存器丢失的(只是寄存器读不到,CPU并没有真正丢弃进位值,这个在后面再简述,下面的丢失同理),如执行下列程序段:

    MOV AX, 0
    MOV BX, 0
    MOV AX, 8226H
    ADD BX, AX
    ADD AX, BX

执行ADD AX, BXAX寄存器的值是8226H + 8226H = 1044CH,但是由于AX只有16位,所以最高位的1是会被丢失的,所以AX寄存器的最终结果是044CH

如前面所说,通用寄存器的高位和低位是可以独立操作的,在对高八位和第八位独立操作时,如果遇到溢出也是会丢失进位值,低位寄存器不会向高位寄存器进位,如下列程序段:

    MOV AX, 0
    MOV AL, 82H
    MOV BH, 82H
    ADD AL, BH

由于82H + 82H = 104H,但是AL只有8位,并不会向AH进位,所以最终AL中的数据时04HAX中的数据也是04H而不是104H

2.3 实战

要求:只用mov和add指令,用不超过四行的代码求2的4次方的值

我们可以先列个表

DECHEXBIN
22H0010B
44H0100B
88H1000B
1610H00010000B

我们可以发现每乘个2,就相当于二进制数向高位位移一次(进位),我们计算2的4次方只需要不断相加就行了,如下参考代码

    MOV AX, 02H    ;AX = 00000010B
    ADD AX, AX     ;AX = 00000100B
    ADD AX, AX     ;AX = 00001000B
    ADD AX, AX     ;AX = 00010000B

三、物理地址与分段管理

CPU在访问内存单元的时候需要给出内存单元的地址,所有的内存单元构成的存储空间是一个一维的线性空间,每一个内存单元在这个空间中都有唯一的地址,这个唯一的地址叫做物理地址,如下图所示

3.1 两个事实引出的矛盾

  1. 8086有20位地址总线,可传送20位地址,寻址能力为1M
  2. 8086是16位的CPU,运算器一次最多可以处理16位的数据,寄存器的最大宽度为16位。在8086内部处理、传输、暂存的地址也是16位,寻址能力也只有64KB

矛盾:8086如何处理在寻址空间上的这个矛盾?

3.2 8086给出物理地址的方法

8086CPU的地址加法器中将两个16位的地址(段地址、偏移地址)合成一个20位的物理地址,相关部件的逻辑结构如下图所示:

在这里插入图片描述

在地址加法器中做的运算为:
物理地址 = 段地址 * 16 + 偏移地址
如下图所示:

在这里插入图片描述

例:段地址为1230H,偏移地址为00C8H
那么其物理地址为12300H + 00C8H = 123C8H

  • M进制数向高位移N位(低位自动补零)代表这个数乘M的N次幂

3.3 段地址与偏移地址不唯一

当8086CPU访问一个内存单元中的地址时,其段地址与偏移地址并不是唯一的。也就是说CPU可以用不同的段地址和偏移地址形成同一个物理地址。如下所示,对于同一个物理地址21F60H

物理地址段地址偏移地址
21F60H2000H1F60H
21F60H2100H0F60H
21F60H21F0H0060H
21F60H21F6H0000H
21F60H1F00H2F60H

3.4 用分段的方式管理内存

内存并没有分段,段的划分来自CPU,我们在编程时可以根据需要,将若干地址连续的内存单元看做一个段,用段地址*16定位段的起始地址(基础地址),用偏移地址定位段中的内存单元。注意以下两点:

  1. 段地址*16必然是16的倍数,所以一个段的起始地址也一定是16的倍数
  2. 偏移地址为16位,变化范围为0~FFFFH,16位地址的寻址能力为64KB,所以一个段的最大长度为64KB
  • 例:给定段地址2000H,则用偏移地址寻址的访问时20000H~2FFFFH,共64KB

3.5 对内存单元的描述

在8086PC机中,一般不会直接取说明内存单元的物理地址,比如说数据A在21F60H内存单元中通常用以下两种方式描述:

  1. 数据A存在内存2000:1F60单元中
  2. 数据存在内存的2000段中的1F60单元中

3.6 段寄存器

8086CPU在访问内存时需要由相关部件提供内存单元的段地址和偏移地址,从而送入地址加法器中合成物理地址。其中8086CPU中有4个专门的段寄存器存放段地址,分别是:

  1. CS:代码段寄存器
  2. DS:数据段寄存器
  3. SS:栈段寄存器
  4. ES:附加段寄存器

四、Debug的使用

4.1 用R命令查看、改变CPU寄存器的内容

  • R (查看寄存器内容)
  • R <寄存器名> (改变指定寄存器的内容)

如,查看寄存器内容并将AX寄存器中的值改为1234:
ax1234

4.2 用D命令查看内存中的内容

  • D (列出预设地址内存处的128字节的内容)
  • D <段地址:偏移地址> (列出内存中指定地址处的内容)
  • D <段地址:偏移地址 结尾偏移地址> (列出内存中指定地址范围中的内容)

如下图所示:
在这里插入图片描述

4.3 用E命令改变内存中的内容

  • E <段地址:偏移地址 data1 data2 ...> (依次写入数据1、数据2…(十六进制))
  • E <段地址:偏移地址> (逐个询问式修改)
  • <空格>:接受并继续修改下一个
  • <回车>:结束修改

如下图所示:
在这里插入图片描述

4.4 用U命令将内存中的机器指令翻译成汇编指令

  • U <段地址:偏移地址> (查看汇编代码)

如先将下述机器码写入到从地址2000:0000开始的一段内存中,最后用U指令查看

    B8 23 01
    BB 03 00
    89 D8
    01 D8

U
我们可以看出其对应的汇编指令为:

    MOV AX, 0123H
    M0V BX, 0003H
    MOV AX, BX
    ADD AX, BX

4.5 用A命令以汇编指令的格式在内存中写入机器指令

  • A <段地址:偏移地址> (写入汇编指令)

还是写入上面的那段指令,我们可以看到,机器码和汇编指令是一一对应的,如图所示:
在这里插入图片描述

4.6 用T命令执行机器指令

  • T (单步执行CS:IP处的指令)

如图所示

在这里插入图片描述

每执行依据之后在最后面都会注明下一段内存中的程序,执行结果在寄存器中一目了然。

4.7 用Q命令退出Debug

五、CS与代码段及JMP指令

5.1 CS、IP简述

  • CS:代码段寄存器
  • IP:指令指针寄存器

CS:IP:CPU将内存中CS:IP指向的内容当做指令执行

5.2 8086PC工作过程

  1. 从CS:IP指向的内存单元中读取指令,读取的指令进入指令缓冲器
  2. IP = IP + 所读取的指令的长度,从而指向下一条指令
  3. 执行指令,转到步骤1,重复执行

5.3 jmp指令

8086CPU执行何处的指令,取决于CS:IP的值,我们可以通过改变CS:IP的内容来控制CPU要执行的目标。下面是一些看似可行的改变CS:IP的方法

  1. Debug中的R命令可以改变寄存器的值R SC,R IP.但是Debug是调试手段,并不是程序方式
  2. 用MOV指令?先看测试:

我们可以发现以下语句都是ERROR

    MOV CS,2000
    MOV IP,0

这是因为8086不提供修改CS和IP的指令,下面这句没有报ERROR:

    MOV AX, 2000
    MOV CS, AX

这在语法上确实是可行的,但是一般不会这样使用,我们更希望有更高级的机制去完成这一特定的功能。到MOV IP,AX又卡壳了,因为IP是自动增长的。那到底如何控制CPU执行的目标呢?其实有一个jmp转移指令

  • JMP <段地址:偏移地址> (用指令中给出的段地址修改CS,用偏移地址修改IP)
  • JMP <某一合法寄存器> (仅修改IP的内容)

5.4 问题分析:CPU运行的流程

在这里插入图片描述

如上图,从20000H开始,执行的序列依次是

    MOV AX, 6622H
    JMP 1000:3
    MOV AX, 0000
    MOV BX, AX
    JMP BX
    MOV AX, 0123H
    MOV AX, 0000
;接下来就一直在10000H到10009H中死循环

以下我们到Debug里面去跟踪测试一下,先将机器码输入,如图:
在这里插入图片描述
从20000H处开始运行:如图所示,可以很清楚得看到CS和IP的跳变
在这里插入图片描述

六、内存中字的存储

对8086CPU,16位作为一个字。前面我们知道,字在16位寄存器中的存储是高8位放在高字节,低8位放在低字节。但是内存是连续分布的,16位的字在内存中需要两个连续的字节来储存。

6.1 小端储存

Intel公司采用的是小端模式来储存,即低位字节存在低地址单元,高位字节存放在高地址单元

假设要将4E20H存放在0、1两个单元,则4EH是存放在单元1中,20H存放在单元0中;
将0012H存放在2、3两个单元,则将00H存放在单元3中,12H存放在单元2中,如下图所示:

6.2 8086中的字单元

由两个地址连续的内存单元组成,存放一个字型数据(16位)。在一个字单元中,低地址单元存放低位字节,高地址单元存放高位字节。

如6.1中的图:

  • 在起始地址为0的单元中,存放的是4E20H
  • 在起始地址为2的单元中,存放的是0012H

注意区分字节型数据和字型数据

  • 0地址单元中存放的字节型数据是20H
  • 0地址单元中存放的字型数据是4E20H

七、DS与数据段

7.1 用DS和[address]实现字的传送

CPU要读取内存单元的时候,必须先给出这个内存单元的地址,在8086PC中,内存地址由段地址和偏移地址组成,在写汇编指令的时候,用DS寄存器存放要访问的数据的段地址,偏移地址用[address]的形式直接给出,如下所示:

7.1.1 读取操作

将10000H(1000:0)中的数据读到AL中:

    MOV BX, 1000H
    MOV DS, BX
    MOV AL, [0]
7.1.2 写入操作

将AL中的数据写到10000H中

    MOV BX, 1000H
    MOV DS, BX
    MOV [0], AL
7.1.3 注意
  • 8086CPU不支持直接将数据直接送入段寄存器(硬件设计的问题),所以都是先将数据写入通用寄存器,再通过通用寄存器写入到段寄存器中
  • MOV指令访问内存单元时可以只给出单元的偏移地址,此时段地址默认在DS寄存器中
7.1.4 字的传送

8086CPU可以一次传动一个字(16位的数据)

    MOV BX, 1000H
    MOV DS, BX
    MOV AX, [0] ;1000:0处的字型数据送入AX
    MOV [0], CX ;CX中的16位数据送到10000H处

7.2 DS与数据段

在8086PC机中,可以根据需要将一组内存单元定义为一个段,将哪段内存当做数据段、段地址如何定,都是由程序员在编程时自己安排的:

将一组长度为N(N≤64K)、地址连续、起始地址为16的倍数的内存单元当做专门储存数据的内存空间,从而就定义了一个数据段

如下所示,将123B0H~123BAH的内存单元定义为数据段:

    MOV AX, 123BH
    MOV DS, AX

累加该数据段中的前三个单元的数据

   MOV AL, 0
   MOV AL, [0]
   MOV AL, [1]
   MOV AL, [2]

累加该数据段中的前三个字型数据

    MOV AX, 0
    MOV AX, [0]
    MOV AX, [2]
    MOV AX, [4]

7.3 小结

  • MOV、ADD/SUB 是具有两个操作对象的指令,访问内存中的数据段
  • JMP是具有一个操作对象的指令,对应内存中的代码段

八、栈操作的实现

8.1 CPU提供的栈机制

stack1

现今的CPU都有栈的设计,8086CPU提供相关的指令,支持用栈的方式访问内存空间,基于8086的CPU编程,可以将一段内存当做栈来实现:

  • PUSH :入栈/压栈
  • POP : 出栈/弹栈

例:

    PUSH AX ;将AX中的数据送入栈中
    POP AX  ;从栈顶取出数据送入AX中

需要注意的是,这里都是以字为单位对栈进行操作

8.2 与栈相关的寄存器

  • 栈段寄存器SS: 存放栈顶的段地址
  • 栈顶指针寄存器SP: 存放栈顶的偏移地址

任意时刻,SS:SP都是指向栈顶元素,SP决定了栈的大小,如以下定义:

    MOV AX, 1000H
    MOV SS, AX
    MOV SP, 000FH

则栈为物理地址为10000H~1000FH的一段内存空间,如下所示:
stack3

8.3 栈的操作示例

    MOV AX, 1000H
    MOV SS, AX
    MOV SP, 0010H
    MOV AX, 001AH
    MOV BX, 001BH
    PUSH AX
    PUSH BX
    POP AX
    POP BX

代码执行时寄存器及栈的状态如下图所示
在这里插入图片描述
我们可以发现,通过栈的机制,AX和BX的数值发生的交换

8.4 PUSH和POP的执行过程

PUSH AX
  1. SP = SP - 2
  2. AX中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶
POP AX
  1. SS:SP指向的内存单元处的数据送入AX
  2. SP = SP + 2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶

PUSH和POP实质上就是一种内存传送指令,可以在寄存器和内存之间传递数据,与MOV指令不同的是,PUSH和POP指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的

8.5 爆栈

出现以下两种情况会发生栈顶超界的问题:

  1. 栈满的时候再使用PUSH指令入栈
  2. 栈空的时候再使用POP指令出栈

对于8086这种低端的CPU,只知道栈顶的位置,并不知道程序安排的栈空间的大小,所以不会对程序中是否爆栈做出检查。程序员在编程的时候要根据可能用到的最大栈空间来安排栈的大小。

九、段的总结

9.1 三种段

数据段
  • 将段地址放在DS
  • MOV/ADD/SUB等访问内存单元的指令是,CPU将我们定义的数据段中的内容当做数据来访问
  • 无法明确得定义出数据段的大小
代码段
  • 将段地址放在CS中,将段中的第一条指令的偏移地址放在IP
  • CPU将代码段的内容当在指令来执行
栈段
  • 将段地址放在SS中,将栈顶单元的偏移地址放在SP中
  • CPU在序号执行栈操作(PUSH/POP)时,将定义的栈段当做栈空间来使用
  • 栈一旦定义,其大小就确定了

9.2 数据段和栈段可以在一起

不在一起

在这里插入图片描述

在一起

在这里插入图片描述

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值