以下所述的汇编指令均以8086CPU为基础所讲述。
1、基础
1.1、机器语言
如果要介绍汇编语言,首先要说一下机器语言,机器语言的定义如下:
机器语言是机器指令的集合,机器指令就是一台机器可以正确执行的命令,计算机的机器指令是一列二进制数字,计算机将之转变为一列高低电平,以驱动计算机硬件进行运算。
每一种CPU都有自己的指令集,早期程序员通过用0、1数字编写的程序打在纸带或卡片上,然后输入计算机完成运算。
1.2、汇编语言
正是由于机器语言的难以辨别和记忆,于是就产生了汇编语言。
程序员通过汇编代码编写程序,通过汇编编译器,最终还是转化为机器语言,最终让计算机执行。
编写汇编代码 –> 汇编编译器(编译) –> 机器码 –> 计算机(执行)
汇编有3类指令:
- 汇编指令:机器码助记符,是汇编语言的核心,有对应的机器码
- 伪指令:由编译器执行,没有对应机器码
- 其他符号:如+、-、*、/,编译器识别,没有对应机器码
1.3、存储器与总线
CPU要想执行指令,必须由存储器(内存)提供指令和数据。
指令和数据在内存或磁盘上没有任何区别,都是二进制信息。
内存的一个存储单元可以存储1个字节,即8个二进制位。
CPU要想进行数据的读写,必须进行下面3类信息交互:
- 存储单元的地址(地址信息)
- 读/写的命令(控制信息)
- 读/写的数据(数据信息)
CPU与存储器分别通过地址总线、控制总线、数据总线进行信息交互。
- 地址总线:地址总线宽度决定CPU寻址能力
- 控制总线:控制总线决定CPU对其他器件的控制能力
- 数据总线:数据总线宽度觉得CPU与其他器件数据传送一次数据传送量
2、存储器
2.1、内存地址空间
假如一个CPU地址总线宽度为10,那么可寻址1024个内存单元,这1024个内存单元就是这个CPU的内存地址空间。
2.2、控制外设
在计算机系统中,所有可用程序控制其工作的设备,都必须受到CPU的控制。
但是CPU不能直接控制外设,直接控制这些设备的是插在扩展插槽的接口卡。
这些扩展插槽通过总线与CPU相连,即接口卡通过总线连接CPU,CPU控制接口卡,从而实现CPU对外设的间接控制。
CPU –> 接口卡 –> 外设
2.3、RAM和ROM
存储器分为随机存储器(RAM)和只读存储器(ROM),使用如下:
- RAM:存放CPU使用的程序和数据
- 装有BIOS的ROM
- 接口卡上的RAM
不管是上述的哪种类型,CPU控制它们的时候,都是当做内存对待,即当做由若干存储单元组成的逻辑存储器,这个逻辑存储器就是内存地址空间。
3、寄存器
寄存器是CPU中程序员可以读写的部件,通过改变寄存器的内容来实现对CPU的控制。
8086CPU有14个寄存器:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW
。
3.1、通用寄存器
其中AX、BX、CX、DX
是通用寄存器,存放一般性数据。1个16位寄存器可以存储16位的数据。
为了向上一代CPU兼容,这4个寄存器可以分为AH、AL,BH、BL,CH、CL,DH、DL
。
3.2、物理地址
CPU要想访问内存单元,就要给出内存单元的物理地址,内存是一个一维的线性空间,每一个内存单元都有唯一的地址,这个地址就是物理地址。
8086CPU是16位结构的,其数据总线是16位宽,但地址总线是20位,1MB寻址能力。
但内部是16位结构,内部一次性处理、传输地址为16位,所以在内部采用2个16位地址合成方式形成一个20位的物理地址进行内存寻址。
物理地址 = 段地址 * 16 + 偏移地址(段地址*16即左移4位)
其本质含义是:CPU在访问内存时,用一个基础地址(段地址*16)和相对于基础地址的偏移地址相加,得到物理地址。
由于偏移地址为16位,16位地址的寻址能力是64KB,所以一个段的长度最大为64K。
CPU可以用不同的段地址和偏移地址形成同一个物理地址。
3.3、段寄存器
8086CPU有4个段寄存器:CS、DS、SS、ES
。
CS
为代码段寄存器,IP
为指令指针寄存器。
任意时刻,CPU将CS:IP指向的内容当做当前指令执行。
8086CPU工作过程:
- 从CS:IP指向的内存单元读指令,指令进指令缓冲器
- IP指向下一条指令
- 执行指令,跳到步骤1,重复这个过程
CPU根据什么将内存中的信息看做指令?答案是CPU将CS:IP指向的内存单元的内容看做指令。
使用jmp 段地址:偏移地址
或jmp 寄存器
修改CS:IP或IP的地址。
在编程时,根据需要可以将长度为N的一组代码,存在一组连续的内存单元中,这段内存单元叫做代码段。
同样的,可以根据需要将长度为N的一组内存单元,专门存储数据,这段内存单元叫做数据段。
8086CPU自动取DS
寄存器中的数据为内存单元的段地址。
例如mov [0] cs
表示将寄存器cs
的值复制到段地址为DS便宜地址为0的内存单元中。
字在内存中存储时,用2个地址连续的内存单元存放,字的低字节放在低地址单元,高字节放在高地址单元。
3.4、栈
栈是一种特殊的访问方式的存储空间,特点是:后进数据先出去。
8086CPU提供入栈和出栈的指令:PUSH
和POP
。
任意时刻,寄存器SS:SP
指向栈顶元素。
push指令执行:
- SP=SP-2
- 向SS:SP指向的字单元送入数据
pop指令执行:
- 从SS:SP指向的字单元读数据
- SP=SP+2
在编程时,要时刻注意栈顶越界的问题。
用栈来可以暂存以后需要恢复的寄存器中的内容。
push和pop
实质上是一种内存传送指令。
同样的,编程时我们可以将长度为N的一组连续内存单元当做栈空间使用,叫做栈段。
一个栈段的容量最大为64KB。
3.5、标志寄存器
标志寄存器作用:
- 存储相关指令某些执行结果
- 为CPU执行相关指令提供行为依据
- 控制CPU相关工作方式
8086CPU中有个叫flag寄存器,共16位,每一位代表专门的含义:
- 第6位
ZF
,零标志位:相关指令执行后,结果为0则zf=1,不为0则zf=0 - 第2位
PF
,奇偶标志位:相关指令执行后,结果所有bit位中1的个数为偶数,pf=1,否则pf=0 - 第7位
SF
,符号标志位:相关指令执行后,结果是否为负,结果为负sf=1,否则sf=0 - 第0位
CF
,进位标志位:对于无符号数运算,CF记录运算结果最高有效位向更高位的进位值/借位值 - 第11位
OF
,溢出标志位:对于有符号数运算,是否发生溢出,溢出则of=1,否则of=0 - 第10位
DF
,方向标志位:在串处理指令中,控制操作后si、di增减,df=0每次操作后si、di递增,否则递减
4、汇编程序
4.1、基础
汇编程序包括:
- 伪指令:编译器执行,没有对应机器码
- 汇编指令:编译器编译成机器码
伪指令包括:
- XXX segment … XXX ends:定义一个段
- end:结束标记
- assume:关联某一寄存器和一个段
一个汇编程序中,所有将被计算机所处理的信息:指令、数据、栈,被划分到不同的段中。
汇编程序的最后一定要有返回:
1 | mov ax,4c00H |
汇编程序从写出到执行的过程:
1 | 编程 --> 1.asm --> 编译 --> 1.obj --> 连接 --> 1.exe --> 加载 -->内存中的程序 --> 运行 |
“()”中的元素可以有3种:
- 寄存器名
- 段寄存器名
- 内存单元物理地址
4.2、寻址方式
寻址方式分以下几种:
寻址方式 | 含义 | 名称 | 举例 |
---|---|---|---|
[idata] | EA=idata;SA=(ds) | 直接寻址 | [idata] |
[bx] | EA=(bx);SA=(ds) | 寄存器间接寻址 | [bx] |
[si] | EA=(si);SA=(ds) | 寄存器间接寻址 | [si] |
[di] | EA=(di);SA=(ds) | 寄存器间接寻址 | [di] |
[bp] | EA=(bp);SA=(ss) | 寄存器间接寻址 | [bp] |
[bx+idata] | EA=(bx)+idata;SA=(ds) | 寄存器相对寻址 | 结构体:[bx].idata ; 数组:idata[si],idata[di] ; 二维数组:[bx][idata] |
[si+idata] | EA=(si)+idata;SA=(ds) | 寄存器相对寻址 | 同上 |
[di+idata] | EA=(di)+idata;SA=(ds) | 寄存器相对寻址 | 同上 |
[bp+idata] | EA=(bp)+idata;SA=(ss) | 寄存器相对寻址 | 同上 |
[bx+si] | EA=(bx)+(si);SA=(ds) | 基地变址寻址 | 二维数组:[bx][si] |
[bx+di] | EA=(bx)+(di);SA=(ds) | 基地变址寻址 | 同上 |
[bp+si] | EA=(bp)+(si);SA=(ss) | 基地变址寻址 | 同上 |
[bp+di] | EA=(bp)+(di);SA=(ss) | 基地变址寻址 | 同上 |
[bx+si+idata] | EA=(bx)+(si)+idata;SA=(ds) | 相对基址变址寻址 | 结构中的数组:[bx].idata[si] ; 二维数组:idata[bx][si] |
[bx+di+idata] | EA=(bx)+(di)+idata;SA=(ds) | 相对基址变址寻址 | 同上 |
[bp+si+idata] | EA=(bp)+(si)+idata;SA=(ss) | 相对基址变址寻址 | 同上 |
[bp+di+idata] | EA=(bp)+(di)+idata;SA=(ss) | 相对基址变址寻址 | 同上 |
在需要暂存数据时,一般都使用栈。
4.3、循环
loop指令执行步骤:
- (cx)=(cx)-1
- cs不为0跳转至标号处执行,为0向下执行
cx
中存放循环次数
1 | mov cx,循环次数 |
在编写汇编程序时,在DOS方式下,要使用0:200~0:2ff内存空间,这段空间是空闲的。
4.4、定义数据
mov ax,[bx]
默认段地址在ds
中,如果不想放在ds
中,可以使用段前缀例如ds:
、cs:
、ss:
、es:
来显示指定段地址所在的段寄存器。
- 用
dw
定义字型数据,如dw 0123h
- 用
dd
定义双字型数据,如dd 1
- 用
db
定义字符数据,如db 'unix'
- 用
dup
表示数据的重复,如dd 3 dup (0)
相当于db 0,0,0
寄存器di
和si
功能同bx
,这2个寄存器不能分成2个8位寄存器使用,例如以下方式实现相同功能:
1 | mov bx,0 |
可以在程序中定义多个段,用于定义数据和栈:
1 | assume cs:code,ds:data,ss:stack |
4.5、数据存放位置和大小
2个问题:
- 机器指令处理的数据在什么地方?
- 指令执行的前一刻
- 数据只会存在3个地方:CPU内部、内存、端口
- 要处理的数据有多长?
- 可以用
word ptr
或byte ptr
表示字和字节型数据
- 可以用
div
指令表示除法:
- 除数:分8位和16位两种
- 被除数:默认在ax或dx和ax中
- 除数是8位,被除数为16位,默认在ax中
- 除数是16位,被除数为32位,则在dx和ax中,dx放高16位,ax放低16位
- 结果:
- 除数是8位,商放在al中,余数放在ah中
- 除数是16位,商放在ax中,余数放在dx中
4.6、转移指令
修改IP或修改CS、IP的指令统称为转移指令。
根据范围划分:
- IP修改范围-128~127:短转移
- IP修改范围-32768~32767:近转移
转移指令分类:
- 无条件转移指令(jmp)
- 条件转移指令
- 循环指令(loop)
- 过程
- 中断
操作符
offset
表示去的标号的便宜地址,由编译器处理。
jmp指令转移分为以下几类:
- jmp short 标号(转到标号处执行指令)(段内短转移):(IP)=(IP)+8位位移
- jmp near ptr 标号(段内近转移):(IP)=(IP)+16位位移
- jmp far pt 标号(段间转移):(CS)=标号所在段地址 (IP)=标号所在段中的偏移地址
- jmp 16位寄存器:(IP)=(16位寄存器)
- jmp word ptr 内存单元地址(段内转移):内存单元地址开始存放一个字作为转移的偏移地址
- jmp dword ptr 内存单元地址(段间转移):内存单元地址开始存放两个字,高地址处的字为段地址,低地址为偏移地址
jcxz指令:
- 条件转移指令,短转移
- jcxz 标号 =
if((cx)==0) jmp short 标号
loop指令:
- 循环指令,短转移
- loop 标号 =
(cx)--; if((cx)!=0) jmp short 标号
编译器会对转移位移进行检测,如果转移范围超出界限,则编译时报错。
ret和retf指令:
- ret指令用栈中的数据,修改IP,实现近转移
- (IP)=((ss)*16+(sp))
- (sp)=(sp)+2
- 相当于
pop IP
- retf指令用栈中的数据,修改CS和IP,实现远转移
- (IP)=((ss)*16+(sp))
- (sp)=(sp)+2
- (CS)=((ss)*16+(sp))
- (sp)=(sp)+2
- 相当于
pop IP pop CS
call指令:
- 将当前IP或CS和IP压入栈
- 转移
- 执行
call 标号
=push IP jmp near ptr 标号
- 执行
call far ptr 标号
=push CS push IP jmp far ptr 标号
- 执行
call 16位寄存器
=push IP jmp 16位寄存器
- 执行
call word ptr 内存单元地址
=push IP jmp word ptr 内存单元地址
- 执行
call dword ptr 内存单元地址
=push CS push IP jmp dword ptr 内存单元地址
call和ret命令配合使用,完成子程序的调用,支持了汇编的模块化设计。
mul指令:
- 2数相乘,要么都是8位,要么都是16位。
- 如果是8位,一个默认在
AL
中,另一个在8位寄存器或内存字节单元
- 如果是16位,一个默认在
AX
中,另一个在16为寄存器或内存字单元
- 如果是8位,一个默认在
- 结果:
- 如果是8位乘法,结果默认在
AX
中 - 如果是16为乘法,高位默认在
DX
中,低位在AX
中
- 如果是8位乘法,结果默认在
- 格式
- mul 寄存器
- mul 内存单元:
mul byte ptr ds:[0]
mul word ptr [bx+si+8]
调用子程序,参数传递:
- 用寄存器来存储参数和结果
- 用栈传递参数
在子程序开始调用之前,将子程序所用到的寄存器内容都保存到栈中,子程序返回前再恢复。
adc指令:
- 进位加法指令,利用CF位上记录的进位值
adc 操作对象1,操作对象2
=操作对象1=操作对象1+操作对象2+CF
- 例如
adc ax,bx
相当于(ax)=(ax)+(bx)+CF
CPU提供
adc
指令的目的,就是用来进行加法的第二步运算的。adc
和add
配合就可以对更大的数据进行加法运算。
sbb指令:
- 带借位减法指令,利用CF位上记录的借位值
sbb 操作对象1,操作对象2
=操作对象1=操作对象1-操作对象2-CF
- 例如
sbb ax,bx
相当于(ax)=(ax)-(bx)-CF
同理,CPU提供
sbb
指令的目的,就是用来进行减法借位运算的。
cmp指令:
- 比较指令,相当于减法指令,只是不保存结果
cmp 操作对象1,操作对象2
=操作对象1-操作对象2
,但不保存结果,只改变标识寄存器- 例如
cmp ax, ax
相当于(ax)-(ax)
,则zf=1,pf=1,sf=0,cf=0,of=0
cmp指令在比较无符号数和有符号数的比较时,结果可能不同。
无符号数比较结果条件转移指令:
指令 | 含义 | 检测标志位 |
---|---|---|
je | 等于则转移 | zf=1 |
jne | 不等于则转移 | zf=0 |
jb | 低于则转移 | cf=1 |
jnb | 不低于则转移 | cf=0 |
ja | 高于则转移 | cf=0且zf=0 |
jna | 不高于则转移 | cf=1或zf=1 |
8086CPU提供2个指令对df
位进行设置:
cld
指令:将df置0std
指令:将df置1
pushf和popf指令:
pushf
:将标志寄存器的值压栈popf
:从栈中弹出数据,送入标志寄存器- 作用:为直接访问标识寄存器提供了方法