8086CPU各寄存器及其简介

8086 CPU 中寄存器总共为 14 个,且均为 16 位 。

即 AX,BX,CX,DX,SP,BP,SI,DI,IP,FLAG,CS,DS,SS,ES 共 14 个。

而这 14 个寄存器按照一定方式又分为了通用寄存器,控制寄存器和段寄存器。

通用寄存器:

AX,BX,CX,DX 称作为数据寄存器:

AX (Accumulator):累加寄存器,也称之为累加器;

BX (Base):基地址寄存器;

CX (Count):计数器寄存器;

DX (Data):数据寄存器;

SP 和 BP 又称作为指针寄存器:

SP (Stack Pointer):堆栈指针寄存器;

BP (Base Pointer):基指针寄存器;

SI 和 DI 又称作为变址寄存器:

SI (Source Index):源变址寄存器;

DI (Destination Index):目的变址寄存器;

控制寄存器:

IP (Instruction Pointer):指令指针寄存器;

FLAG:标志寄存器;

段寄存器:

CS (Code Segment):代码段寄存器;

DS (Data Segment):数据段寄存器;

SS (Stack Segment):堆栈段寄存器;

ES (Extra Segment):附加段寄存器;

通用寄存器

从上面可以知道,在 8086 CPU 中,通用寄存器有 8 个,分别是 AX,BX,CX,DX,SP,BP,SI,DI ,

至于为什么给它们取名做通用寄存器,那是因为,这些个寄存器每一个都有自己专门的用途,

比如 CX 作为计数寄存器,则是在使用 LOOP 指令循环时用来指定循环次数的寄存器,

如果它们每一个都只有一个专用的作用,那就它们只能称之为专用寄存器了,

正是因为这些个寄存器还可以用来传送数据和暂存数据,所以才称它们为通用寄存器 。

下面就按顺序来一一介绍这几个通用寄存器了:

数据寄存器(AX,BX,CX,DX):

数据寄存器有 AX,BX,CX,DX 四个组成,

由于在 8086 之前的 CPU 为 8 位 CPU,所以为了兼容以前的 8 位程序,

在 8086 CPU 中,每一个数据寄存器都可以当做两个单独的寄存器来使用,

由此,每一个 16 位寄存器就可以当做 2 个独立的 8 位寄存器来使用了 。

AX 寄存器可以分为两个独立的 8 位的 AH 和 AL 寄存器;

BX 寄存器可以分为两个独立的 8 位的 BH 和 BL 寄存器;

CX 寄存器可以分为两个独立的 8 位的 CH 和 CL 寄存器;

DX 寄存器可以分为两个独立的 8 位的 DH 和 DL 寄存器;

除了上面 4 个数据寄存器以外,其他寄存器均不可以分为两个独立的 8 位寄存器 ;

注意在上面标志中的“独立”二字,这两个字表明 AH 和 AL 作为 8 位寄存器使用时,

可以看做它们是互不相关的,也就是看做两个完全没有联系的寄存器 X 和 Y 即可,

比如指令 MOV AH , 12H ,CPU 在执行时根本就不会知道 AL 中是什么鬼东西,因为它只认识 AH 。

下面给出一幅 16 位数据寄存器的结构图:

表示 16 位 寄存器 AX 可以表示成两个 8 位寄存器,

其中 AH 表示高位的 8 位寄存器,AL 表示低位的 8 位寄存器 。

image-20210421111213179.png

AX寄存器

如上所说,AX 的另外一个名字叫做累加寄存器或者简称为累加器,其可以分为 2 个独立的 8 位寄存器 AH 和 AL;

在写汇编程序时,AX 寄存器可以说是使用率最高的寄存器(不过,总共才那么 14 个寄存器,哪一个不经常使用咯?),

既然 AX 是数据寄存器的话,那么理所当然,其可以用来存放普通的数据,由于其是 16 位寄存器,

自然也就可以存放 16 位数据,但是因为其又可以分为 2 个独立的 8 位寄存器 AH 和 AL ,

所以,在 AH 和 AL 中又可以独立的存放 2 个 8 位的数据,

可以有以下代码(即将 AX 当做普通的寄存器使用,即可以用来暂存数据):

MOV AX,1234H           ;向寄存器 AX 传入数据 1234H
MOV AH,56H           ;向寄存器 AX 的高 8 位寄存器 AH 中传入数据 56H
MOV AL,78H        ;向寄存器 AX 的低 8 位寄存器 AL 中传入数据 78H

3条语句的执行过程如下:

image-20210421111546761.png

而既然 AX 又被称作为累加器,自然其还有一点点特殊的地方的:

AX 寄存器还具有的特殊用途是在使用 DIV 和 MUL 指令时使用,

DIV 在 8086 CPU 中是除法指令,而在使用除法的时候有两种情况,即除数可以是 8 位或者是 16 位的,

而且除数可以存放在寄存器中或者是内存单元中,而至于被除数的话,自然,应该由 AX 来代替了,

当除数是 8 位时,被除数一定会是 16 位的,并且默认是放在 AX 寄存器中,

而当除数是 16 位时,被除数一定是 32 位的,因为 AX 是 16 位寄存器,自然,放不下 32 位的被除数,

所以,在这里还需要使用另一个 16 位寄存器 DX ,

其中 DX 存放 32 位的被除数的高 16 位,而 AX 则存放 32 位的被除数的低 16 位,

同时,AX 的作用还不仅仅是用来保存被除数的,当除法指令执行完成以后,

如果除数是 8 位的,则在 AL 中会保存此次除法操作的商,而在 AH 中则会保存此次除法操作的余数,

当然,如果除数是 16 位的话,则 AX 中会保存本次除法操作的商,而 DX 则保存本次除法操作的余数。

上面介绍的是 AX 寄存器在除法操作中的应用,下面还需要介绍一下 AX 在乘法操作中的应用,

当使用 MUL 做乘法运算时,两个相乘的数要么都是 8 位,要么都是 16 位,

如果两个相乘的数都是 8 位的话,则一个默认是放在 AL 中,

而另一个 8 位的乘数则位于其他的寄存器或者说是内存字节单元中,

而如果两个相乘的数都是 16 位的话,则一个默认存放在 AX 中,

另一个 16 位的则是位于 16 的寄存器中或者是某个内存字单元中。

同时,当 MUL 指令执行完毕后,如果是 8 位的乘法运算,则默认乘法运算的结果是保存在 AX 中,

而如果是 16 位的乘法运算的话,则默认乘法运算的结果有 32 位,

其中,高位默认保存在 DX 中,而低位则默认保存在 AX 中。

AX 寄存器在 DIV 指令中的使用:

MOV DX,0H        ;设置 32 位被除数的高 16 位为 0H
MOV AX,8H        ;设置 32 位被除数的低 16 位为 8H
MOV BX,2H        ;设置 16 位除数为 2H
DIV BX               ;执行计算

4 条语句的执行过程如下:

image-20210421111816522.png

AX 寄存器在 MUL 指令中的使用:

MOV AX,800H        ;设置 16 位乘数为 800H
MOV BX,100H        ;设置 16 位乘数为 100H
MOV DX,0H        ;清空用来保存乘法结果的高 16 位    
MUL BX               ;执行计算

4 条语句的执行过程如下:

image-20210421111939619.png

BX 寄存器:

首先可以明确的是,BX 作为数据寄存器,表明其是可以暂存一般的数据的,

即在某种程度上,它和 AX 可以暂存一般性数据的功能是一样的,

其同样为了适应以前的 8 位 CPU ,而可以将 BX 当做两个独立的 8 位寄存器使用,即有 BH 和 BL,

除了暂存一般性数据的功能外,BX 作为通用寄存器的一种,BX 主要还是用于其专属功能 – 寻址(寻址物理内存地址)上,

BX 寄存器中存放的数据一般是用来作为偏移地址使用的,何为偏移地址呢?

既然是偏移地址的话,当然得有一个基地址了,而这个基地址其实就是段地址,这里就涉及到了段寄存器,

当然,在介绍 BX 寄存器的时候,我不会去介绍段寄存器,上面提到 BX 的主要功能是用在寻址上,

那么,其是如何寻址的呢?

对于寻址这个话题,我会在我的下一篇博文中作出详细的介绍,

