汇编语言编程相关详解笔记

寄存器助记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
4个段地址寄存器:
CS(code segment)——16位的代码段寄存器;
DS(data segment)——16位的数据段寄存器;
ES(extra segment)——16位的扩展段寄存器;
SS(stack segment)——16位的堆栈段寄存器;

16位的指令指针寄存器IP;

20位的地址加法器;

6字节的指令队列缓冲器。

执行单元
执行部件由下列几个部分组成:

8个通用寄存器:即AX、BX、CX、DX,BP,SP,SI,DI ;
其中,4个数据寄存器:AX、BX、CX、DX;
2个地址指针寄存器:BP(base pointer),SP(stack pointer);
2个变址寄存器:SI(source index),DI(destination index) [2] ;

标志寄存器FR(flags register);

算术逻辑单元ALU(arithmetic logic unit)。

指令助记

mov 传送指令, mov ax,123 ax值设为123

add 增加指令, add ax,bx ax+bx赋值给ax寄存器

jmp 转移指令, jmp 3:0B16 CS=003H,IP=0B16H,cpu将从00B46H处读取指令

sub 减去指令, sub ax,bx ax-bx之后赋值给ax寄存器

push 指令 入栈,以栈的顺序访问

pop 指令 出栈,将对这段空间按后进先出的规则进行访问

SS寄存器, 栈顶的段位置存放在SS

SP寄存器, 栈的偏移地址存放在SP中

ds寄存器, 段寄存器,通常用来存放访问数据的段地址

[bx], 默认是为ds的段前缀+bx的偏移地址 bx可通过mov设值

loop, 循环,循环次数由cx来决定

inc bx bx += 1

dw 定义字型数据

db 定义字节型数据

dd 定义双字型数据

cs 存放代码段的段地址

CS:IP 指向的内容当作指令执行

and指令 逻辑与指令,按位进行与运算

or指令 逻辑或指令,按位进行或运算

si 偏移地址,源变址寄存器可用来存放相对于DS段之源变址指针

di 偏移地址,变址指针

BP 基址指针寄存器,可用作SS的一个相对基址位置

idata 常量,一个1或2或3等的常量数值

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

ah 表示高位的 8 位寄存器

al 表示低位的 8 位寄存器

div 除法指令

jcxz 当cx!=0什么也不做,jcxz标号 相当于 if(cx == 0) jmp 标号

adc 是带进位加法指令,利用了CF标志上记录的进位值

sbb 是带借位减法指令,利用了CF标志上记录的进位值

cmp cmp是比较指令,cmp的功能相当于减法指令,只是不保存结果,只影响标志位

基础知识

8086CPU完成计算 s = 768 + 12288 - 1280的机器码如下

书写和阅读机器码程序十分难懂和不易差错

汇编语言产生

汇编语言是机器指令便于记忆的书写格式

一个cpu中有很多寄存器,AX是其中一个寄存器的代号,BX是另一个寄存器的代号

此后程序员用汇编指令编写程序,需要有个编译器,将汇编指令转换成机器指令的翻译程序,机器最终执行机器码

汇编语言的组成

  • 汇编指令:机器码助记符,有对应的机器码
  • 伪指令:没有对应的机器码,由编译器执行,计算机并不执行
  • 其他符号:如+、-、*、/、由编译器识别,没有对应的机器码

存储器

指令和数据在存储器存放,也就是内存,内存作用仅次于CPU,离开了内存,性能再好的CPU也无法工作,就像再聪明的大脑,没有了记忆也无法进行思考

磁盘不同于内存,磁盘上数据或程序如果不读到内存,就无法被CPU使用

指令和数据

指令和数据都是应用上的概念,在内存或磁盘上,指令和数据没有区别,都是二进制信息,CPU在工作的时候把有的信息看作指令,有的信息看成数据,为同样的二进制赋予不同的意义

机器把它看做大小为89D8H的数据处理,也可以看做指令mov ax,bx来执行

存储单元

存储器划分若干个存储单元,每个存储单元从0开始顺序编号

通常一个存储单元为1个字节

CPU对存储器的读写

cpu要从内存读数据,首先要指定存储单元地址

cpu要进行数据的读写,必须和外部器件进行3类的信息交互

  • 存储单元的地址(地址信息)
  • 器件的选择,读或写的命令(控制信息)
  • 读或写的数据(数据信息)

  • 1: CPU通过地址线将地址信息3发出
  • 2:CPU通过控制线发出内存度命令,选中存储器芯片,并通知它,将要从中获取数据
  • 3:存储器3号单元中的数据8通过数据线送入CPU

写操作和读操作步骤类似

  • 1:CPU通过地址线将地址信息3发出
  • 2:CPU通过控制线发出内存写命令,选中存储器芯片,并通知它,要向其中写入数据
  • 3:CPU通过数据线将数据26送入内存3号单元中

