1数据传送类指令
传送指令把数据从一个位置传送到另一个位置 除标志寄存器传送指令外,均不影响标志位
重点掌握 MOV XCHG XLAT PUSH POP LEA
指令的详细可以查看指令帮助文档,如查看指令影响的标志寄存器或者指令支持的功能
传送指令MOV
把一个字节或字的操作数从源地址传送至目的地址
指令 指令支持的功能,
Mov reg/mem,imm 立即数传送给寄存器或者主存(内存)
Mov reg/mem/seg,reg 寄存器传送给 段寄存器,或者内存,或者 寄存器
Mov reg/seg,mem 内存中的内容送给 寄存器,或者段寄存器
Mov reg/mem,seg 段寄存器送给寄存器或者给主存取内容给值
mov 注意事项
1.两个操作数的类型不一致
例如源操作数是字节,目的操作数是字,或者是相反
2.两个操作数不能是存储器
3 小心段操作寄存器(请注意,立即数不能直接给段寄存器,都是通过中转的)
3.1.立即数不能直接给段寄存器
例如 mov ds,100 (比如经过寄存器的中转)
3.2不能直接改变cs段寄存器的值
3.3段寄存器和段寄存器不能直接数据传送
交换指令XCHG
指令支持的功能
寄存器与寄存器之间对换数据
寄存器与存储器之间对换数据
不能在存储器与存储器之间对换数据
例子:
ax = 0 bx = 1
xchg ax,bx 那么此时ax的值就是1,bx就是0
换码指令XLAT
将BX指定的缓冲区中、AL指定的位移处的一个字节数据取出赋给AL
换码指令没有显式的操作数,但使用了BX和AL;因为换码指令使用了隐含寻址方式——采用默认操作数
例子:
mov bx,100h
mov al,03h
xlat ;这就相当于在内存ds:100的位置偏移3的位置获取一个字节数据赋值给al,
转变成mov 相当于 改为 mov al,ds:[dx+al],相当于下标寻址
堆栈
堆栈只有两种基本操作:进栈和出栈,对应两条指令PUSH和POP,栈的操作必须是字
PUSH
进栈指令先使堆栈指针SP减2,然后把一个字操作数存入堆栈顶部
POP
出栈指令把栈顶的一个字传送至指定的目的操作数,然后堆栈指针SP加2
堆栈作用:堆栈常用来 临时存放数据 传递参数 保存和恢复寄存器
例子:
mov ax,1122h
push ax ;sp先减去2,再把ax的值放入 ss:sp位置,
pop ax ;把栈顶的数据传送到ax中,然后sp+2
标志寄存器传送指令
标志寄存器传送指令用来传送标志寄存器FLAGS的内容,方便进行对各个标志位的直接操作
有2对4条指令 低8位传送:LAHF和SAHF
16位传送:PUSHF和POPF
LAHF :AH←FLAGS的低字节
LAHF指令将标志寄存器的低字节送寄存器AH SF/ZF/AF/PF/CF状态标志位分别送入AH的第7/6/4/2/0位,而AH的第5/3/1位任意
SAHF :FLAGS的低字节←AH
SAHF将AH寄存器内容送FLAGS的低字节 用AH的第7/6/4/2/0位相应设置SF/ZF/AF/ PF/CF标志
PUSHF :SP←SP-2 SS:[SP]←FLAGS
PUSHF指令是将标志寄存器压进堆栈,步骤是先将栈顶指针SP减2,然后标志寄存器的内容压入堆栈
POPF :FLAGS←SS [SP] SP←SP+2
POPF指令将栈顶字单元内容送标志寄存器,同时栈顶指针SP加2
地址传送指令
地址传送指令将存储器单元的逻辑地址送至指定的寄存器
有效地址传送指令 LEA ,注意不是获取存储器单元的内容,而是获取地址
指针传送指令 LDS和LES ,可以用于切换段
LDS r16,mem ;r16←mem, ;DS←mem+2
1 LDS指令将主存中mem指定的字送至r16,并将mem的下一字送DS寄存器
例子:
lds ax,ds:[1000h] ;将ds:1000位置的两个字节给ax,然后将ds:[1000+2]的位置的两个字节赋值给ds
2 LES r16,mem ;r16←mem, ;ES←mem+2
LES指令将主存中mem指定的字送至r16,并将mem的下一字送ES寄存器
例子:
lds ax,ds:[1000h] ;将ds:1000位置的两个字节给ax,然后将ds:[1000+2]的位置的两个字节赋值给es
2算术运算类指令
四则运算是计算机经常进行的一种操作。算术运算指令实现二进制(和十进制)数据的四则运算
请注意算术运算类指令对标志的影响
掌握:ADD/ADC/INC、SUB/SBB/DEC/ NEG/CMP
熟悉:MUL/IMUL、DIV/IDIV
理解:CBW/CWD、DAA/DAS、 AAA/ AAS/AAM/AAD
加法指令ADD
ADD指令将源与目的操作数相加,结果送到目的操作数
ADD指令按状态标志的定义设置相对应的标志位,如果add溢出后,则会of=1,这些都可以查找指令的帮助文档
指令支持的语法和功能
ADD reg,imm/reg/mem
ADD mem,imm/reg
带进位加法指令ADC
ADC指令将源与目的操作数相加,再加上进位CF标志,结果送到目的操作数
ADC指令按状态标志的定义相应设置
ADC指令主要与ADD配合,实现多精度加法运算
例子:
mov ax,1
mov bx,2
sub ax,bx ;此时有进位,所以影响adc的值,
adc bx,0 ;有进位加上进位的值 bx=3
增量指令INC
INC指令对操作数加1(增量)
INC指令不影响进位CF标志,按定义设置其他状态标志
例子:
mov al,0ffh ;INC指令对操作数加1(增量),al溢出, ;INC指令不影响进位CF标志,所以al=0
inc al
减法指令SUB
SUB指令将目的操作数减去源操作数,结果送到目的操作数
SUB指令按照定义相应设置状态标志
带借位减法指令SBB
SBB指令将目的操作数减去源操作数,再减去借位CF(进位),结果送到目的操作数。
SBB指令按照定义相应设置状态标志
SBB指令主要与SUB配合,实现多精度减法运算
例子:
mov ax,1
mov bx,2
sub ax,bx ;此时有进位,所以影响sbb的值,
sbb bx,0 ;有进位减去进位的值 bx=2
减量指令DEC
DEC指令对操作数减1(减量)
DEC指令不影响进位CF标志,按定义设置其他状态标志
求补指令NEG
NEG指令对操作数执行求补运算:用零减去操作数,然后结果返回操作数
求补运算也可以表达成:将操作数按位取反后加1
NEG指令对标志的影响与用零作减法的SUB指令一样
比较指令CMP
CMP指令将目的操作数减去源操作数,按照定义相应设置状态标志
CMP指令执行的功能与SUB指令,但结果不回送目的操作数
执行比较指令之后,可以根据标志判断两个数是否相等、大小关系等
例子:
mov ah,1
cmp ah,1
je LABEL ;相等就跳转
乘法指令(指令执行周期长,效率低,可以用add指令做乘法)
MUL r8/m8
;无符号字节乘法
;AX←AL×r8/m8
MUL r16/m16
;无符号字乘法
;DX.AX←AX×r16/m16 DX放高位,AX放低位
IMUL r8/m8
;有符号字节乘法
;AX←AL×r8/m8
IMUL r16/m16
;有符号字乘法
;DX.AX←AX×r16/m16
乘法指令的功能
乘法指令分无符号和有符号乘法指令
乘法指令的源操作数显式给出,隐含使用另一个操作数AX和DX
---字节量相乘:AL与r8/m8相乘,得到16位的结果,存入AX
----字量相乘:AX与r16/m16相乘,得到32位的结果,其高字存入DX,低字存入AX
乘法指令利用OF和CF判断乘积的高一半是否具有有效数值
乘法指令对标志的影响
乘法指令如下影响OF和CF标志:
---------MUL指令——若乘积的高一半(AH或DX)为0,则OF=CF=0;否则OF=CF=1
---------IMUL指令——若乘积的高一半是低一半的符号扩展,则OF=CF=0;否则均为1
乘法指令对其他状态标志没有定义
对标志没有定义:指令执行后这些标志是任意的、不可预测(就是谁也不知道是0还是1) 对标志没有影响:指令执行不改变标志状态
例子:
mov al,0b4h ;al=b4h=180 当做无符号,也就是原码=补码 值=180
mov bl,11h ;bl=11h=17
mul bl ;ax=Obf4h=3060 ;AX高8位不为0 ,OF=CF=1,
mov al,0b4h ;al=b4h=-76 当做有符号数的话b4h符号位为1,则是负数,则取反+1算出原码得到-76
mov bl,11h ;bl=11h=17 ;整数原码=补码 所以值不变=17
imul bl ;ax=faf4h=-1292 ;OF=CF=1,AX高8位ah是有效数字,不是符号扩展
除法指令(执行指令周期长,效率低)
DIV r8/m8 ;无符号字节除法:
AL←AX÷r8/m8的商,Ah←AX÷r8/m8的余数
DIV r16/m16 ;无符号字除法:
;AX←DX.AX÷r16/m16的商,DX←DX.AX÷r16/m16的余数
IDIV r8/m8 ;有符号字节除法:
AL←AX÷r8/m8的商,Ah←AX÷r8/m8的余数
IDIV r16/m16 ;有符号字除法:
;AX←DX.AX÷r16/m16的商,DX←DX.AX÷r16/m16的余数
除法指令的功能
除法指令分无符号和有符号除法指令
除法指令的除数显式给出,隐含使用另一个操作数AX和DX作为被除数
字节量除法:AX除以r8/m8,8位商存入AL,8位余数存入AH
字量除法:DX.AX除以r16/m16,16位商存入AX,16位余数存入DX
除法指令对标志没有定义
除法指令会产生结果溢出
除法错中断
当被除数远大于除数时,所得的商就有可能超出它所能表达的范围。如果存放商的寄存器AL/AX不能表达,便产生溢出,8086CPU中就产生编号为0的内部中断——除法错中断
--------对DIV指令,除数为0,或者在字节除时商超过8位,或者在字除时商超过16位,则发生除法溢出
--------对IDIV指令,除数为0,或者在字节除时商不在-128~127范围内,或者在字除时商不在-32768~32767范围内,则发生除法溢出
例子:
mov ax,0400h ;ax=400h=1024
mov bl,0b4h ;bl=b4h=180
div bl ;商al=05h=5 ;余数ah=7ch=124
mov ax,0400h ;ax=400h=1024
mov bl,0b4h ;bl=b4h=-76
idiv bl ;商al=f3h=-13 ;余数ah=24h=36
符号扩展指令
CBW ;AL的符号扩展至AH
;如AL的最高有效位是0,则AH=00
;AL的最高有效位为1,则AH=FFH。AL不变
CWD ;AX的符号扩展至DX
;如AX的最高有效位是0,则DX=00
;AX的最高有效位为1,则DX=FFFFH。AX不变
不影响标志位
符号扩展的概念
符号扩展是指用一个操作数的符号位(即最高位)形成另一个操作数,后一个操作数的各位是全0(正数)或全1(负数)。符号扩展不改变数据大小
对于数据64H(表示数据100),其最高位D7为0,符号扩展后高8位都是0,成为0064H(仍表示数据100)
对于数据ff00H(表示有符号数-256),其最高位D15为1,符号扩展后高16位都是1,成为ffffff00H(原码仍表示有符号数-256)
例子
mov al,80h ;al=80h
cbw ;ax=ff80h ;最高位为1,ah扩展为全1
add al,255 ;al=7fh
cbw ;ax=007fh ;最高位为0,ah扩展为全0
十进制调整指令
十进制数调整指令对二进制运算的结果进行十进制调整,以得到十进制的运算结果
分成压缩BCD码和非压缩BCD码调整
3位操作类指令
位操作类指令以二进制位为基本单位进行数据的操作;这是一类常用的指令,都应该特别掌握
注意这些指令对标志位的影响
1、逻辑运算指令 AND OR XOR NOT TEST
2、移位指令 SHL SHR SAR
3、循环移位指令 ROL ROR RCL RCR
逻辑与指令AND
只有相“与”的两位都是1,结果才是1;否则,“与”的结果为0
对两个操作数执行逻辑与运算,结果送到目的操作数
与运算主要用来取位 和 置0
例子
and al, 80h ;运算后得到al最高位值
and al, 00h ;运算后al置为0
逻辑或指令OR
只要相“或”的两位有一位是1,结果就是1;否则,结果为0
对两个操作数执行逻辑或运算,结果送到目的操作数
或运算主要用来置1
or al, 1 ;运算后all最后一位必为1
逻辑异或指令XOR
只有相“异或”的两位不相同,结果才是1;否则,结果为0
对两个操作数执行逻辑异或运算,结果送到目的操作数
异或运算主要用来清零操作
例子:
xor al, 0f0h
xor al, al ;两个一样的数,位值都一样,结果都为0 清0
逻辑非指令NOT
按位取反,原来是“0”的位变为“1”;原来是“1”的位变为“0”
对一个操作数执行逻辑非运算
----NOT指令是一个单操作数指令
----NOT指令不影响标志位
例子
mov al,45h
not al ;逻辑非 al=0bah 标志不变
测试指令TEST
对两个操作数执行逻辑与运算,结果不回送到目的操作数,根据结果设置zf标志位
Test的一个非常普遍的用法是用来测试一方寄存器是否为空:
例子
test ax, ax
jz TEST_ ;如果ecx为零,设置ZF零标志为1,Jz跳转
例子:
mov al,12h
test al,01h
jz TEST_ ;运算结果为0,zf=1 条件跳转,如果结果不为0,zf=0,则顺序执行
移位指令
----将操作数移动一位或多位,分成逻辑移位和算术移位,分别具有左移或右移操作
----逻辑左移一位相当于无符号数乘以2 逻辑右移一位相当于无符号数除以2
左移指令:
SHL SAL (逻辑左移,和算术左移)
逻辑左移: 移动的时候,补零
SHL:逻辑左移,最高位进入CF,空的低位补0
SAL:算术左移,最高位进入CF,空的低位补0
其中逻辑左移和算术左移是一样的,一般都会汇编成逻辑左移
右移指令:
SHR:逻辑右移,最低位进入CF,空的高位补0
SAR:算术右移,最低位进入CF,空的高位补符号位,如果符号位是1就全补1,是0就全补0,这样的话可以保证数值的数学意义不变
别的语言汇编移位原则:
由于计算机均按补码保存数值,所以不管符号正负,左移对于符号位并不产生影响,而右移则就不同了,无符号数怎么右移都不影响符号位,但是有符号数逻辑右移时高位补0将改变符号位,所以只能采用算术右移。
总结:只有有符号数右移才采用算术右移,否则其它情况都采用逻辑移位操作(逻辑左移或逻辑右移)。原来只要明白计算机是以补码方式保存数值的,就一切都清楚了。
移位指令的操作数
----移位指令的第一个操作数是指定的被移位的操作数,可以是寄存器或存储单元
----后一个操作数表示移位位数,该操作数为1,表示移动一位;当移位位数大于1时,则用CL寄存器值表示,该操作数表达为CL
移位指令对标志的影响
----按照移入的位设置进位标志CF
----根据移位后的结果影响SF、ZF、PF
----对AF没有定义
----如果进行一位移动,则按照操作数的最高符号位是否改变,相应设置溢出标志OF:如果移位前的操作数最高位与移位后操作数的最高位不同(有变化),则OF = 1;否则OF = 0。当移位次数大于1时,OF不确定
例子
mov al,1
SHL al, 1;逻辑左移,al相当于乘2了 al=2
SHR al, 1;逻辑右移 al相当于除2 al=1
SAL al, 1;算术左移 al相当于乘2了 al=2
mov ah,-8;
SAR ah, 1;算术右移,补符号位 ah=0fch=-4
循环移位指令
循环左移ROL(Rotate Left)和循环右移ROR(Rotate Right)。
格式:ROL(或ROR) OPR,CNT
循环左移/右移指令只是移位方向不同,它们移出的位不仅要进入CF,而且还要填补空出的位。可以理解为蛇咬尾巴型循环(高低位交换)
带进位的循环左移RCL(Rotate Left Through Carry)和带进位的循环右移RCR(Rotate Right)。
格式:RCL(或RCR) OPR, CNT
带进位的循环左移/右移指令只有移位的方向不同,它们都用原CF的值填补空出的位,移出的位再进入CF。
循环移位指令对标志的影响
按照指令功能设置进位标志CF
不影响SF、ZF、PF、AF
如果进行一位移动,则按照操作数的最高符号位是否改变,相应设置溢出标志OF:如果移位前的操作数最高位与移位后操作数的最高位不同(有变化),则OF = 1;否则OF = 0。当移位次数大于1时,OF不确定
例子
;32位移位 ax=ffff dx=0000
mov ax, 0ffffh
mov dx, 0
shl ax, 1 ;移出的位进入cf
rcl dx, 1 ;CF的值填补空出的位,移出的位再进入CF
4串操作类指令
串操作指令是8086指令系统中比较独特的一类指令,采用比较特殊的数据串寻址方式,在操作主存连续区域的数据时,特别好用、因而常用
重点掌握: MOVS STOS LODS CMPS SCAS REP
一般了解: REPZ/REPE REPNZ/REPNE
串数据类型
---------串操作指令的操作数是主存中连续存放的数据串(String)——即在连续的主存区域中,字节或字的序列
---------串操作指令的操作对象是以字(W)为单位的字串,或是以字节(B)为单位的字节串
串寻址方式
源操作数用寄存器SI寻址,默认在数据段DS中,但允许段超越:DS:[SI]
目的操作数用寄存器DI寻址,默认在附加段ES中,不允许段超越:ES:[DI] ,所以初始化时es段地址和sd段地址一样
每执行一次串操作指令,SI和DI将自动修改:
----------±1(对于字节串)或±2(对于字串)
----------执行指令CLD指令后,DF = 0,地址指针增1或2
----------执行指令STD指令后,DF = 1,地址指针减1或2
DF: 方向标志,其用于在串处理指令中,用来控制每次操作后 SI 和 DI 是自增还是自减
串传送MOVS
把字节或字操作数从主存的源地址传送至目的地址
MOVSB
-----------字节串传送:ES:[DI]←DS:[SI] ;SI←SI±1,DI←DI±1
MOVSW
-----------字串传送:ES:[DI]←DS:[SI] ;SI←SI±2,DI←DI±2
例子
std ;DF=0 地址指针是减的方向,反向拷贝
mov si,offset BUFF_SI+4
mov di,offset BUFF_DI+4
mov cx, 5
rep movsb ;rep重复执行cx此指令,也就是拷贝5次
cld ;DF=0 地址指针是增的方向,正向拷贝
mov si,offset BUFF_SI
mov di,offset BUFF_DI
mov cx, 5
rep movsb ;rep重复执行cx此指令,也就是拷贝5次
串存储STOS
把AL或AX数据传送至目的地址
STOSB
----------字节串存储:ES:[DI]←AL ;DI←DI±1
STOSW
----------字串存储:ES:[DI]←AX ;DI←DI±2
例子:
mov di,offset BUFF_DI
mov ax,1122h
stosw ;把ax的值给di这块内存,di+2
串读取LODS
LODSB
----------字节串读取:AL←DS:[SI] ;SI←SI±1
LODSW
-----------字串读取:AX←DS:[SI] ;SI←SI±2
例子:
mov si,offset BUFF_SI
lodsw ;把si的值给ax si+2
串比较CMPS
将主存中的源操作数减去至目的操作数,以便设置标志ZF,进而比较两操作数之间的关系
CMPSB
----------字节串比较:DS:[SI]-ES:[DI] ;SI←SI±1,DI←DI±1
CMPSW
-----------字串比较:DS:[SI]-ES:[DI] ;SI←SI±2,DI←DI±2
例子:
mov si,offset BUFF_SI
mov di,offset BUFF_DI
cmpsb
je CMP_ ;比较后标志ZF=1 相等跳转
串扫描SCAS
将AL/AX减去至目的操作数,以便设置标志ZF,进而比较AL/AX与操作数之间的关系
SCASB
---------字节串扫描:AL-ES:[DI] ;DI←DI±1
SCASW
---------字串扫描:AX-ES:[DI] ;DI←DI±2
例子:
mov di, offset BUFF_DI
mov al, 20h ;空格
mov cx, 10
FOR_FINE:
scasb ;查找di中的空格
je FIND_ ;如果找到就跳转
dec cx ;否则循环次数减1,继续查找,scasb执行后 di会自动加一
jmp FOR_FINE
重复前缀指令
串操作指令执行一次,仅对数据串中的一个字节或字量进行操作。但是串操作指令前,都可以加一个重复前缀,实现串操作的重复执行。重复次数隐含在CX寄存器中
重复前缀分2类,3条指令:
-----------配合不影响标志的MOVS、STOS(和LODS)指令的REP前缀
-----------配合影响标志的CMPS和SCAS指令的REPZ和REPNZ前缀
REP重复前缀指令
REP
------每执行一次串指令,CX减1 直到CX=0,重复执行结束
------REP前缀可以理解为:当数据串没有结束(CX≠0),则继续传送
REPZ重复前缀指令
REPZ/REPE前缀可以理解为:当数据串没有结束(CX≠0),并且串相等(ZF=1),则继续比较,
条件相当于if(CX != 0 && ZF==1)满足条件就重复执行
REPNZ重复前缀指令
REPNZ/REPNE前缀可以理解为:当数据串没有结束(CX≠0),并且串不相等(ZF=0),则继续比较
条件相当于if(CX != 0 && ZF==0)满足条件就重复执行
例子
mov si, offset BUFF_SI
mov di, offset BUFF_DI
mov cx, 5
repz CMPSB ;执行cmpsb后 ZF=1 说明字符相等,相等就重复执行CMPSB指令
cmp cx,0 ;如果cx=0,说明执行了5次CMPSB,说明字符串的5个字符都相等
重复比较的解释
指令repz cmpsb结束重复执行的情况
------① ZF=0,即出现不相等的字符
------② CX=0,即比较完所有字符:
这种情况下,如果ZF=0,说明最后一个字符不等;而ZF=1表示所有字符比较后都相等,也就是两个字符串相同 所以, 重复比较结束后,jnz unmat指令的条件成立ZF=0,表示字符串不相等
5控制转移类指令
控制转移类指令用于实现分支、循环、过程等程序结构,是仅次于传送指令的最常用指令
控制转移类指令通过改变IP(和CS)值,实现程序执行顺序的改变
无条件转移指令
只要执行无条件转移指令JMP,就使程序转到指定的目标地址处,从目标地址处开始执行那里的指令
操作数label是要转移到的目标地址(目的地址、转移地址)
JMP指令分成4种类型:
⑴ 段内转移、直接寻址
⑵ 段内转移、间接寻址
⑶ 段间转移、直接寻址
⑷ 段间转移、间接寻址
目标地址的寻址方式
直接寻址方式
------------转移地址象立即数一样,直接在指令的机器代码中,就是直接寻址方式
间接寻址方式
--------------转移地址在寄存器或主存单元中,就是通过寄存器或存储器的间接寻址方式
目标地址的范围:段内
段内转移——近转移(near)
-----------在当前代码段64KB范围内转移( ±32KB范围) 不需要更改CS段地址,只要改变IP偏移地址
段内转移——短转移(short)
-----------转移范围可以用一个字节表达,在段内-128~+127范围的转移
目标地址的范围:段间
段间转移——远转移(far)
-----------从当前代码段跳转到另一个代码段,可以在1MB范围 需要更改CS段地址和IP偏移地址
实际编程时,jmp段内转移汇编程序会根据目标地址的距离,自动处理成短转移、近转移 程序员可用操作符short、near ptr指定,这也没有必要,段内转移直接jmp就可以了,不需要显示写near ptr,short
段间转移必须显示指定far ptr 语法: jmp far ptr 地址
例子:
assume cs:MyCode,ds:MyData,ss:MyStack;段名相当于一个标号,它标识(标记)了该段的段地址
;定义栈段 段名+关键字segment
MyStack segment stackdb 256 dup(?)
MyStack endsMyData segment
szStuBuf db "aaaabbb$"
FileName db "test.txt",0 ;文件名,以0为字符串的结束标志
MyData ends;第二个代码段
mycode2 segment
LABEL1:mov ax, 1mov bx, 2jmp far ptr LABEL2
mycode2 ends;开始代码段
MyCode segment
START:;初始化段寄存器mov ax,MyStackmov ss,axmov ax, MyDatamov ds,axmov es,ax;jmp short label_short ;短转移-128~+127,如果超过跳转距离会报错;db 128 dup(?)
;label_short:;mov ax,1133hLABEL2:jmp far ptr LABEL1 ;IP←label的偏移地址;CS←label的段地址mov bx,3344hEXIT:mov ah,4chint 21h
MyCode ends
end START
条件转移指令
条件满足,发生转移:IP←IP+8位位移量 (也就是只能跳转一个;短转移-128~+127距离,距离太远你可以写个中转跳转,跳转第二个标号,标号中执行用jmp跳转到指定位置) ;条件不满足,顺序执行
Jcc指令的分类
Jcc指令不影响标志,但要利用标志 根据利用的标志位不同,17条指令分成4种情况:
⑴ 判断单个标志位状态
⑵ 比较无符号数高低
⑶ 比较有符号数大小
⑷ 判断计数器CX为0
条件指令如下图:
例子:
mov ax,0
cmp ax,0 ;ax和0比较
je CMP_ ;相等就跳转,不相等就顺序向下执行
mov ax,1122h
子程序指令(函数的就是这样实现的)
子程序是完成特定功能的一段程序
当主程序(调用程序)需要执行这个功能时,采用CALL调用指令转移到该子程序的起始处执行
当运行完子程序功能后,采用RET返回指令回到主程序继续执行
子程序调用指令call语法
CALL指令分成4种类型(类似JMP)
-----------CALL label ;段内调用、直接寻址
-----------CALL r16/m16 ;段内调用、间接寻址
-----------CALL far ptr label ;段间调用、直接寻址
-----------CALL far ptr mem ;段间调用、间接寻址
CALL指令需要保存返回地址(保存的地址是call的下一条指令的地址):
-----------段内调用——偏移地址入栈IP SP←SP-2,SS:[SP]←IP
-----------段间调用——偏移地址IP和段地址入栈CS SP←SP-2,SS:[SP]←CS SP←SP-2,SS:[SP]←IP
子程序返回指令
根据段内和段间、有无参数,分成4种类型 RET
-----------无参数段内返回 RET i16
-----------有参数段内返回 RETF
-----------无参数段间返回 RETF i16
-----------有参数段间返回
需要弹出CALL指令压入堆栈的返回地址 (ret会自动弹出返回地址,段间返回把地址放入cs:ip中,段内返回把地址放入ip中,注意的是自己要确定当前的sp指向返回地址)
-----------段内返回——出栈偏移地址IP IP←SS:[SP], SP←SP+2
-----------段间返回——出栈偏移地址IP和段地址CS IP←SS:[SP],SP←SP+2 CS←SS:[SP],SP←SP+2
返回指令RET的参数
RET i16
-----------有参数返回 RET指令可以带有一个立即数i16, 则堆栈指针SP将增加,即 SP←SP+i16
-----------这个特点使得程序可以方便地废除若干执行CALL指令以前入栈的参数(也就是清栈操作)
模拟stdcall代码例子:
assume cs:mycode,ds:mydata,ss:mystack;定义栈段
mystack segment stackdb 256 dup(?)
mystack ends;定义数据段
mydata segmentdb 256 dup(?)
mydata ends;定义代码段
mycode segmentSTART:;初始化段寄存器mov ax, mystackmov ss, axmov ax, mydatamov ds, axmov es, ax;段间转移子程序mov ax, 2push axmov ax, 3push axcall far ptr MY_MULmov ax,1133h;stdcall被调用者清理栈,这是标准调用约定
MY_MUL:push bp ;保存环境mov bp, sp ;保存栈底 保存了这个位置以后,就可以以bp为相对位置查参数和查局部变量 参数可以直接用bp+xxxx, 查局部变量可以直接用bp-xxx sub sp, 4 ;申请局部变量空间push ax ;保存寄存器环境mov word ptr[bp-2], 1 ;定义局部变量mov word ptr[bp-4], 2mov ax, [bp+6] ;获取参数mul word ptr [bp+8]pop ax ;恢复寄存器环境mov sp, bp ;释放局部变量空间,恢复栈底pop bp ;恢复环境ret 4 ;返回 ;retn 4代表先把返回地址出栈到ip中,然后再让sp+4个字节,相当于在函数内部就平栈了,这样外部就不用清理了,相当于stdcall(标准调用约定)mycode ends
end START
上面代码中整体栈结构如下图
模拟c调用约定
mov ax, 1 ;参数2入栈
push ax
mov ax, 4 ;参数1入栈
push ax
call MY_ADD
add sp, 4 ;参数出栈(栈平衡)MY_ADD:push bp ;保存环境mov bp, sp ;保存栈底;sub sp, 4 ;申请局部变量空间;push ax ;保存寄存器环境mov ax, [bp+4]add ax, [bp+6];pop ax ;恢复寄存器环境;add sp, 4 ;释放局部变量空间pop bp ;恢复环境retn ;返回
中断指令
中断(Interrupt )是又一种改变程序执行顺序的方法 中断具有多种中断类型
中断的指令有3条: INT i8 IRET INTO
IRET 和Call差不多,Call的ret返回的时候会把栈顶的元素弹出两个字节,这两个字节是返回地址,所以可以回到正确的地方执行指令,但是IRET明显比ret保存的东西多,其中ret我们可以手工的pop和jmp去执行,IRET也可以自己去做,但是你要完整的模拟才可以,一般还是调用IRET即可
8086的外部中断
8086可以管理256个中断
各种中断用一个向量编号来区别
主要分成外部中断和内部中断 外部中断——来自CPU之外的原因引起的中断
--------------又可以分成 可屏蔽中断:可由CPU的中断允许标志IF控制
--------------非屏蔽中断:不受CPU的中断允许标志IF控制
8086的内部中断
内部中断——CPU内部执行程序引起的中断,又可以分成:
---------除法错中断:执行除法指令,结果溢出产生的 0 号中断
---------指令中断:执行中断调用指令INT i8产生的 i8 号中断
---------断点中断:用于断点调试(INT 3)的 3 号中断
---------溢出中断:执行溢出中断指令,OF=1产生的 4 号中断
---------单步中断:TF=1在每条指令执行后产生的 1 号中断
中断指令INT
INT i8
-----中断调用指令:产生i8号中断
IRET
-----中断返回指令:实现中断返回
INTO
-----溢出中断指令: ;若溢出标志OF=1,产生4号中断 ;否则顺序执行
INT功能调用的格式(相当于调用系统api)具体查看指令字典
通常按照如下4个步骤进行:
⑴ 在AH寄存器中设置系统功能调用号
⑵ 在指定寄存器中设置入口参数
⑶ 执行指令INT 21H(或ROM-BIOS的中断向量号)实现中断服务程序的功能调用
⑷ 根据出口参数分析功能调用执行情况
例子:
mov ah, 09h ;输出提示消息,调用系统调用9号 默认输出的地址为ds:dx,所有要把字符串地址给dx
mov dx, offset MSG_ADD_PROMP
int 21h
6处理机控制类指令
这些指令在特定的情况下,必须使用 对标志位进行设置的指令
----------CLC STC CMC CLD STD CLI STI
对CPU状态进行控制的指令
----------NOP CS: SS: DS: ES: LOCK HLT ESC WAIT
进位标志操作指令
用于任意设置进位标志
----------CLC ;复位进位标志:CF←0
----------STC ;置位进位标志:CF←1
----------CMC ;求反进位标志:CF←~CF
方向标志操作指令
串操作指令中,需要使用
----------CLD ;复位方向标志:DF←0
----------STD ;置位方向标志:DF←1
中断标志操作指令
在编写中断服务程序时,需要控制可屏蔽中断的允许和禁止
----------CLI ;复位中断标志:IF←0
----------STI ;置位中断标志:IF←1
空操作指令
NOP
不执行任何操作,但占用一个字节存储单元,空耗一个指令执行周期
NOP常用于程序调试
----------在需要预留指令空间时用NOP填充
----------代码空间多余时也可以用NOP填充
----------还可以用NOP实现软件延时
事实上,NOP和XCHG AX,AX的指令代码一样,都是 90H
封锁前缀指令
LOCK ;封锁总线
这是一个指令前缀,可放在任何指令前
这个前缀使得在这个指令执行时间内,8086 处理器的封锁输出引脚有效,即把总线封锁,使别的控制器不能控制总线;直到该指令执行完后,总线封锁解除
暂停指令
HLT ;进入暂停状态
暂停指令使CPU进入暂停状态,这时CPU不进行任何操作。当CPU发生复位或来自外部的中断时,CPU脱离暂停状态
HLT指令可用于程序中等待中断。当程序中必须等待中断时,可用HLT,而不必用软件死循环。然后,中断使CPU脱离暂停状态,返回执行HLT的下一条指令
交权指令
ESC 6位立即数,reg/mem ;把浮点指令交给浮点处理器执行
浮点协处理器8087指令是与8086的整数指令组合在一起的,当8086发现是一条浮点指令时,就利用ESC指令将浮点指令交给8087执行
实际编写程序时,一般采用易于理解的浮点指令助记符格式
-----------ESC 6,[SI] ;实数除法指令:FDIV dword ptr [SI]
-----------ESC 20H,AL ;整数加法指令:FADD ST(0),ST
等待指令
WAIT ;进入等待状态
8086利用WAIT指令和测试引脚实现与8087同步运行
浮点指令经由8086处理发往8087,并与8086本身的整数指令在同一个指令序列;而8087执行浮点指令较慢,所以8086必须与8087保持同步