而这里,我只点一下,在 8086 CPU 中,CPU 是根据 <段地址:偏移地址> 来进行寻址操作的,

而 BX 中存放的数据表示的是偏移地址的话,自然,便可以通过 <段地址:[BX]> 的方式来完成寻址操作了。
为了介绍 BX 在寻址当中的作用,下面我给出一副示意图:

image-20210421145533560.png

上面的示意图表示:可以令 BX = 2,然后通过 DS : [BX] 来访问到内存中段地址为 DS,且偏移量为 2 的内存单元了。

上面介绍的这种寻址方式是 BX 在寻址中最最简单的应用了,而对于稍微复杂的寻址方式,

还可以依赖于 SI,DI,BP 等寄存器来一起完成

BX 寄存器在寻址中的使用:

MOV BX,5H
MOV AH,11H
MOV AH,[BX]        ;设置 AX 的值为偏移地址为 BX 中的值时所代表的内存单元

3 条语句的执行过程如下:

image-20210421145640105.png

从上图可以看出,在偏移地址为 5 时的内存单元中的数据位 BBH,

image-20210421145739365.png

而从这幅图上面就可以看出,确实通过 [BX] 找到了偏移地址为 5 处的内存单元,并且将内存单元移入了 AH 中。

CX 寄存器:

CX 寄存器作为数据寄存器的一种呢,其同样具有和 AX,BX 一样的特点,即可以暂存一般性的数据,

同时还可以将其当做两个独立的 8 位寄存器使用,即有 CH 和 CL 两个 8 位寄存器,

当然,CX 也是有其专门的用途的,CX 中的 C 被翻译为 Counting 也就是计数器的功能,

当在汇编指令中使用循环 LOOP 指令时,可以通过 CX 来指定需要循环的次数,

而 CPU 在每一次执行 LOOP 指令的时候,都会做两件事:

一件就是令 CX = CX – 1,即令 CX 计数器自动减去 1;

还有一件就是判断 CX 中的值,如果 CX 中的值为 0 则会跳出循环,而继续执行循环下面的指令,

当然如果 CX 中的值不为 0 ,则会继续执行循环中所指定的指令 。

CX 寄存器在循环中的使用(输出 5 个白底蓝字的 A):

MOV AX,0B800H
MOV DS,AX        ;使用 80x25 彩色字符模式,内存地址 0xB8000 - 0xBFFFFF
MOV BX,0        ;从 0xB8000 开始
MOV CX,5H        ;循环 5 次
MOV DX,41H        ;A 的16 进制为 41H
MOV AX,01110001B    ;显示白底蓝字
s:  MOV [BX],DX    ;显示 ASCII 字符
    ADD BX,1
    MOV [BX],AX    ;设置字符显示属性
    ADD BX,1
LOOP s

语句的执行过程如下:

image-20210421150013677.png

DX 寄存器:

DX 寄存器作为数据寄存器的一种,同样具有和 AX,BX,CX 一样的特点,即可以暂存一般性的数据,

同时还可以将其当做两个独立的 8 位寄存器使用,极有 DH 和 DL,

同时,DX 作为一个通用寄存器的话,自然其还有其他的用途,而关于 DX 在其他方面的用途,

其实在前面介绍 AX 寄存器时便已经有所介绍了,

即当在使用 DIV 指令进行除法运算时,如果除数为 16 位时,被除数将会是 32 位,而被除数的高 16 位就是存放在 DX 中,

而且执行完 DIV 指令后,本次除法运算所产生的余数将会保存在 DX 中,

同时,在执行 MUL 指令时,如果两个相乘的数都是 16 位的话,

那么相乘后产生的结果显然需要 32 位来保存,而这 32 位的结果的高 16 位就是存放在 DX 寄存器中 。

DX 寄存器在 DIV 指令中的使用(即 2293812 / 256 = 8960 余数为 52):
MOV DX,0023H    ;32 位被除数的高 16 位
MOV AX,0034H    ;32 位被除数的低 16 位
MOV BX,100H    ;16 的除数
DIV BX  

语句的执行过程如下:

image-20210421150129259.png

可以看到在语句结束以后,AX = 2300H 即十进制的 8960,而 DX = 34H即十进制的 52 和我们的结果是一致的。
DX 寄存器在 MUL 指令中的使用则各位可以参考在 AX 中 MUL 运算的使用,

指针寄存器(BP,SP):

BP 寄存器:

8086 CPU 中的指针寄存器包括两个,即 SP 和 BP ,在这里呢,我先只对 BP 寄存器做介绍,

因为 SP 寄存器实质上必须和 SS 段寄存器一起使用,所以,我将会把 SP 寄存器留到后面和 SS 段寄存器一起作介绍。

BP (Base Pointer)也就是基指针寄存器,它和其他的几个用来进行寻址操作所使用的寄存器(还有 BX,SI,DI)没有太大的区别,

关于 SI 和 DI 寄存器的下面请见下文。

首先,BP 寄存器作为通用寄存器的一种,说明其是可以暂存数据的,而后,BP 又不是数据寄存器,

也就意味着其不能分割成 2 个独立的 8 位寄存器使用,

而后当以 […] 的方式访问内存单元而且在 […] 中使用了寄存器 BP 的话,

那么如果在指令中没有明确或者说是显示的给出段地址时,

段地址则使用默认的 SS 寄存器中的值(BX,SI,DI 会默认使用 DS 段寄存器),

比如 DS:[BP] 则在这里明确给出了段地址位于 DS 中,

所以,这里代表的内存单元即是段地址为 DS ,偏移量为 BP 寄存器中的值的内存单元,

而如果单单是使用 [BP] 的话,则代表的内存单元是段地址为 SS,偏移量为 BP 寄存器中的值的内存单元。

并且 BP 寄存器主要适用于给出堆栈中数据区的偏移,从而可以方便的实现直接存取堆栈中的数据,

至于堆栈的话,会在后面的博文中介绍。

在 8086 CPU 中,只有 4 个寄存器可以以 […] 的方式使用,这四个寄存器分别是 BX,SI,DI,BP。
下面的 Demo 是 BX 寄存器在寻址中的使用:
MOV BP,0
MOV AX,[BP]         ;将 SS:[BP] 代表的内存单元移入 AX 中
MOV AX,CS:[BP]      ;将 CS:[BP] 代表的内存单元移入 AX 中

语句的执行过程如下:

image-20210421150338394.png

变址寄存器(SI,DI):

首先,变址寄存器和上面介绍的指针寄存器(也就是 BP 和 SP),它们的功能其实都是用于存放某个存储单元地址的偏移,

或者是用于某组存储单元开始地址的偏移,即作为存储器指针使用,当然,由于变址寄存器和指针寄存器都是属于通用寄存器,

所以它们也可以保存算术结果或者说是具有暂存数据的功能,但是因为它们不是数据寄存器,所以无法分割成 2 个独立的 8 位寄存器使用,

关于变址寄存器和指针寄存器的详细使用,笔者将会在下一篇博文中作出最详细的介绍,

SI (Source Index) 是源变址寄存器,DI (Destination Index) 即是目的变址寄存器,

8086 CPU 中的 SI 寄存器和 DI 寄存器其实和 BX 寄存器的功能是差不多的,

只不过 SI 寄存器和 DI 寄存器均不是数据寄存器,所以它们不能够拆分为 2 个独立的 8 位寄存器,

而这也就是 SI 寄存器和 DI 寄存器与BX 寄存器所不同的地方,

既然,SI,DI 两个寄存器的功能和 BX 差不多,自然,SI 和 DI 中也是可以暂存一般性数据的,

同时,通过使用 SI 和 DI 寄存器也是可以用来完成寻址操作的。

比如下面的代码就是可行的:
MOV SI,0        ;初始化偏移地址为 0
MOV AX,[SI]        ;将段地址为 DS 偏移地址为 SI 的内存单元中的值移入 AX 中
MOV AX,DS:[SI]        ;将段地址为 DS 偏移地址为 SI 的内存单元中的值移入 AX 中
MOV AX,SS:[SI]        ;将段地址为 SS 偏移地址为 SI 的内存单元中的值移入 AX 中
 