地址总线

CPU通过地址总线来指定存储器单元

假设一个CPU有10根地址总线,现看一下寻址情况,在电子计算机,一个导线可以传送稳定状态有两种,高电平和低电平,用二进制表示为1或0,10根导线可以传送10位二进制数据, 而10位二进制数可以表示2的10次方个数据,最小数为0,最大数为1023

一个CPU有N根地址线,则可以说这个CPU的地址总线宽度为N,这样的CPU最多可寻找2的N次方个内存单元

CPU的总线宽度为10,那么可以寻址1024个内存单元

数据总线

CPU与内存或其他器件之间的数据传送是通过数据总线来进行的,数据总线的宽度决定了CPU与外界的数据传送速度

8跟数据总线一次可传送8位二进制数据(即一个字节),16根数据总线一次课传送2个字节

8位数据总线上传送的信息

16位数据总线上传送的信息

控制总线

CPU对外部器件的控制是通过控制总线来进行,控制总线是一些不同控制线的集合

有多少根控制总线,就意味着CPU提供了对外部期缴的多少种控制,所以控制总线的宽度决定了CPU对外部器件的控制能力

内存的读或写命令由几根控制总线综合发出

  • 其中一根称为”读信号输出”负责CPU向外传送读信号
    CPU向该控制线上输出低电平表示将要读取数据
  • 有一根是”写信号输出”的控制线则负责传送写信号

硬件设备

主板

主板上的核心器件和一些主要器件,这些器件通过(地址总线、数据总线、控制总线相连),这些器件有CPU、存储器、外围芯片组、扩展插槽,扩展插槽一般都插有RAM内存条和各种接口卡

接口卡

所有可程序控制其工作的设备,必须受到cpu的控制,如显示器,音响等

各类存储器芯片

  • 随机存储器(RAM):可读可写,但必须带点存储
  • 只读存储器(ROM):只读存储器只能读取不能写入,关机后其中内容不丢失

寄存器

典型的CPU由运算器、控制器、寄存器等器件构成,这些器件由内部总线相连

内部总线实现了CPU内部各个器件之前的联系,外部总线实现了cpu与主板上其他器件的联系

  • 运算器的信息处理
  • 寄存器的信息存储
  • 控制器控制各类器件进行工作
  • 内部总线连接各种器件,在他们之前进行数据传送

通用寄存器

8086CPU有十四个寄存器,每个寄存器都有名字:AX,BX,CX,DX,SI,DI,SP,BP,IP,CS,SS,DS,ES,PSW

8086cpu寄存器都是16位,可存放两个字节,AX,BX,CX,DX这四个寄存器存放一般性数据,被称为通用寄存器

这四个寄存器可分为两个可独立使用的8位寄存器来用

  • AX可分为AH和AL;
  • BX可分为BH和BL;
  • CX可分为CH和CL;
  • DX可分为DH和DL;

字在寄存器的存储

  • 字节,记为byte,一个字节8个位,可存在8位寄存器中
  • 字,记为word,一个字由两个字节组成,这两个字节分别称为这个字的高位字节和低位字节

几条汇编指令

寄存器名称不区分大小写,如mov ax,18和MOV AX,18相同

物理地址

CPU访问内存单元,要给出内存单元的地址,所有内存单元构成的存储空间是一个一维线性空间,每个内存单元在这个空间都有唯一的地址,这个唯一的地址称为物理地址

CPU通过地址总线送入存储器,必须是一个内存单元的物理地址,CPU向地址总线上发出物理地址之前,必须要在内部先形成这个物理地址,不同的CPU可以由不同形成物理地址的方式

16位结构的CPU

8086是16位机,也可以说16位结构的cpu

  • 运算器一次最多可以处理16位的数据
  • 寄存器的最大宽度是16位
  • 寄存器和运算器之间的通路位16位

所以8086内部能够一次性处理、传输、暂时存储的信息最大长度是16位

8086CPU给出物理地址的方法

8086CPU有20位地址总线,可以传送20位地址,达到1MB寻址能力

8086CPU又是16位结构,在内部一次性处理、传输、暂时存储的地址为16位

从8086cpu的内部结构来看,如果地址从内部简单发出,那么只能送出16位的地址,表现的寻址能力只有64kb

而内部用了两个16位地址合成方法形成20位的物理地址

当CPU要读写内存时

  • cpu的相关部件提供两个16位地址,一个段地址,另一个称偏移地址
  • 段地址和偏移地址通过内部总线送入一个称为地址加法器的部件
  • 地址加法器将两个16位地址合成一个20位的物理地址
  • 地址加法器通过内部总线将20位物理地址送入输入输出控制电路
  • 输入输出控制电路将20位物理地址送上地址总线
  • 20位物理地址被地址总线传送到存储器

