8086汇编的各种指令

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 stack
        db 256 dup(?)
MyStack ends

MyData segment
szStuBuf db "aaaabbb$"
FileName db "test.txt",0	;文件名,以0为字符串的结束标志
MyData ends

;第二个代码段
mycode2 segment
LABEL1:
    mov ax, 1
    mov bx, 2
    jmp far ptr LABEL2
mycode2 ends


;开始代码段
MyCode segment
START:
	;初始化段寄存器
	mov ax,MyStack
	mov ss,ax
	mov ax, MyData
	mov ds,ax
	mov es,ax
	
	;jmp short label_short     ;短转移-128~+127,如果超过跳转距离会报错
	;db 128 dup(?)
;label_short:
	;mov ax,1133h
	
LABEL2:
	jmp far ptr LABEL1		;IP←label的偏移地址
							;CS←label的段地址
	mov bx,3344h

EXIT:
	mov ah,4ch
	int 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 stack
        db 256 dup(?)
mystack ends

;定义数据段
mydata segment
        db 256 dup(?)
mydata ends


;定义代码段
mycode segment
 START:
     ;初始化段寄存器
    mov ax, mystack
    mov ss, ax
    mov ax, mydata
    mov ds, ax
    mov es, ax

    ;段间转移子程序
    mov ax, 2
    push ax
    mov ax, 3
    push ax
    call far ptr MY_MUL
    mov 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], 2
    
    mov 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保持同步

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值