MOV DI,0            ;初始化偏移地址为 0
MOV AX,[DI]        ;将段地址为 DS 偏移地址为 DI 的内存单元中的值移入 AX 中
MOV AX,DS:[DI]        ;将段地址为 DS 偏移地址为 DI 的内存单元中的值移入 AX 中
MOV AX,SS:[DI]        ;将段地址为 SS 偏移地址为 DI 的内存单元中的值移入 AX 中

其他寄存器(CS,IP,SS,SP,DS,ES)

由于段寄存器总是和其他一些像指针寄存器,变址寄存器,控制寄存器一起使用,

所以在这里,我并不会单独介绍段寄存器,而是将段寄存器和一些其他的常用寄存器搭配介绍 。

由于下面的介绍中会涉及到很多关于段和栈的概念,而段和栈的介绍又都必须关系到物理内存,

所以在介绍段寄存器以及其他一些呈协作关系的寄存器之前,还是先来介绍一下这几个基本的概念比较好。

8086 CPU 访问内存(物理地址):

当 CPU 需要访问一个内存单元时,需要给出内存单元的地址,

而每一个内存单元在物理内存空间中都有一个唯一的地址,

即可以通过这个地址定位到内存单元,而这个地址即为物理地址。

CPU 通过地址总线将一个内存单元的物理地址送入存储器,

而后 CPU 便可以通过这个物理地址来访问这个物理地址所指向的内存单元了。

那么这个物理地址在 CPU 中是如何形成的呢?

首先,我们知道 8086 CPU 的地址总线是 20 根,

即每次都可以传输 20 位的地址,从而寻址能力有 220 也就是 1MB 的大小,

但是 8086 CPU 的寄存器只有 16 位,也就是在 8086 CPU 的内部,

一次性处理,传输,暂存的地址都只能是 16 位,

即 8086 CPU 不能完整的保存下一个物理地址(物理地址为 20 位),

如果单单以最简单的方式(即直接用 16 位寄存器来保存物理地址)的话,那么,寻址能力只有 216 ,也就是 64KB,

如果真以如此简单的方式的话,那么地址总线还需要 20 根干嘛呢?而且,难不成我们以后的内存就是 64KB 了吗?

当然不是的,8086 CPU 在这里采取了一定的措施从而使其寻址能力达到 1MB 。

8086 CPU 在内部通过两个 16 位的地址进行合成从而形成一个 20 位的物理地址,由此,8086 CPU 的寻址能力便可以达到 1MB 。

那么 8086 CPU 又是如何将两个 16 位的地址合成为一个20 位的物理地址的呢?

当 CPU 在访问内存时,其会使用一个 16 位的基地址,然后再使用一个 16 位的偏移地址,

通过将基地址和偏移地址传入 8086 CPU 的地址加法器中进行合成即可以构造出 20 位的物理地址。

至于合成的方式如下:

基地址其实是通过一个 16 位的段地址来形成的,将一个段地址左移 4 位即形成了基地址,

而至于偏移地址的话,自然不必多说,为 16 位,通过将基地址和偏移地址相加便形成了 20 位的物理地址 。

下面给出一幅示意图来表示物理地址的合成:

image-20210421150556333.png

段:

至于段的话,其实在物理内存中是没有段这一概念的,事实上,段的概念来自于 CPU ,

因为 CPU 拥有段寄存器,既然在 CPU 中拥有了段寄存器,自然,在 CPU 中就肯定有段的概念了,

其实段也就是在编程时,我们将若干个地址连续的内存单元看做是一个段,

然后通过将一个段地址左移 4 位形成基地址,再通过这个基地址来定位这个段的起始地址,

然后,再通过偏移地址便可以精确定位到段中的内存单元了,由于段的起始地址是一个段地址左移 4 位,

所以很明显,段的起始地址肯定是 16 的倍数,而且由于一个段内部,只能通过偏移地址来定位,

而偏移地址为 16 位,所以一个段的长度也就是 216 也就是 64KB 的大小。

在编程时,可以讲一段内存定义成为一个段,而这里,我们又可以引出数据段,代码段,栈段这三种类型的段 。

数据段:其实就是我们自个儿定义一段内存(当然段起始地址肯定是 16 的倍数,并且段长度 <= 64KB),然后我们在这个段里头存放我们所需要使用的数据,这就是数据段;

代码段:其实也很简单,也是我们自己在编程的时候定义一段内存,然后这段内存用来存放我们的代码(也就是指令),既然是存放的代码,自然就称之为代码段;

栈段:至于栈段的话,有接触过数据结构的朋友应该是很清楚栈的,而这里我们也就是在内存中分配出一个段,然后将这个段当做栈来使用,

首先,对于任何一个段来说,均有段地址,而这些段地址是存放在段寄存器中(段寄存器的作用也在于此),

但是对于不同的段,它们默认的段地址存放在不同的段寄存器中,像

数据段来说,它的段地址存放在 DS (Data Segment)寄存器中,

代码段的段地址存放在 CS (Code Segment)寄存器中,

栈段的段地址存放在 SS (Stack Segment)寄存器中 。

下面给出一幅在段中寻址的示意图:

image-20210421150556333.png

上面的示意图中,通过将段地址左移四位,然后与偏移地址相加便可以得到 20 位的物理地址了 。

栈:

8086 CPU 中提供了对栈的支持,并且其还提供了相应的指令来以栈的方式访问内存空间 。
什么是栈?
通过上面在段中的介绍,栈其实就是一个段,再说白一点,也就是一块内存,当然,这块内存是一块连续的内存 。

既然栈是一个段的话,那么当然就可以以使用段的方式来使用栈,当然,除了像段一样的使用栈以外,

栈还提供了其特殊的访问方式(如果和段一模一样的话,那还需要栈干吗呢?),

众所周知,栈是先进后出类型的数据结构,在 8086 CPU 中也是如此,

可以通过 ”PUSH“ 指令将数据压入栈中,然后再通过 ”POP“ 指令将栈顶的元素取出来 。

下面给出一幅示意图来描述栈:

image-20210421151956354.png

即通过 PUSH 10 来将元素 10 放入栈中,因为,先前栈中没有任何数据,所以,10 就会作为栈顶元素存在,

然后再在栈中压入元素 20 ,此时,栈顶中的元素就是 20 了,然后再使用 POP 指令将栈顶元素取出,

此时取出的栈顶元素是 20 ,取出 20 后,栈中便只剩下 10 了,自然 10 就成为了栈顶,

最后再通过 POP 指令将栈顶 10 取出,此时,栈便变成了空栈了 。

好了,在介绍段寄存器之前的基础知识介绍就到这里了,下面开始正式介绍段寄存器以及与它们协作使用的寄存器。

CS 寄存器 和 IP 寄存器:

经过前面对段的介绍,相信各位朋友对段寄存器应该也有一定的了解了,

下面将要介绍的是一组非常非常重要的寄存器,即 CS:IP 。

CS:IP 两个寄存器指示了 CPU 当前将要读取的指令的地址,其中 CS 为代码段寄存器,而 IP 为指令指针寄存器 。

什么叫做指示了 CPU 当前将要读取的指令呢?在 8086 CPU 中,为什么 CPU 会自动的执行指令呢?

这些指令肯定是存放在内存中的,但是 CPU 怎么知道这些指令存放在内存的那个位置呢?

比如,我有下面的两条指令要执行:

MOV AX,1234H
MOV BX,AX

而假设这两条指令在内存中存放为:

image-20210421152143460.png

很显然, 1000H:0000H 指向的是 MOV AX,1234H 的首地址,

如果 CPU 要读取到我的指令的话,很显然,必须要知道地址 1000H:0000H ,

然后 CPU 就可以根据这个首地址,将汇编指令 MOV AX,1234H 所对应的机器码读入到 CPU 的指令寄存器中,

最后便可以在 CPU 中进行处理了。

但关键是 CPU 如何知道我的 1000H:0000H 这个首地址?

其实这就需要使用到 CS:IP 这个寄存器组了 。

当我们运行一个可执行文件时,很明显,我们需要另外一个程序来将这个可执行文件加载到内存当中,

关于这个加载可执行文件的程序,我们在这里不管他,点一下即可,

一般是通过操作系统的外壳程序(也就是传说中的 Shell 程序),

Shell 将可执行文件加载到内存中以后,就会设置 CPU 中的两个寄存器,