地址加法器采用了物理地址=段地址X16+偏移地址的方法用段地址和偏移地址合成物理地址

“段地址X16+偏移地址=物理地址”的含义

本质含义CPU在访问内存是,用一个基础的地址和一个相对基础地址的偏移地址相加,给出内存单元的物理地址

这种寻址功能是”基础地址+偏移地址=物理地址”的寻址模式一种具体实现方案

举例

纸条通信,你问我图书馆的地址,我只能写在纸条,我必须有张容纳4位数据的纸条写2826,可是现在仅两张3位数据纸条

于是写上200 和 826两张

而你做这样的运算:200(段地址)X10+826(偏移地址) = 2826物理地址

段的概念

使人误认为内存被划分一个个段,每个段有个段地址,这样是错误的认识

内存并没有分段,段的划分来自CPU,8086CPU用”基础地址(段地址X16) + 偏移地址 = 物理地址”
的方式给出内存单元的物理地址, 使得我们可以分段管理内存

以后编程可以根据需要,将若干地址连续的内存看作一个段,用段地址X16定位段的起始地址(基础地址),用偏移地址定位段中的内存单元

段寄存器

8086CPU有四个段寄存器:CS、DS、SS、ES
当8086CPU要访问内存时由着4个段寄存器提供内存单元的段地址

CS和IP

CS和IP是8086cpu最关键的寄存器

假设任意时刻,CS中的内容为M,IP中的内容为N,8086CPU将从内存MX16 + N单元开始,读取一条指令并执行,即CPU将CS:IP指向的内容当作指令执行

以下组图展示了8086cpu读取和执行一条指令的过程








下面的组图是以2.19为初始状态,展现8086CPU继续读取、执行3条指令的过程
忽略了读每条指令的细节



8086工作过程简要描述如下

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

修改CS、IP的指令

8086提供了修改AX值的指令

可以用mov指令,如mov ax,123 将ax中的值设为123
显然可以设置其他寄存器的值,如mov bx,123; mov cx,123; mov dx,123等

mov指令称为传送指令,但不能用于设置CS,IP的值,因为8086cpu没有提供这样的功能,8086cpu为CS,IP提供了另外的指令改变他们的值

能够修改CS,IP内容的指令被称为转移指令:jmp指令

若同时修改CS,IP的内容,可用形如”jmp 段地址:偏移地址”的指令完成 如

1
2
jmp 2AE3:3 执行后: cs=2AE3H, ip=0003H,cpu将从2AE33H处读取指令
jmp 3:OB16 执行后: cs=0003H, ip=0B16H,cpu将从0OB46H处读取指令

如果仅仅想修改IP则可以如下

代码段

8086PC机,编程时,可以根据需要将一组内存单元定义为一个段,长度为N(N小于等于64kb)的一组代码,存在一组地址连续,起始地址为16的倍数的内存单元中,可以认为这段内存是用来存放代码的,从而定义一个代码段

这段长度10个字节的代码,存放在123B0H~123B9H的一组内存单元中

如何使得代码段指令执行?

将一段内存当代码段,CPU却不知道这样做,CPU只认CS:IP指向的内存单元中的内容为指令

所以要让CPU执行我们放在代码段的指令,就将CS:IP指向所定义的代码段中第一条指令首地址

于是设CS=123BH,IP=000H即可

寄存器(内存访问)

内存中字的存储

CPU中用16位寄存器存储一个字,高8位存放高位字节,低8位存放低位字节,由于内存单元是字节单位(一个单元存放一个字节),则一个字需要两个地址连续的内存单元存放

这个字的低位字节存放在低位地址单元中,高位字节存放在高地址单元中

如存放数据20000(4E20H),运用了0,1两个内存单元用来存储一个字,这两个单元
0号是低地址单元,1是高地址单元

又比如存放数据18(0012H),则3是高位存放00,2是低位存放12

DS和ADDRESS

cpu要读写一个内存单元的时候,必须先给出这个内存单元的地址
0806PC中内存地址由段地址+偏移地址组成

所以8086cpu中有一个DS寄存器,通常用来存放访问数据的段地址

1
2
3
mov bx,1000H
mov ds,bx
mov al,[0]

上面3条指令将10000H中的数据读到了al中

mov指令,可完成两种传送

  • 将数据直接送入寄存器
  • 将一个寄存器中的内容送入另一个寄存器

“[…]”表示一个内存单元,”[…]”中的0表示内存单元的偏移地址

只有偏移地址不能定位一个内存单元,得知道段地址, 8086CPU自动取ds中的数据为内存单元的段地址

如此再来看mov bx, 10000H指令中的mov指令从10000H中读取数据,10000H用段地址和偏移地址表示为1000:0,
再将段地址1000H放入ds,然后mov al,[0]完成传送,mov指令中的[]说明操作对象是一个内存单元,[]中的0说明这个内存单元的偏移地址是0,它的段地址默认放在ds中,指令执行时,8086cpu自动从ds中取出

