目录
控制转移类指令
控制转移类指令负责常见程序设计的顺序、分支、循环等的流程控制,而处理机控制类指令负责指挥一些CPU的其他工作。在8086中,指令的执行顺序和CS代码段寄存器和指令指针IP确定。
通常来说,指令采用顺序寻址,控制转移则控制程序流程从当前指令跳转到目的地指令。8086处理器设计了相对、直接和简介3种指明目标地址的方式,类似于存储器寻址方式。
相对寻址方式
提供目的地址相对于当前IP的位移量,转移到目的地址就是当前IP加上位移量。当向地址增大方向转移时,位移量为正,否则为负。
直接寻址方式
指令代码中提供目的地的逻辑地址,转移后的CS和IP值直接来自指令操作码后的目的地地址操作数。
间接寻址方式
指令代码中指示寄存器或者存储单元,目的地址从寄存器或者存储单元中间接获得,分别被称为指令寻址的寄存器间接寻址和存储器间接寻址。
下面展示不同类别的控制转移类指令。
无条件转移指令JMP
即直接改变程序的执行顺序。无条件转移指令只有一条指令JMP,使程序跳转到指定的目标地址处。其基本格式如下:
JMP label
JMP r16/m16
JMP far ptr label
JMP far ptr mem ;将连续两个字单元的数据送到CS:IP,其中低字送IP,高字送CS
寻址方式:直接数寻址,寄存器寻址和段间转移。其中,如果转移范围是8位字节量,那么称为短转移;如果转移范围是16位字量,那么成为近转移;当需要转移到其他的代码段,不在本代码段时,称为段间转移,使用far ptr标识符。
标志影响:无。
条件转移指令JCC
根据指定的条件确定程序是否发生转移,如果满足条件,则转移到目标地址去执行程序;否则继续顺序执行下一条指令。JCC根据标志确定是否转移,包含JZ,JNZ,JS,JNS,JP,JNP,JO,JNO,JC,JNC等等。
寻址方式:和JMP相同。
标志影响:不改变标志,但是JCC指令是否跳转要利用标志。
下面例子展示了测试AL的最高位来控制流程的JZ语句。
TEST AL,80H
JZ NEXT0
MOV AH, 0FFH
JMP DONE
NEXT0: MOV AH,0
DONE: ……
TEST通过结果设置了ZF标志,通过JZ指令设置了跳转;同时使用JMP语句保证在不跳转的情况下不执行NEXT0语句。这种结构有一点类似于高级语言的try/except,else/finally结构。
常见的JZ,JNZ,JS,JNS,JP,JNP,JO,JNO,JC,JNC通过ZF,SF,PF,OF,CF指令来确定是否进行条件转移,不做过多描述。下面着重讲述比较无符号数和有符号数判断流程转移的指令。
比较无符号数转移指令
比较无符号数转移指令包括JB(<),JNB(≥),JBE(≤),JNBE(>)。
下面例子展示了max函数的汇编实现方法。
CMP AX,BX
JNB RETURN ;若AX>=BX ,直接输出AX
XCHG AX,BX ;否则交换AX和BX
RETURN: MOV RESULT,AX
比较有符号数转移指令
判断有符号数的大小(Greater or Less)同样分为四种指令:JL,JNL,JLE,JNLE。具体用法和比较无符号数转移指令相同。
可见,程序转移指令之前通常需要通过CMP和TEST,加减运算和逻辑运算设置标志再进行转移。
循环指令
当满足(或者不满足)某条件时,将一直执行一系列操作直到可以退出循环为止。循环流程的条件一般是循环计数,在程序中用循环计数来控制循环次数。8086设计了专门的循环指令用于控制循环流程;当然,也可以使用条件转移指令实现。
常用的循环指令有JCXZ,LOOP,LOOPZ/LOOPE,LOOPNZ/LOOPNE,其基本格式如下:
JCXZ label ;CX=0时转移(类似于break)
LOOP label ;CX--且CX!=0时循环
LOOPZ/LOOPE label ;若CX!=0且ZF=1,循环
LOOPNZ/LOOPNE label ;若CX!=0且ZF=0,循环
寻址方式:和JMP相同。
标志影响:无。
使用LOOP指令实现循环有三个要点:
首先在CX中设置循环次数;
LOOP指令的标号一般在前面,循环体在标号和LOOP指令之间;
防止CX初值为0导致出错,可以使用JCNZ指令进行判断,如为0直接跳到LOOP指令后方。
子程序指令
子程序通常和主程序分开,完成一段特定的功能,类似于高级语言之中的函数。当主程序需要该功能时,调用该子程序。当运行完子程序后,再返回主程序。常见的子程序指令包括调用指令CALL和返回指令RET。
子程序调用指令CALL
子程序和主程序可以在同一个代码段内,也可以不在。但是子程序结束时是需要返回的,所以需要保护CS:IP值,一般将这些值压入堆栈,在返回时再出栈。其基本格式如下:
CALL label ;段内调用,sp<-sp-2,ss:[sp]<-ip,ip<-label
CALL r16/m16 ;段内调用,sp<-sp-2,ss:[sp]<-ip,ip<-r16/m16
CALL far ptr label;段间调用 sp<-sp-2,ss:[sp]<-cs,sp<-sp-2,ss:[sp]<-ip
CALLA far ptr mem;
寻址方式:和JMP相同。
标志影响:无。
子程序返回指令RET
RET指令将堆栈内的CS:IP地址按照先IP后CS的顺序弹出,并且分别送CS:IP寄存器。
寻址方式:和JMP相同。
标志影响:无。
中断指令
在程序运行时,遇到某些紧急情况或者重要错误时,当前程序应该能够暂停报错并且针对错误进行处理的指令叫做中断。中断服务程序处理完后应返回原来程序的断点,类似于子程序转移,继续执行被中断的程序。处理器一般都有处理中断的能力。
8086的中断类型
8086有256种中断类型(一个字节量)。如果进行一个大的分类,又可以分为外部中断和内部中断两种。
外部中断:外部中断是来自CPU之外的原因引起的程序中断。其中一部分是可屏蔽中断,即CPU可以根据中断允许标志IF不执行中断。还有一部分是非屏蔽中断,例如奇偶校验出错,浮点运算出错等等。
内部中断:指的是CPU内部执行程序引起的程序中断。包括除法错中断或者指令终端INT,INTO,以及debug调试时-t指令设置的单步中断标志TF=1。
8086的中断过程
中断服务程序可以看作一种特殊的子程序,终端服务程序的首地址被安排在中断向量表中。中断向量表被设置在主存的最低1KB区域内。其中每4字节对应一个中断,对应CS:IP。256种中断每个都为4字节,加起来变为1KB。
8086对中断的处理过程为:
1.标志寄存器入栈保存
2.禁止新的可屏蔽中断和单步中断
3.断电地址入栈
4.读取中断服务程序的起始地址
8086中断返回的处理过程为
1.断点地址出栈恢复
2.标志寄存器出栈恢复。
8086中断指令
8086主要有三种中断指令,其基本格式如下:
INT i8 ;产生i8号中断
IRET ;中断返回指令,和RET类似
INTO ;溢出中断指令:若OF=1,则产生4号中断,否则程序顺序执行
处理机控制类指令
处理机控制类指令用来控制各种CPU内部操作,比如暂停,等待或者空操作等。
空操作指令NOP
不进行任何操作,但占用1字节存储单元,一般用于程序调试或者预留指令空间,或者实现软件延时。实际上,NOP就是 XCHG AX,AX指令。
段超越前缀指令SEG:
使用时用实际的段寄存器代替SEG,即操作数寻址时采用的常见指令。
封锁前缀指令LOCK
该指令执行时间内,处理器的封锁输出引脚有效,也就是把总线封锁。可以用于某些意外操作破坏有效信息。
暂停指令HLT
该指令是CPU暂停执行任何操作指导收到复位或者外部中断。注意,一般的应用程序不适用该指令,因为该指令将会暂时性引起完全死机。
交权指令ESC
交权指令将浮点指令交给浮点处理器执行,这通常是为了提高浮点运算的速度。
等待指令WAIT
CPU进入等待状态,当测试引脚为低电平有效时,CPU脱离等待状态。这个指令常用于协调CPU和浮点处理器的执行速度。
实际上,除非一些非常底层的程序,否则不会使用到除了SEG:之外的所有处理机控制类指令,所以这些指令在汇编程序中一般不是特别重要。
总结
到此,我们已经完成了8086汇编指令的学习。接下来我们进入8086一般程序的格式设计讲解,实现一些高级语言中常见的结构。