即设置 CS:IP 两个寄存器指向可执行文件的起始地址,此后 CPU 便从这个起始地址开始读取内存中的指令,并且执行,

比如我们在写汇编程序时,通常会使用 START 标记,其实这个标记就是用来标记起始地址的,

当将一个汇编程序编译,连接成可执行文件以后,再通过操作系统的 Shell 程序将可执行文件加载到内存中以后,

这个 START 所标记处的地址就是整个可执行文件的起始地址了 。

也就是说,当一个可执行文件加载到内存中以后,CS:IP 两个寄存器便指向了这个可执行文件的起始地址,

然后 CPU 就可以从这个起始地址开始往下读取指令,

当读取完指令后,CS:IP 将会自动的改变,基本上是改变 IP ,从而指向下一条要读取的指令,这样就可以执行这个可执行文件了 。

最后再对 CS:IP 总结一下:

1、你想让  CPU  执行哪行指令,你就让  CS:IP  指向保存有指令的那块内存即可。
2、任何时候,CS:IP  指向的地址中的内容都是  CPU  当前执行的指令。

下面我们来看一个 Demo,并详细观察其执行的过程:

ASSUME CS:CODES
 
CODES SEGMENT
    
START:
    
    MOV AX,1234H
    MOV BX,AX
    
    MOV AH,4CH
    INT 21H
CODES ENDS
    END START

语句的执行过程如下:

image-20210421152341902.png

从上面的截图中可以看出,当我使用 Shell (在 DOS 下也就是 Command 命令解释器)将可执行文件加载进内存后,

可以看到,整个程序的起始地址为 0C54H : 0000 H ,并且,可以看到 CS 的地址为 0C54H ,IP 的地址为 0000H,

这正好吻合我们上面对 CS:IP 的分析,很明显,CPU 将会读取 MOV AX ,1234H 到 CPU 中并且执行 ,

然后我们继续向下看:

image-20210421152418027.png

可以看到,我们单步执行后,AX 中的值编成了 1234H ,而 IP 寄存器中的值变成了 0003H,

对于 AX 中的值的改变,我们是能够理解的,但是 IP 中的值为什么会从 0000H 变到 0003H 呢?

从最上面的一幅关于指令在内存中的存放可以看出 MOV AX ,1234H 在内存中需要 3 个内存单元存放,

也就是 CPU 为了执行 MOV AX ,1234H 这条指令,已经将内存中相对应的 3 个内存单元读入内存中了,

执行完这条指令后,自然,CPU 就要将偏移地址向下移动 3 个单元,从而使得 CS:IP 指向下一条需要执行的指令了 ,

为了更深刻的理解,我们再来继续看执行过程,

image-20210421152456288.png

从最上面的一幅关于指令在内存中的存放可以看出 MOV BX ,AX 在内存中只占 2 个内存单元,

这也就是为什么 IP 这一次只向下移动了 2 个单元的缘故 。

SS 寄存器和 SP 寄存器:

我只提到了一个栈就是先进后出操作,同时可以使用 ”PUSH“ 和 ”POP“ 指令,

然后就是稍微带了一下 SS 这个寄存器的介绍,

我们虽然在内存中是可以方便的定义一个栈了,但是,我们为什么要定义这么一个栈呢?

自然,是为了操作方便,同时提供给 CPU 使用的,

既然 CPU 要使用的话,自然,CPU 又必须根据一定的方式找到这个栈,

而这就需要使用 SS 和 SP 寄存器了 。

同时,一个栈也就是一块内存区域,通过上面的介绍,我们也知道了如果要在一块内存中精确地定位到内存单元的话(寻址),

我们必须要有基地址(也就是段地址左移 4 位)和偏移地址,自然,要在一个栈中寻址的话,也需要段地址和偏移地址,

而对于一个栈来说,我们使用的最多的是什么呢?

当然是栈顶了,因为只有栈顶可以用来存取数据,所以对于一个栈来说,我们只需要有栈顶的段地址和偏移地址即可,

而对于栈顶的段地址,其是存放在段寄存器 SS 中的,而对于栈顶的偏移地址,其则是存放在 SP 寄存器中的 。

记住,在任何时刻,SS:SP 都是指向栈顶元素 。

其实关于栈的使用还是比较简单的,但是要注意的是 8086 CPU 并不会保证我们对栈的操作会不会越界 。

所以我们在使用栈的时候需要特别注意栈的越界问题 。

当使用 PUSH 指令向栈中压入 1 个字节单元时,SP = SP - 1;即栈顶元素会发生变化;

而当使用 PUSH 指令向栈中压入 2 个字节的字单元时,SP = SP – 2 ;即栈顶元素也要发生变化;

当使用 POP 指令从栈中弹出 1 个字节单元时, SP = SP + 1;即栈顶元素会发生变化;

当使用 POP 指令从栈中弹出 2 个字节单元的字单元时, SP = SP + 2 ;即栈顶元素会发生变化;

下面通过一个 Demo 来介绍栈的使用:

ASSUME CS:CODES
 
CODES SEGMENT
    
START:
    
    MOV AX,1000H        ;首先是定义好栈的段地址
    MOV SS,AX    
    MOV AX,10H            ;再定义好栈的长度(初始时刻的栈顶偏移地址即栈的长度)
    MOV SP,AX
    
    MOV AX,0001H
    PUSH AX
    MOV AX,0002H
    PUSH AX
    MOV AX,0003H
    PUSH AX
    MOV AX,0004H
    PUSH AX
    MOV AX,0005H
    PUSH AX
    
    POP AX
    POP AX
    POP AX
    POP AX
    POP AX
    
    
    MOV AH,4CH
    INT 21H
CODES ENDS
    END START

然后我们来看栈在内存中的结构图:

image-20210421152753066.png

语句的执行过程如下:
首先我们来看尚未执行上述任何指令时栈中的数据情况:

image-20210421152828405.png

然后我们再来依次执行上述指令:

image-20210421152901617.png

从上副截图中可以看出已经设置好了 SS:SP ,也就是栈已经设置 OK 了,

下面开始往栈中压入数据了,

image-20210421152934273.png

由于我们压入栈中的数据为字数据,即占 2 个内存单元,所以,每次 SP = SP – 2 ;

将 5 个字型数据压入栈中后,我们可以来查看栈中的数据了,

因此,在内存中的一个好看点的结构图如下所示:

image-20210421153021193.png

下面开始进行出栈操作了

image-20210421153056876.png

由于我们弹出栈时的数据为字数据,即占 2 个内存单元,所以,每次 SP = SP + 2 ;

将 5 个字型数据全部弹出栈中后,我们可以来查看栈中的数据了,

image-20210421153131743.png

可以看到 SP 变成了初始状态了,也就是说栈中所有的数据已经全部弹出了,虽然我们查看内存时看到的不是 0 ,

但是我们看到的这些数据都是无效的,我们这里不理会 。

DS 寄存器和 ES 寄存器:

DS 寄存器和 ES 寄存器都属于段寄存器,其实它们和 CS 寄存器以及 SS 寄存器用起来区别不大,

既然是段寄存器的话,自然它们存放的就是某个段地址了 。

通过上面对基础知识的介绍呢,我们已经知道,如果 CPU 要访问一个内存单元时,

我们必须要提供一个指向这个内存单元的物理地址给 CPU ,

而我们也知道在 8086 CPU 中,物理地址是由段地址左移 4 位,然后加上偏移地址形成的,

所以,我们也就只需要提供段地址和偏移地址即 OK 。

8086 CPU 呢,提供了一个 DS 寄存器,并且通常都是通过这个 DS 段寄存器来存放要访问的数据的段地址 。

DS(Data Segment):很显然,DS 中存放的是数据段的段地址 。

但是这里不得不再点一下,那就是我们对段的支持是在 CPU 上体现的,而不是在内存中实现了段,

所以事实上我们使用的段其实是一个逻辑概念,即是我们自己定义的,

再说白了,我定义一个段,我说它是数据段那它就是数据段,我说它是代码段那么它就是代码段,

它们其实都是一块连续的内存而已,至于为什么要区分为数据段和代码段,

很明显,是用来给我们编程提供方便的,即我们在自己的思想上或者说是编码习惯上规定,