内存中字的传送

mov指令在寄存器和内存之间进行字节型数据的传送,因为8086CPU有16位结构,有16根数据线,也就是一次性传送一个字,mov指令只需给出16位的寄存器就可以进行16位数据的传送了

相关问题

第一题

第二题


mov、add、sub指令

mov指令的以下几种形式

add和sub指令同mov一样,都有两个操作对象

1
2
3
4
5
add 是后方+前方 结果赋值 前方
sub和add类似,不过是相减,
sub ax,9 给ax减9,之后的结果赋值给ax
sub ax,bx 语意是ax = bx - ax
sub ax,[0] 将偏移地址为0的内存单元 - ax 再赋值给ax

数据段

8086PC在编程时,可根据需要将一组内存单元定义为一个段,可以将一组长度为N(N小于等于64kb),
地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段

相关问题

第一题

入栈方式

出栈方式

CPU提供的栈机制

现如今的cpu都有栈的设计,8086CPU提供相关指令来以栈的方式访问内存空间
这意味着可以把一段内存当做栈来使用

下面举例将10000H~1000FH这段内存当做栈来使用

push指令和pop指令,将对这段空间按后进先出的规则进行访问

但是CPU如何知道10000H~1000FH这段空间被当做栈来使用?

push ax等入栈指令执行,要将寄存器中的内容放入当前栈顶单元的上方成为新的栈顶元素
pop ax等指令执行,要从栈顶单元中取出数据,送入寄存器
显然push、pop在执行的时候,必须知道哪个单元是栈顶单元,可是如何知道呢?

CS,IP存放着当前指令的段地址和偏移地址,CPU如何知道栈顶的位置?
显然也应该有响应的寄存器来存放栈顶地址, 8086CPU有两个寄存器

  • SS寄存器,栈顶的段位置存放在SS
  • SP寄存器,偏移地址存放在SP中

任意时刻,SS:SP指向栈顶元素,push和pop指令执行时,CPU从SS和SP中得到栈顶地址

栈顶超界问题

用SS和SP指示栈顶的地址,并提供push和pop指令实现入栈和出栈

如何保证栈顶不超过栈空间

只对8086CPU来说,它只考虑当前的情况,当前的栈顶在何处,当前要执行的指令是哪一条,他们都无从知晓

push、pop指令

相关问题

第一题

栈段

8086PC编程可以根据需要,将一组内存单元定义为一个段,长度为N(N小于等于64kb)的一组连续、起始地址为16的倍数的内存单元,当做栈空间,从而定义了一个栈段

将SS:SP指向我们定义的栈段即可

第一个程序

一个源程序从写出到执行的过程

源程序

汇编语言源程序,包含两种指令,一种是汇编指令,另一种是伪指令

汇编指令是有对应机器码的指令,可以编译成机器指令,最终为cpu执行的

而伪指令没有对应的机器指令,最终不被cpu所执行,伪指令由编译器来执行的指令,编译器根据伪指令进行相关的编译工作

1
2
3
segment和ends是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时必须要用到的一对伪指令  

segment和ends的功能是定义一个段,segment说明一个段开始,ends说明一个段结束

汇编程序拥有多个段,用来存放代码、数据或当作栈空间使用

assume这条伪指令含义为”假设”,假设某一段寄存器和程序中的某个用segment..ends相关联

程序结构

[BX]和loop指令

[BX]

[0]表示一个内存单元,0表示单元的偏移地址,段地址默认在ds中

[bx]同样也表示一个内存单元,它的偏移地址在bx中



loop指令

loop指令格式 loop 标号,cpu执行loop指令的时候,要进行两步操作

  • cx = cx - 1

  • 判断cx的值,不为零则转至标号处执行程序

于是我们编写这样的loop简化了程序

[bx]和loop联合应用

考虑这样的问题,计算ffff:0~ffff:b单元中的数据的和,结果存储在dx中

这样写的过长了,可以简化成这样

段前缀

mov ax,[bx]内存单元的偏移地址由bx给出,而段地址默认在ds中,我们可以在访问内存单元的指令中显式地给出内存单元的段地址所在的段寄存器

例如

段前缀的使用

原程序

改进后程序

一段安全的空间

在8086模式中,随意向一段内存空间写入内容是很危险的,因为这段空间可能存放着重要的系统数据或代码

1
2
3
4
mov ax,1000h
mov ds,ax
mov al,0
mov ds:[0],al

此指令做法是不合理的,因为之前并没有论证过1000:0中是否存放着重要的系统数据或代码

如果1000:0中存放着重要的系统数据或代码,那么mov ds:[0],al将引发错误

所以总结如下

  • 我们需要直接向一段内存中写入内容
  • 这段内存空间不应存放系统或其他程序的数据或代码,否则写入操作很可能引发错误
  • DOS方式下,一般情况,0:200~0:2ff空间中没有系统或其他程序的数据或代码
  • 以后我们需要直接向一段内存中写入内容,就是用0:200~0:2ff这段空间

包含多个段的程序

代码段中使用数据

第五章内说过,0:200~0:2ff是相对安全的,可这段空间的容量只有256个字节,如果需要的空间超过26个字节怎么办?

在操作系统环境合法地通过操作系统取得的空间都是安全地,因为操作系统不会让一个程序所用的空间和其他程序以及系统自己的空间相冲突

当可执行文件中的程序被加载入内存时,这些数据也同时被加载入内存,与此同时,要处理的数据也就自然而然地获得了存储空间

dw的含义是定义字型数据,dw即”define word”

程序中要对8个数据进行累加, 程序运行的时候CS中存放代码段的段地址,所以可以从CS中得到他们的代码段,偏移地址是多少?

因为dw定义数据处于代码段最开始,所以偏移地址为0

这8个数据在代码段的偏移0、2、4、6、8、A、C、E处,程序运行时,它们的地址就是CS:0,CS:2,CS:4,CS:6,CS:8,CS:A,CS:C,CS:E

程序中,用bx存放加2的递增偏移地址,用循环累加

代码段中使用栈

利用栈将程序中定义的数据逆序存放

可以在程序中通过定义数据来取得一段空间,然后将这段空间当做栈空间来用

程序代码如下

dw定义了16个数据,即在程序中写入了16个字型数据,而程序在加载后,将用32个字节的内存空间来存放他们,这段内存空间是我们所需要的,程序将它用作栈空间

数据、栈、代码放入不同段

程序中用到了数据和栈,将数据、栈和代码都放到了一个段里面,在编程的时候注意何处是数据,何处是栈,何处是代码

如果数据、栈和代码需要的空间超过64kb,就不能放在一个段中,因为一个段容量不能大于64kb,这64kb只是8086模式的限制,并不是所有都是如此

于是以下代码就将数据、栈和代码放到了不同的段中


1:定义多个段的方法

只是对于不同的段,要有不同的段名

2:对段地址的引用

限制程序中有多个段,如何访问段中数据,通过段地址和偏移地址

mov ax,data将名称为data的段的段地址送入ax,一个段中的数据的段地址可由段名代表,偏移地址就看它在段中的位置

更灵活的定位内存地址方法

  • and指令:逻辑与指令,按位进行与运算

  • or指令:逻辑或指令,按位进行或运算

and

1
2
3
4
mov al, 01100011B  
and al, 00111011B

;al = 00100011B

or

1
2
3
4
mov al, 01100011B  
or al, 00111011B

;al = 01111011B

ASCII码

将符号变为二进制信息,如61H表示”a”,62H表示”b”

以字符形式给出的数据


上面的源程序 “db ‘unix”相当于”db 75H,6EH,49H,58H”

u,n,I,X的ASCII码分别为75H,6EH,49H,58H

大小写转换问题

“A”的ASCII码为41H,”a”的ASCII码为61H,改变大小写就是改变所对应的ASCII码,所以只要对比一下就能找到减20就成小写转大写,加20就从大写转小写

[bx+idata]

我们用[bx]的方式来指明一个内存单元,还可以用更灵活的方式来指明内存单元,

bx+idata]表示一个内存单元

它的偏移地址就是(bx)+idata (bx中的数值加上idata)

数字描述为:(ax) = ((ds) * 16 + (bs) + 200)

案例问题

用[bx+idata]的方式进行数组处理

以下将数据段的第一个字符串转大写,第二个字符串转小写

现在有了[bx+idata]的方式,可以更加简化我们的程序

SI和DI

si和di是8086cpu中和bx功能相近的寄存器

下面三组实现了相同的功能

下面三组也实现了相同的功能

[bx+si]和[bx+di]

在之前我们用[bx(si或di)]和[bx(si或di)+idata]的方式来指明一个内存单元,我们还可以用更灵活的方式:[bx+si]和[bx+di]

1
2
3
mov ax,[bx+si]
;数字化描述
;(ax) = ((ds)*16 + (bx) + (si))

案例


[bx+si+idata]和[bx+di+idata]

[bx+si+idata][bx+di+idata]的含义相似

[bx+si+idata]表示一个内存单元,偏移地址为(bx)+(si)+idata

1
2
3
;指令
mov ax,[bx+si+idata]
;数字化描述为 (ax) = ((ds)*16 + (bx) + (si) + idata)

案例

不同寻址方式的灵活应用

总结

案例

datasg中定义了6个字符串,每个长度为16个字节(注意,为了直观字符串后面加上了空格,满足长度16)

所以这6个字符是一个6行16列的二维数组