数据放数据段中,代码放代码段中 。而我们在使用数据段的时候,为了方便或者说是代码的编写方便起见,

我们一般把数据段的段地址放在 DS 寄存器中,当然,如果你硬要觉得 DS 不顺眼,那你可以换个 ES 也是一样的,

至于 ES(Extra Segment) 段寄存器的话,自然,是一个附加段寄存器,如果再说得过分点,

就当它是个扩展吧,当你发现,你几个段寄存器不够用的时候,你可以考虑使用 ES 段寄存器,

在使用方式上,则和其他的段寄存器没什么区别 。

下面看一个介绍使用 DS 寄存器的 Demo:

ASSUME CS:CODES
 
CODES SEGMENT
   
START:
 
    MOV AX,1000H
    MOV DS,AX
    MOV AL,1
    MOV BX,0
    
    MOV CX,5            ;设计一个循环,让其循环 5 次
    s: MOV [BX],AL        ;这里 [BX] 并没有指定段地址哦
       INC AL
       INC BX
       LOOP s            
    
    MOV AH,4CH
    INT 21H
CODES ENDS
    END START

上面的代码所做的事情,就是循环将 1,2,3,4,5 写入到地址 1000H:0000H ,1000H:0001H,

1000H:0002H,1000H:0003H,1000H:0004H 中,

语句的执行过程如下:

首先我们来看尚未执行上述任何指令时栈中的数据情况:

image-20210421153348270.png

而当循环执行完成以后,我们再来看内存 1000H:0000H 处的值:

image-20210421153430468.png

在这里,我们可以看到确实达到了我们预期的效果,但是大家注意看代码:

s: MOV [BX],AL        ;这里 [BX] 并没有指定段地址哦
    INC AL
   INC BX
   LOOP s  

这里可以看到,我们在 [BX] 中并没有给其指定段地址,而只有一个偏移地址,

但是根据我们一开始的介绍,必须要有段地址和偏移地址才能够定位内存单元,

莫非这里出问题了?

其实不是的,因为我们在最前面定义了段地址 DS 为 1000H,

当我们定义好段地址后,每一次 CPU 执行到 [BX] 时,便会自动或者说是默认的从 DS 中取值,

并且将取得的值作为段地址,因此,当 [BX] 为 0001H 时,CPU 会从 DS 中取得一个 1000H ,

由这两个一合成即可以得到正确的物理地址 1000H:0000H 。

最后还提醒一点,那就是 8086 CPU 不支持直接将一个数据送入段寄存器中,

也就是下面的做法是错误的:

MOV DS,1000H       (X)

标志寄存器(FLAG):

前面呢,已经介绍了 8086 CPU 14 个寄存器中的 13 个了,下面我们将介绍最后一个寄存器也就是 FLAG 寄存器,

FLAG 寄存器之所以放到最后一个介绍,是因为其和其他的一些寄存器不同,像 AX,BX,CX,DX 这些寄存器来说,

它们都是用来存放数据的,当然 FLAG 中存放的也是数据啦,

呵呵,不过,AX,BX 这些寄存器中的数据是作为一个整体使用的,

最多也就分成一个 AL 和 AH 使用而已,但是在 FLAG 中,数据是按位起作用的,

也就是说,FLAG 中的每一个位都表示不同的状态,

由于一个位也就能表示 0 和 1 ,自然,FLAG 中的每一个位就是用来描述状态的,

而且 FLAG 寄存器中存储的信息通常又被称作程序状态字(PSW) 。

下面我给出一幅 FLAG 寄存器中各个位的示意图:

image-20210421153635527.png

从上面这幅图中可以看出,FLAG 的第 0 个位表示的是 CF ,第 2 个位表示的是 PF ,与此类推 . . . .

首先,我们来看一个列表:

image-20210421153839885.png

上面的这个表怎么看呢?我们通过看下面一幅截图就知道了 。

image-20210421153912383.png

从上面的标记中可以看出,从左到右依次代表 OF,DF,SF,ZF,PF,CF 标志位的值,

再通过与上面的表格相对照可以知道:

OF = 0 ;

DF = 0 ;

SF = 0 ;

ZF = 0 ;

PF = 0 ;

CF = 0  ;

至于为什么我们在 Debug 模式下,使用 R 命令时,只会列出这几个标志位,我菜的话是因为相对来说,

列出的这几个标志位更为常用,其他的几个标志位并不经常使用的缘故吧 。

下面我们就按不同的位来分别介绍这些位所描述的状态,以及它们代表的意义:

CF(Carry FLag) - 进位标志(第 0 位):

CF: 进位标志是用来反映计算时是否产生了由低位向高位的进位,或者产生了从高位到低位的借位 。

if(运算过程中产生了进位或者借位)

    {
            CF  =  1;
    }
    else
    {
            CF  =  0;
    }

PF(Parity FLag) - 奇偶标志(第 2 位):

PF: 奇偶标志是用来记录相关指令执行后,其结果的所有的 Bit 位中 1 的个数是否为偶数 。

if(运算结果中 1 的个数为偶数)
{
        PF  =  1;
}
else
{
        PF  =  0;
}

AF(Auxiliary Carry FLag) - 辅助进位标志(第 4 位):

AF: 用来辅助进位标志 。

if(字节操作中发生低半个字节向高半个字节借位或者进位  ||  字操作中发生低字节向高字节借位或者进位)

    {
           AF = 1;
    }
    else
    {
           AF = 0;
    }

ZF(Zero FLag) – 零标志(第 6 位):

ZF: 记录的是相关的指令执行完毕后,其执行的结果是否为 0 。

if(执行的结果  ==  0)

    {
           ZF = 1;
    }
    else
    {
           ZF = 0;
    }

SF(Sign FLag) - 符号标志(第 7 位):

SF: 符号标志,其记录相关指令执行完以后,其结果是否为负数 。

if(运算结果为负数)

    {
            SF  =  1;
    }
    else
    {
            SF  =  0;
    }

TF(Trap FLag) - 追踪标志(第 8 位):

TF: 追踪标志,主要是用于调试时使用 。

if(TF  ==  1)
{
       CPU 进入单步方式;
}

IF(Interrupt-Enable FLag) - 中断允许标志(第 9 位):

IF: 中断允许标志,其决定 CPU 是否能够响应外部可屏蔽中断请求(以后会做详细介绍) 。

if(IF  ==  1)
{
        CPU 能够响应外部的可屏蔽中断请求;
}
else
{
        CPU 不能够响应外部的可屏蔽中断请求;
}

DF(Direction FLag) - 方向标志(第 10 位):

DF: 方向标志,其用于在串处理指令中,用来控制每次操作后 SI 和 DI 是自增还是自减 。

if(DF == 0)
{
        SI++;


        DI++;
}
else
{

    SI--;

        DI--;
}

OF(OverFlow FLag) - 溢出标志(第 11 位):

OF: 溢出标志,其通常记录了有符号数运算的结果是否发生了溢出 。

if(运算发生溢出)
{
        OF  =  1;
}
else
{
        OF  =  0;
}



Debug命令详解

R命令的使用

作用:观看和修改寄存器的值。

在提示符“-”下输入以下命令:R。DEBUG将会显示出当前所有寄存器和标志位的状态。

接下来再输入命令rax。在提示符“:”后输入100。该命令的作用是将寄存器ax的值设置为100(注意:DEBUG使用的是十六进制,这里的100相当于十进制的256。)

最后再执行r命令,观看修改后的寄存器值。

image-20210421092719727.png

H命令的使用

H命令作用:计算两个十六进制数的和与差。

在提示符“–”下输入以下命令:h 10 1。观看命令执行结果。

image-20210421093216752.png

D命令的使用

D命令作用:显示内存区域的内容。

在提示符“–”下连续执行命令R、D、D。观看命令执行结果。

image-20210421093340081.png

命令R的作用是显示当前寄存器的值。而命令D的作用是显示内存区域的内容,最左边是内存的起始地址,中间以十六进制的形式显示内存值,最右边是以ASCII码的形式显示内存值。每行最多显示16个字节的内容。

命令D可以带参数也可省略参数。设DEBUG启动时DS的值为X,当省略参数时,命令D显示内容以X:100为起始,每次显示128个字节的内容。以后再执行不带参数的命令D时,DEBUG将按上次的位置接着显示下去。