bx,si,di和bp

  • 8086cpu只有这四个寄存器可以用在”[…]”中进行内存单元寻址

这四个寄存器可以单个出现,或以4种组合出现

机器指令处理的数据在什么地方

处理大致分为:读取,写入,运算

所处理的数据可以再3个地方:CPU内部、内存、端口

数据位置的表达

立即数

包含在机器指令中的数据(执行前在CPU的指令缓冲器),在汇编语言中称”立即数”

寄存器

指令要处理的数据在寄存器,在汇编指令中给出相应的寄存器名

段地址(SA)和偏移地址(EA)

指令要处理的数据在内存中,在汇编指令中可用[X]的格式给出EA,SA在某个段寄存器

例如

寻址方式

当数据放在内存中的时候,多种方式来给定这个内存单元的偏移地址,这类定位内存单元的方法称为寻址方式

指令处理的数据长度

8086cpu指令可以处理两种尺寸的数据:byte和word

1:用寄存器名来指明处理数据的尺寸

2:用操作符X ptr指明内存单元的长度,X在汇编指令中可以为word或byte

案例

div指令

  • 除数:有8位和16位两种,在一个reg或内存单元中

  • 被除数:默认放在AX或DX和AX中,如果除数是8位,被除数则为16位,默认在AX中存放:如果除数为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位

  • 结果:如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数

格式如下

1
2
div reg
div 内存单元

伪指令dd

db和dw定义字节型数据和字型数据,dd是用来定义dword(double word 双字)型数据

1
2
3
4
5
6
7
data segment

db 1
dw 1
dd 1

data ends
  • 第一个数据为01H,在data:0处,占1个字节
  • 第二个数据为0001H,在data:1处,占1个字
  • 第三个数据为00000001H,在data:3处,占两个字

案例

dup

dup是一个操作符,在汇编语言同db、dw、dd等一样,也是由编译器识别处理的符号

它是和db、dw、dd等数据定义伪指令配合使用的,用来进行数据的重复

1
2
3
4
5
6
db 3 dup (0)
;相当于 db 0,0,0


db 3 dup(0, 1, 2)
;定义了9个字节 他们是0、1、2、0、1、2、0、1、2相当于db 0,1,2,0,1,2,0,1,2

db 重复次数 dup (重复的字节型数据)
dw 重复次数 dup (重复的字型数据)
dd 重复次数 dup (重复的双字型数据)

转移指令原理

可以修改ip,或同事修改CS和IP的指令统称为转移指令

8086cpu的转移行为有以下几类

  • 只修改IP时,称为段内转移,比如 jmp ax
  • 同时修改CS和IP时,称为段间转移,比如 jmp 1000:0

由于转移指令对IP修改范围不同,段内转移又分为:短转移和近转移

  • 短转移IP的修改范围-128~127
  • 近转移IP的修改范围-32768~32767

转移指令分以下几类

  • 无条件转移如jmp
  • 条件转移指令
  • 循环指令如loop
  • 过程
  • 中断

操作符offset

功能是取得标号的偏移地址

上面程序 offset操作符取得标号start和s的偏移地址0和3

案例

jmp指令

无条件转移指令

jmp指令要给出两种信息:

  • 转移的目的地址
  • 转移的举例(段间转移、段内短转移、段内近转移)

依据位移进行转移的jmp指令

jmp short 标号 (转到标号处执行指令)

这种格式是段内短转移,对IP修改范围是-128~127

转移地址在指令中的jmp指令

jmp指令,其对应的机器指令并没有转移的目的地址,相当于当前IP的转移位移

jmp far ptr 标号

段间转移,又称远转移

功能如下:
CS=标号所在段的段地址; IP=标号在段中的偏移地址

far ptr 指明了指令用标号的段地址和偏移地址修改CS和IP

案例

转移地址在寄存器中的jmp指令

指令格式: jmp 16位 reg
功能: IP=(16位 reg)

转移地址在内存中的jmp指令

  • jmp word ptr 内存单元地址(段内转移)

功能:从内存单元地址处开始存放着一个字,是转移的目的偏移地址

  • jmp dword ptr 内存单元地址(段间转移)

功能:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址

jcxz指令

jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的唯一,而不是目的地址,对IP的修改范围是-128~127

指令格式: jcxz 标号(如果(cx)=0,转移到标号处执行)

操作 当(cx)=0时,(IP)=(IP)+8位位移
当(cx) 不等0时,什么也不做

jcxz 标号功能相当于如下

1
if((cx) == 0) jmp short 标号;

loop指令

指令格式: loop 标号((cx) = (cx) - 1,如果(cx)不等0,转移到标号处执行)

  • (cx)= (cx) - 1
  • 如果(cx)不等0,(ip)=(ip)+8位位移

loop 标号的功能相当于如下

1
2
(cx)--;
if((cx)!=0) jmp short 标号;

CALL和RET指令

call和ret指令都是转移指令,都修改IP,或同时修改CS和IP,经常被共用来实现子程序的设计

ret和retf

ret指令用栈中的数据,修改IP的内容,从而实现近转移
retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移

call指令

当cpu执行call指令时,进行两步操作

  • 将当期的IP或CS和IP压入栈中
  • 转移

依据位移进行转移的call指令

call 标号 (将当期的IP压栈后,转到标号处执行指令)

CPU执行此种格式的call指令时,进行如下操作

  • (sp) = (sp) - 2
    ((ss)*16 + (sp)) = (IP)

  • (IP) = (IP)+16位 位移

16位位移=标号处的地址-call指令后的第一个字节的地址
16位位移的范围为-32768~32767,用补码表示
16位位移由编译程序在编译时算出

所以CPU执行”call 标号”时,相当于进行

1
2
push IP
jmp near ptr 标号

转移地址在指令中的call

转移地址在寄存器中的call

转移地址在内存中的call

call和ret的配合使用

mul指令

mul乘法指令注意以下两点

  • 两个相乘的数:两个相乘的书,要么都是8位,要么都是16位,如果是8位,一个默认放在AL中,另一个放在8位的reg或内存字节单元中;如果是16位,一个默认在AX中,另一个放在16位reg或内存字单元中

  • 结果:如果是8位乘法,结果默认在AX;如果是16位乘法,结果高位默认在DX中存放,低位在AX中放

格式如下:

1
2
mul reg
mul 内存单元

内存单元可以用不同的寻址方式给出,比如

1
2
3
4
5
6
7
8
mul byte ptr ds:[0]
;含义为 ax = ax*(ds*16 + 0)


mul word ptr [bx+si+8]

;含义为 ax = ax*(ds*16 + bx + si + 8)的结果低16位
; dx = ax*(ds * 16 + bx + si + 8)的结果高16位

案例

计算100 * 10000

100小于255,可10000大于255, 所以要做16位乘法

计算100 * 10

10和100都小于255,做8位乘法

参数和结果传递的问题

子程序一般都要根据提供的参数处理一定的事务,处理后将结果(返回值)提供给调用者,

如何存储子程序需要的参数和产生的返回值?

用寄存器来存储,将参数放到bx中;因为子程序要计算,可将结果放到dx和ax

案例

批量数据的传递

子程序只有一个参数,放bx中,如果有两个参数,那么可以放两个寄存器,可是如果3个,4个甚至更多怎么办?

寄存器的数量还是有限的,对于返回值也是有同样的问题

这个时候,将批量数据放到内存中,然后将他们所在的内存空间的首地址放在寄存器中,传递给需要的子程序,对于具有批量数据的返回结果,也可用同样的方法

案例

将一个全是字母的字符串转化为大写

标志寄存器

CPU内部寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)

  • 用来存储相关指令的某些执行结果
  • 用来为CPU执行相关指令提供行为依据
  • 用来控制CPU的相关工作方式

这类特殊寄存器被称为标志寄存器

我们已经使用过了ax,bx,cx,dx,si,di,bp,sp,IP,cs,ss,ds,es等13个寄存器了,标志寄存器(flag)是学习的最后一个寄存器

flag不同其他寄存器,其他寄存器是用来存放数据,都是整个寄存器具有一个含义的,而flag寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息

flag的1、3、4、12、13、14、15位在8086CPU中没有使用,不具有任何含义
而0、2、4、6、7、8、9、10、11位具有特殊的含义

ZF 标志

flag的第六位是ZF,零标志位,记录相关指令执行后,其结果是否为0

1
2
mov ax,1
sub ax,1

如果结果为0,那么zf=1;如果结果不为0,那么zf=0

计算机中1表逻辑真,表肯定,所以当结果为0时zf=1,表示结果为0

如果结果不为0,zf要记录下”不是0”这样的否定信息,在计算机中0表示逻辑假,表示否定

指令集中影响标志寄存器的如下

  • add
  • sub
  • mul
  • div
  • inc
  • or
  • and等

大多数运算指令

对标志寄存器不影响的是

  • mov
  • push
  • pop

大多是传送指令

PF标志

flag的第2位是PF,奇偶标志位,记录相关指令执行后

起结果的所有bit位中1的个数是否为偶数,如果为偶数pf=1,如果为奇数,那么pf=0

SF标志

flag的第7位是SF,符号标志位,记录相关指令执行后,其结果是否为负

如果结果为负,sf=1,如果非负,sf=0

计算机都是补码来表示有符号数据,计算机中的一个数据可以看做有符号数或无符号数

SF标志就是CPU对有符号数运算结果的一种记录,记录数据的正负,在将数据当做有符号数来运算的时候,可以通过它来得知结果的正负