带参数时DEBUG能够显示指定地址范围的内容。带参数的方式有三种:

方式一:d [起始位置]。DEBUG从起始位置开始显示128个字节的内容。在提示符“-”下执行命令d 1000:100。观看命令执行结果。

image-20210421094719977.png

方式二:d [起始位置] [结束位置]。DEBUG从起始位置开始一直显示到结束位置。在提示符“-”下执行命令d 1000:100 200。观看命令执行结果。

image-20210421095434588.png

方式三:d [起始位置] [L长度],长度以L参数为标识。DEBUG从起始位置开始显示指定长度的内容。在提示符“-”下执行命令d ds:100 L10。观看命令执行结果。

image-20210421095644618.png

E命令的使用

E命令作用:改变内存单位的内容。

E命令的使用方式为:E [起始位置]。

在提示符“-”下输入以下命令:e 1000:100。

image-20210421100018247.png

DEBUG首先显示[1000:100]的内容01.,这时可以修改该字节的值。如果还要修改后续的内容,可以按空格键继续。当要跳过某个字节时,可以按连续的两个空格跳到后一个字节去。

F命令的使用

F命令作用:使用指定的值填充指定内存区域中的地址。

F命令的使用方式为:F [范围] [填充列表]。

在提示符“-”下输入以下命令:F 1AF5:100 L20 1 2 3 4 5。执行命令D 1AF5:100观看命令执行结果。

image-20210421100235049.png

说明:该命令是用字节序列01、02、03、04、05轮流填充从1AF5:100开始长度为20H的内存区域。

在提示符“-”下输入以下命令:F 1AF5:100 13F 41 42 43 44。

image-20210421100516145.png

说明:该命令是用字节序列41、42、43、44轮流填充从1AF5:100开始一直到1AF5:13F的内存区域。

M命令的使用

M命令作用:将指定内存区域的数据复制到指定的地址去。

M命令的使用方式为:M [范围] [指定地址]。

在提示符“-”下输入以下命令:M 1AF5:100 13F 1AF5:140。执行命令D 1AF5:100观看命令执行结果。

image-20210421100801823.png

C命令的使用

C命令作用:将两块内存的内容进行比较。

C命令的使用方式为:C [范围] [指定地址],意思就是将指定范围的内存区域与从指定地址开始的相同长度的内存区域逐个字节进行比较,列出不同的内容。

在提示符“-”下输入以下命令:C 1AF5:100 13F 1AF5:140。由于两块内容完全相同,所以命令执行后没有任何显示。

在提示符“-”下输入以下命令:C 1AF5:100 107 1AF5:180,比较的区域长度为8个字节。命令执行后列出比较结果不同的各个字节。

image-20210421100955726.png

S命令的使用

S命令作用:在指定的内存区域中搜索指定的串。

S命令的使用方式为:S [范围] [指定串]。

在提示符“-”下输入以下命令:D 1AF5:100 11F。显示该区域的内存值。

在提示符“-”下输入以下命令:S 1AF5:100 11F 41 42 43 44。搜索该区域是否存在字节串41 42 43 44,并将搜索结果一一列出。

image-20210421101207158.png

从执行结果可以看出,总共搜索到八处。

A命令的使用

A命令作用:输入汇编指令。

以下的程序要在屏幕上显示“ABCD”四个字符。

首先用E命令将“ABCD$”四个字符预先放在内存CS:200处,然后执行A100命令输入汇编程序代码:

MOV AX,CS

MOV DS,AX

MOV DX,200

MOV AH,9

INT 21

INT 20

(说明:前两行汇编指令用于将段寄存器CS的值赋给段寄存器DS。第三到第五行汇编代码的作用是显示以“$”为结尾的字符串。最后一行用于结束程序。)

image-20210421101609229.png

G命令的使用

G命令作用:执行汇编指令。

G命令的使用方法是:G [=起始地址] [断点地址],意思是从起始地址开始执行到断点地址。如果不设置断点,则程序一直运行到中止指令才停止。

在设置完示例九的的内存数据并且输入完示例九的程序后运行这些汇编代码。在DEBUG中执行命令G=100,观看运行结果。

image-20210421103525791.png

汇编程序运行后在屏幕上显示出“ABCD”四个字符。

接下来在DEBUG中执行G=100 10B,意思是从地址CS:100开始,一直运行到CS:10B停止。观看运行结果。

命令执行后,不但显示出字符串“ABCD”,而且列出当前寄存器和标志位的值。

image-20210421103634690.png

g命令在loop中的使用:

使用loop循环时,如果要跳出循环执行后面的代码,可以先用u命令查看代码的偏移地址,在用g命令跳转到此偏移到此代码处,如下:

image-20210421103737607.png

U命令的使用

U命令作用:对机器代码反汇编显示。

U命令的使用方法是:U [范围]。如果范围参数只输入了起始地址,则只对20H个字节的机器代码反汇编。执行命令U100,观看反汇编结果。

image-20210421104126885.png

执行命令U100 10B,观看反汇编结果。该命令的作用是对从100到10B的机器代码进行反汇编。

image-20210421104226787.png

N命令的使用

N命令作用:设置文件名,为将刚才编写的汇编程序存盘做准备。

以下的DEBUG命令序列作用将刚才的汇编程序存为磁盘的COM可执行程序。

D200 20F

U100 10C

N  E:\FIRST.COM

RCX

:110

W

第一和第二条命令的作用是检查一下刚才编写的汇编指令。第三条命令的作用是设置存盘文件名为E:\FIRST.COM,第四条命令的作用是设置存盘文件大小为110H个字节。最后一条命令是将文件存盘。
演示 :略
文件存盘后执行E:\FIRST.COM,观看存盘的可执行文件的运行效果。
演示 :略

W命令的使用

W命令作用:将文件或者特定扇区写入磁盘。

在示例“N命令的使用”中已经实验了如何使用W命令将文件存盘。

在没有很好地掌握汇编语言和磁盘文件系统前,暂时不要使用W命令写磁盘扇区,否则很容易损坏磁盘文件,甚至破坏整个磁盘的文件系统。

L命令的使用

L命令作用:从磁盘中将文件或扇区内容读入内存。

将文件调入内存必须先用DEBUG的N命令设定文件名。以下例子是将E:\FIRST.COM读入内容。

N FIRST.COM

L

观看调入程序的汇编代码可以使用DEBUG的U命令,用U100观看调入的COM文件。
读取磁盘扇区的方式是:L [内存地址] [磁盘驱动器号] [起始扇区] [扇区数]。“内存地址”指定要在其中加载文件或扇区内容的内存位置,如果不指定“内存地址”的话,DEBUG将使用CS寄存器中的当前地址。“磁盘驱动器号”指定包含读取指定扇区的磁盘的驱动器,该值是数值型:0=A,1=B,2=C等。“起始扇区”指定要加载其内容的第一个扇区的十六进制数。“扇区数”指定要加载其内容的连续扇区的十六进制数。

只有要加载特定扇区的内容而不是加载文件时,才能使用[磁盘驱动器号] [起始扇区] [扇区数]参数。

例如:要将C盘第一扇区读取到内存DS:300的位置,相应的DEBUG命令为L DS:300 2 1 1。但是由于Windows操作系统对文件系统的保护,这条命令可能会被操作系统禁止运行。

T命令的使用

T命令作用:执行汇编程序,单步跟踪。

T命令的使用方式是T [=地址] [指令数]。如果忽略“地址”的话,T命令从CS:IP处开始运行。“指令数”是要单步执行的指令的数量。

以下示例对E:\FIRST.COM进行单步跟踪。

N E:\FIRST.COM

L

U100 10B

R

T=100

T

第一、二条命令是装入文件,第三条命令是列出程序反汇编代码,第四条命令是显示当前寄存器值,第五条命令是从CS:100处开始单步跟踪,第六条命令是继续跟踪后续的指令。

P命令的使用

P命令作用:执行汇编程序,单步跟踪。与T命令不同的是:P命令不会跟踪进入子程序或软中断。

P命令的使用方式与T命令的使用方式完全相同。

p命令还可以用于结束本次循环,进入下一次循环。

I命令的使用

I命令作用:从计算机输入端口读取数据并显示。

I命令的用法是I [端口地址]。例如从3F8号端口读取数据并显示的命令为:I 3F8。这里不对该命令做解释。

O命令的使用

O命令作用:向计算机输出端口送出数据。

O命令的用法是O [端口地址] [字节值]。例如向278号端口发出数据20H的命令为:I 278 20。这里不对该命令做解释。

Q命令的使用

Q命令的作用是退出DEBUG,回到DOS状态。

实验一 DEBUG命令的应用

一 实验要求
熟悉DEBUG的应用,利用DEBUG观察寄存器的值,掌握直接汇编指令,单步调试,编辑数据等指令,为学习指令系统打好基础。
二 实验步骤
DEBUG程序是DOS操作系统为汇编语言程序设计者和系统管理员提供的一个通用调试工具,使用DEBUG可以直接深入到计算机系统内部,读写CPU各寄存器或存储器单元内容,并可以直接访问接口和外设的寄存器。DEBUG的功能可分为下面几类:

(1) 读写、比较和显示存储器单元内容;
(2) 在存储器之间,存储器与磁盘之间传送数据和程序;
(3) 在指定存储单元中添充数据或字符串,在指定范围内查找数据或字符串;
(4) 设置程序起始执行地址或断点,执行程序或分段执行程序;
(5) 跟踪程序执行,显示处理器状态;
(6) 汇编或反汇编源程序;

DEBUG只使用十六进制表示数据(十六进制数后不能加H),但所有数据在内存中均以二进制形式存放和运算。
DEBUG只有几十条单字母命令,功能强且易掌握,下面介绍DEBUG命令的使用方法。
(说明:下面的”< >”表示命令的一项参数,“[ ]”表示该选项为可选项。)
1、DEBUG 的进入和退出:
(1)进入DEBUG的两种方法:
方法一:在DOS提示符下直接键入:C> DEBUG↙(回车符)
DEBUG即被调入内存并启动运行,显示DEBUG的提示符“—”。
方法二:在DOS提示符下,键入:C> DEBUG 文件名(回车符)
DEBUG即被调入内存并启动运行,然后将指定的文件调入内存,并显示DEBUG提示符“—”。由于DEBUG可以将任一文件调入内存,所以“文件名”必须写全称,若有扩展名一定要输入扩展名。
(2) 退出DEBUG时,键入命令:
—Q↙ 就可从DEBUG状态返回DOS。
2、汇编与反汇编命令:
汇编命令A和反汇编命令U是DEBUG下常使用的命令。
(1)汇编命令A

格式:A [<起始地址>]
功能:逐行汇编程序,主要用于小段汇编程序。

例:—A 100 ↙
然后在其后面分别输入以下几条命令:

0AF3:0100 MOV DL,41
0AF3:0102 MOV AH,2
0AF3:0104 INT 21
0AF3:0106 INT 20
0AF3:0108 ^C
-  

(2)反汇编命令U
格式:U [<地址范围>]
功能:对指定地址范围内的二进制代码进行反汇编,常用于分析目标代码。
例:对上例程序反汇编如下:

 -U 100 106↙

得到如下结果:

0AF3:0100 B241          MOV     DL,41
0AF3:0102 B402          MOV     AH,02
0AF3:0104 CD21          INT     21
0AF3:0106 CD20          INT     20 

3、执行程序命令
在DEBUG下,可以完整执行程序,分段执行程序或单步执行程序。
(1)执行程序命令G
格式:G [=<程序起始地址>] [<断点>…]
功能:完整或分段执行程序 。
说明:如果G命令不带参数,则从头到尾执行程序;如有断点,则执行到断点地址时暂停并显示当前各寄存器状态。其中断点最多允许设置10个。程序正常结束时显示“Program terminated normally”。
例:-a 100↙

0AF3:0100 MOV AX,10
0AF3:0103 MOV BX,6
0AF3:0106 SUB AX,BX
0AF3:0108 INT 20
0AF3:010A ^C
-G↙
Program terminated normally   

(2)单步执行命令T
格式:T[=<程序起始地址>] [<跟踪条数>]
功能:逐条跟踪执行程序命令
说明:每条指令执行后都将显示各寄存器当前值。
例:单步执行上面程序。

-t↙
AX=0010  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=0AF3  ES=0AF3  SS=0AF3  CS=0AF3  IP=0103   NV UP EI PL NZ NA PO NC
0AF3:0103 BB0600        MOV     BX,0006



-t↙
AX=0010  BX=0006  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=0AF3  ES=0AF3  SS=0AF3  CS=0AF3  IP=0106   NV UP EI PL NZ NA PO NC
0AF3:0106 29D8          SUB     AX,BX



-t↙
AX=000A  BX=0006  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=0AF3  ES=0AF3  SS=0AF3  CS=0AF3  IP=0108   NV UP EI PL NZ AC PE NC
0AF3:0108 CD20          INT     20

4、访问寄存器命令R
格式:R [<寄存器>]
功能:显示或修改寄存器内容。
说明:当R命令后面不指定寄存器时,显示各寄存器的内容,否则修改指定寄存器的内容。在显示寄存器内容时,首先显示13个16位寄存器的内容,随后是程序状态字寄存器的内容,最后一行是下一条要执行的指令地址及指令。
例1:显示所有寄存器的内容

-R↙
AX=000A  BX=0006  CX=0000  DX=0000  SP=FFE8  BP=0000  SI=0000  DI=0000
DS=0AF3  ES=0AF3  SS=0AF3  CS=00C9  IP=0FA8   NV UP DI PL NZ AC PE NC
00C9:0FA8 90            NOP

例2:修改寄存器内容
要修改某寄存器内容,可在R命令后键入寄存器名,DEBUG将显示出这个寄存器的值,然后键入新值就可修改该寄存器的内容。
例如:

-R AX↙
AX 000A
:3000

再对AX寄存器进行查看:

-R↙
AX=3000  BX=0006  CX=0000  DX=0000  SP=FFE8  BP=0000  SI=0000  DI=0000
DS=0AF3  ES=0AF3  SS=0AF3  CS=00C9  IP=0FA8   NV UP DI PL NZ AC PE NC

修改PSW寄存器时,无论修改哪一位标志位,或修改哪几位标志位,只要键入当前标志位的相反值就可完成修改,与键入标志位值顺序无关。PSW寄存器用F表示,各标志位的符号见课本。
例3:修改PSW寄存器的零标志位和奇偶标志位,再交换键入值顺序改成原值。

-RF↙
NV UP DI PL NZ AC PE NC  -ZR PO  修改零标志和奇偶标志
-RF↙
NV UP DI PL ZR AC PO NC  -NZ PE  交换键入值改成原值
-RF↙
NV UP DI PL NZ AC PE NC          显示原值

标志位显示    1    0
CF    CY()    NC()
ZF    ZR()    NZ()
SF    NG()    PL()
AF    AC()    NA()
OF    OV()    NV()
PF    PE()    PO()
DF    DN()    UP()
IF    EI()    DI()

5、存储器单元访问命令:
存储器单元访问命令包括:显示存储器单元命令D,向存储器单元写入数据命令E和指定在存储区域填充数据命令F。
(1)显示命令D
格式:D [<地址范围>]
功能:显示指定地址范围内的存储区数据,包括十六进制形式及其对应的ASCII码字符。
例:显示存储单元110到116的内容:

-D 110 116↙
0AF3:0110  B9 04 01 AC 3C 0D 74

(2)写入数据命令E
格式:E<地址> [<字符串>]
功能:逐个修改指定单元内容或用字符串替代指定的一族连续单元内容。
说明:字符串可以是 单引号括起来的字母数字或一系列用空格分开的十六进制字节数据。
例1:将存储器单元142开始的内容修改为‘string‘。

-E 142 'string'↙
-D 142 147↙(验证是否正确)
0AF3:0140        73 74 72 69 6E 67   string

(3)填充命令F
格式:F<地址范围> <要添入的字节或字节串>
功能:在指定地址范围内写入指定数据。
例:联用R命令和F命令在彩显缓冲区中写入一串小写字母’a’(ASCII码值61H)。

-R DS↙
DS 0AF3
:B800↙
-F 0000 0050 61↙