如果通过无符号数来运算,则SF的值无意义

案例


CF标志

flag的第0位是CF,进位标志位,一般情况下,在进行无符号数运算的时候,记录了运算结果的最高有效位向更高为的进位值,或从更高为的错位值

两个数据相加,有可能产生从最高有效位向更高位的进位


两个数做减法,向更高位借位

OF标志

在进行有符号数运算的时候,如果超过了机器所能表示的范围称为溢出

指令运算的结果用8位寄存器或内存单元来放,比如add al,3,那么对于8位的有符号数据,机器所能表示的范围就是-128~127

flag的第11位是OF,溢出标志位,一般情况下,OF记录有符号数运算的结构是否发生了溢出,如果发生溢出OF=1,如果没有,OF=0

adc指令

adc是带进位加法指令,利用了CF标志上记录的进位值

指令格式

1
abc 操作对象1, 操作对象2

功能:操作对象1 = 操作对象1 + 操作对象2 + CF

案例

sbb指令

sbb是带借位减法指令,利用了CF标志上记录的进位值

指令格式

1
sbb 操作对象1,操作对象2

功能:操作对象1 = 操作对象1 - 操作对象2 - CF

案例

cmp指令

cmp是比较指令,cmp的功能相当于减法指令,只是不保存结果

cmp指令执行后,将对标志寄存器产生影响

指令格式

1
cmp 操作对象1,操作对象2

功能:计算操作对象1-操作对象2 但并不保存结果,仅仅根据计算结果对标志寄存器进行设置

案例

规律如下

各类条件转移指令

“转移”指的是它能修改IP,“条件”指的是它可以根据某种条件,决定是否修改IP

DF标志和串传送指令

flag 第10位的DF,方向标志位,串处理指令中,控制每次操作后si、di的增减

df = 0 每次操作后si、di递增
df = 1 每次操作后si、di递减

movsb

格式: movsb
功能:执行movsb指令相当于进行以下几步操作

1
2
3
4
5
6
7
- (es*16 + di) = (ds * 16 + si)

- 如果df = 0 则: si = si + 1
di = di + 1

df = 1 则: si = si - 1
di = di - 1

movsw

格式: movesw
功能: 将ds:si指向的内存字单元中的字送入es:di中,然后根据标志寄存器df位的值,将si、di递增2或递减2

内中断

检测到从CPU外部发送过来的或内部产生的一种特殊信息,并且可以立即对所接收到信息进行处理,这类特殊信息,称其为中断信息

cpu内部产生中断信息情况如下

  • 除法错误,比如执行div指令产生除法溢出 0中断类型码
  • 单步执行 1中断类型码
  • 执行into指令 4中断类型码
  • 执行int指令,指令格式 int n, n为字节型立即数,提供CPU中断类型码

中断处理程序

CPU收到中断信息,如何对中断信息处理,由编程决定,用来处理中断信息的程序被称为中断处理程序

如8086cpu执行某处的程序,就要将CS:IP执行它的入口(即程序第一条指令的地址)

首要问题,cpu在收到中断信息后,如何根据终端信息确定处理程序入口

CPU设计者必须在中断信息与其处理程序的入口地址之间建立某种联系,使得CPU根据中断信息找到要执行的处理程序

中断向量表

CPU用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址

中断向量表在内存中保存,存放着256个中断源所对应的中断处理程序入口

可见CPU只要知道了中断类型码,就可以定位程序的入口地址

中断过程

我们通过中断向量表获得入口地址,用它设置CS和IP,使CPU执行中断处理程序

CPU在执行完中断处理程序后,应该返回原来的执行点继续执行下面的指令,所以在中断过程中,在设置CS:IP之前,还要将原来的CS和IP值保存起来,在使用call指令调用子程序时有同样的问题,子程序执行后还要返回原来的执行点继续执行,所以call指令先保存当前CS和IP值,然后再设置CS和IP值

中断过程如下

  • 从终端信息中获取中断类型码

  • 标志寄存器的值入栈(因为中断过程要改变标志寄存器的值,所以先将保存在栈中)

  • 设置标志寄存器的第8位TF和第9位IF的值0

  • CS的内容入栈

  • IP的内容入栈

  • 从内存地址中断类型码4和中断类型码4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS

中断处理程序和iret指令

由于CPU随时都处理中断程序,所以中断处理程序必须一直存储在内存某段空间之中,而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表表项中

iret通常和硬件自动完成的中断过程配合使用,寄存器入栈的顺序是标志寄存器、CS、IP
而iret出栈顺序是IP、CS、标志寄存器,刚好对应

实现了用于执行中断程序钱CPU线程恢复标志寄存器和CS、IP的工作

iret指令执行后,cpu又回到执行中断前继续执行程序

int指令

int指令格式:int n,n为中断类型码,功能是引发中断过程

编写中断例程