6、读写磁盘命令:
读写盘有两种方式,一种是按扇区读写,直接使用读写命令即可,另一种是按文件名读写,后一种方法首先要指定读写文件名,再键入读写命令。
(1)按文件名读写:
格式:N<文件名> [<文件名>…]
功能:为读写磁盘文件定义文件名。
说明:可以指定多个文件名
例:-N FILE.COM↙

  • 指定要读写的文件为FILE.COM
    (1)写盘命令W:
    格式:W [<地址>[<盘号><相对扇区号><扇区数>]]
    功能:将指定存储区单元内容写入指定盘区或盘文件。
    说明:<地址>项指出存储区单元起始地址,<盘号>项为0表示A盘,为1表示B盘,<相对扇区号>是盘上存放输出数据扇区的第一个扇区号,扇区数为存放输出数据的扇区个数。
    例1:把从02E0地址开始的存储区数据,写到A盘上相对扇区30H开始的8个扇区中。

    -W 02E0 0 30 8↙

若要写文件,则先用N命令给出文件名,再用R命令把文件长度送到寄存器BX和CX中,然后键入W命令。
例2:把下面程序以DEMO.COM为文件名存入盘中。

-A100↙
00C9:0100 MOV DL,61↙
00C9:0102 MOV AH,2↙
00C9:0104 INT 21↙
00C9:0106 INT 20↙
00C9:0108 ^C(按下CTRL+C即可得到)
-N DEMO.COM↙
-RBX  0000↙
:↙
-RCX  0000↙
:8↙
-W↙
Writing 00008 bytes
-

(3)读盘命令L:
格式:L [<地址>[<盘号><相对扇区号><扇区数>]]
功能:将指定盘文件或盘区内容读入存储器中。
说明:<地址>项存放读入数据的存储区单元地址,其余项与W命令说明类似。
例1:读入文件DEMO.COM,再反汇编程序。

-N DEMO.COM↙
-L↙
-U↙
1291:0100 B261          MOV     DL,61
1291:0102 B402          MOV     AH,02
1291:0104 CD21          INT     21
1291:0106 CD20          INT     20

7、访问输入输出端口命令:
(1)输入命令I
格式:I <端口地址><字节>
功能:输入指定端口数据并显示。
例:由3F8H端口输入数据。

-I 3F8
FF

(1)输出命令O
格式:O<端口地址><字节>
功能:将字节数据输出到指定端口。
例:把07H送到端口3BCH。

-O 3BC  7
-

8、其它命令(主要包括以下几个命令:内存数据比较命令C、内存数据查找命令S、内存数据移动命令M、和十六进制运算命令H,对于它们的详细使用请同学们查找相关的书籍。)

实验二 寻址方式实验

说明:其中划横线的地方为该例题指令执行的结果;int 3为设置断点的指令,即执行到该指令时停止执行指令,并显示各寄存器的内容;对于g命令后面的值等于多少由运行该例题是从那一个开始地址决定的,即指令执行的开始地址.
一 实验要求: 掌握寻址方式,熟悉用DEBUG命令来观测指令的执行情况,利用单步跟踪调试观察寄存器的变化和指令执行的结果,加深对寻址方式的理解。
二 实验步骤
(1)立即寻址方式:
例(1) MOV AL,5
在debug下如何查看其执行结果:首先在MS—DOS方式下输入debug:其操作过程如下:

image-20210421082417162.png

C:\WINDOWS>debug↙(回车符)
-a↙
126C:0100 mov al,5↙
126C:0102 int 3↙
126C:0103↙
-g↙(执行程序命令G)
AX=0005  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=126C ES=126C  SS=126C  CS=126C IP=0102   NV UP EI PL NZ NA PO NC
126E:0102 CC            INT     3

例(2)MOV AX,3064(其执行过程如下:)

-a↙
126C0103 MOV AX,3064↙
126C:0106 INT 3↙
126C:0107↙
-G=0103↙
AX=3064  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=126C  ES=126C  SS=126C  CS=126C  IP=0106   NV UP EI PL NZ NA PO NC
126E:0106 CC            INT     3

(2)寄存器寻址方式:
例(1)MOV AX,BX(假设指令执行前AX=3064H,BX=1234H;)
其操作过程如下:

image-20210421082746348.png

-a↙
126C:0107 mov ax,3064↙
126C:010A mov bx,1234↙
126C:010D mov ax,bx↙
126C:010F int 3↙
126C:0110↙
-g=0107↙
AX=1234  BX=1234  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=126C  ES=126C  SS=126C  CS=126C  IP=010F   NV UP EI PL NZ NA PO NC
126C:010F CC            INT     3

(3)直接寻址方式:
例(1)MOV AX,2000H=3000H,并且(32000H)=3050H)其执行过程如下:

image-20210421083213473.png

-r ds↙
DS 126C
:3000↙
-e ds:2000
3000:2000  08.50 (敲一下空格)   00.30↙
-a↙
126C:0110 mov ax,[2000] ↙
126C:0113 int 3↙
126C:0114↙
-g=0110↙
AX=3050  BX=1234  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=3000  ES=126C  SS=126C  CS=126C  IP=0113   NV UP EI PL NZ NA PO NC
126C:0113 CC            INT     3

(4)寄存器间接寻址方式:
例(1)MOV AX,[BX](假设(DS)=2000H,(BX)=1000H,(21000H)=50A0)
其执行过程如下:

image-20210421083522899.png

-r ds↙
DS 3000
:2000↙
-e ds:1000
2000:1000  6C.A0 (敲一下空格)   61.50↙
-A↙
126C:0114 MOV BX,1000↙
126C:0117 MOV AX,[BX] ↙
126C:0119 INT 3↙
126C:011A↙
-G=0114↙
AX=50A0  BX=1000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=2000  ES=126C  SS=126C  CS=126C  IP=0119   NV UP EI PL NZ NA PO NC
126C:0119 CC            INT     3

(5)寄存器相对寻址方式:
例(1)MOV AX,COUNT[SI](假设(DS)=3000H,(SI)=2000H,COUNT=3000H,
(35000H)=1234H;)其执行过程如下:

image-20210421083729901.png

-r ds↙
DS 2000
:3000↙
-e ds:5000↙
3000:5000  00 34 (敲一下空格)   00 12↙
-a↙
126C:011A mov si,2000↙
126C:011D mov ax,[3000+si] ↙
126C:0121 int 3↙
126C:0122↙
-g=011a↙
AX=1234  BX=1000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=2000  DI=0000
DS=3000  ES=126C  SS=126C  CS=126C  IP=0121   NV UP EI PL NZ NA PO NC
126C:0121 CC            INT     3

(6)基址变址寻址方式:
例(1)MOV AX,BX(假设(DS)=2100H,(BX)=0158H,(DI)=10A5H2,(221FDH=1234H)其执行过程如下:

image-20210421084032797.png

-R DS↙
DS 3000
:2100↙
-E DS:11FD↙
2100:11FD  00 34 (敲一下空格)  00 12↙
-A↙
126C:0122 MOV BX,0158↙
126C:0125 MOV DI,10A5↙
126C:0128 MOV AX,[BX][DI] ↙
126C:012A INT 3↙
126C:012B↙
-G=0122↙
AX=1234  BX=0158  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=2000  DI=10A5
DS=2100  ES=126C  SS=126C  CS=126C  IP=012A   NV UP EI PL NZ NA PO NC
126C:012A CC            INT     3

(7)相对基址变址寻址方式:
例(1)MOV AX,MASKBX(假设(DS)=3000H,(BX)=2000H,(SI)=1000H,MASK=0250H(33250H=1234H)其执行过程如下:

image-20210421084335590.png

-R DS↙
DS 2100
:3000↙
-E DS:3250
3000:3250  10.34 (敲一下空格)   13.12↙
-A↙
126C:012A MOV BX,2000↙
126C:012D MOV SI,1000↙
126C:0130 MOV AX,[0250+BX+SI] ↙
126C:0134 INT 3↙
126C:0135↙
-G=012A↙
AX=1234  BX=2000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=1000  DI=10A5
DS=3000  ES=126C  SS=126C  CS=126C  IP=0134   NV UP EI PL NZ NA PO NC
126C:0134 CC            INT     3



















参考博客:
debug命令详解
8086CPU各寄存器及其简介

标签: none

暂无评论