前面介绍的所有指令都具有一个相同的特征,即它们并不影响指令计数器IP的值,也就是说,这些指令不改变程序的流程,只能顺序地逐条执行。但实际上程序不可能全部顺序执行而是经常需要改变程序的执行流程,这就需要有一些指令来控制程序的执行流程,这些指令就是控制转移指令。控制转移指令主要包括五类:无条件转移指令、条件转移指令、循环转移指令、子程序调用与返回指令和中断控制指令。
1.无条件转移指令
X86系列微处理器的无条件转移指只有一条,即JMP指令,其作用如同C语言中的goto语句,使执行流程转移到指定的目标地址。
●JMP
格式:JMP opr
执行的操作:根据不同的寻址方式执行(IP)←(opr)或(CS:IP)←(opr)
无条件地转移到指令指定的地址去执行从该地址开始的指令。
JMP指令必须指定转移的目标地址(或称转向地址)。总的说来,根据转移目标地址的不同,转移可以分成两类:段内转移和段间转移。段内转移是指在同一段的范围之内进行转移,此时只需改变IP寄存器的内容,即用新的转移目标地址代替原有的IP的值就可达到转移目的。段间转移则是要转到另一个段去执行程序,此时不仅要修改IP寄存器的内容,还需要修改CS寄存器的内容才能达到目的,因此,此时的转移目标地址应由新的段地址和偏移地址两部分组成。根据使用的寻址方式的不同,JMP指令可以有5种不同的格式。
1)段内直接短转移
格式:JMP SHORT opr
执行的操作:(IP)←(IP)+8位位移量
采用相对寻址方式,其中8位位移量是由目标地址opr和当前地址共同确定的。操作数opr可以是一个汇编标号,这时该标号的地址和当前地址之间的距离必须在8位位移量能够表示的范围之内(-128~+127)。
2)段内直接近转移
格式:JMP NEAR PTR opr
执行的操作:(IP)←(IP)+16位位移量
可以看出除位移量为16位外,它和段内短转移一样,也采用相对寻址方式,在汇编格式中opr也只需要使用符号地址。由于位移量为16位,它可以转移到段内的任一个位置。
3)段内间接转移
格式:JMP WORD PTR opr
执行的操作:(IP)←(EA)
采用间接寻址方式,其中有效地址EA值由opr的寻址方式确定。它可以使用除立即数方式以外的任一种寻址方式,如果指定的是16位寄存器则把寄存器的内容送到IP寄存器中去,如果使用的是直接寻址等访问存储器的寻址方式,则对应存储单元中的16位字将被送到IP寄存器。
4)段间直接(远)转移
格式:JMP FAR PTR opr
执行的操作:(IP)←opr的段内偏移地址
(CS)←opr所在段的段地址
使用直接寻址方式。在汇编格式中opr可使用符号地址,即标号,而机器语言中则要指定转向地址的偏移地址和段地址。
5)段间间接转移
格式:JMP DWORD PTR opr
执行的操作:(IP)←(EA)
(CS)←(EA+2)
和段内间接转移类似,使用间接寻址方式,其中EA由opr的寻址方式确定,它可以使用除立即数及寄存器方式以外的任何存储器寻址方式,根据寻址方式求出EA后,把指定存储单元的字内容送到IP寄存器,并把下一个字的内容送到CS寄存器,这样就实现了段间跳转。(思考:为什么不能使用寄存器寻址?)例如,JMP DWORD PTR ALPHA[SP][DI]等。
和C语言中的goto语句很少被使用不同,JMP指令是汇编语言中经常被使用的一条指令。由于X86的指令集中的条件转移指令的转移目标地址和当前地址之间的距离非常有限,实现分支、循环等结构时都经常使用到JMP指令。
最后需说明一下,JMP指令不影响条件码。
2.条件转移指令
和无条件转移指令JMP不同,条件转移指令根据上一条指令所设置的条件码来判别测试条件,在测试条件满足的时候转移到指定的目标地址,如果测试条件不满足则继续顺序执行下一条指令。每一种条件转移指令都有它特定的测试条件,这个测试条件可以是某个标志位是否被置位,也可以是更加复杂的条件。
条件转移指令采用相对寻址方式,位移量用一个字节表示。其执行过程是:当满足条件时,(IP)←(IP)+符号扩展到16位后的位移量,如不满足测试条件则(IP)不变。在汇编格式中opr应指定一个目标地址,这个目标地址应在本条转移指令下一条指令地址的-128~+127个字节的范围之内,如超出了这个范围,汇编程序将报错。另外,所有的条件转移指令都不影响条件码。根据测试条件的不同,条件转移指令可以被分为四类。
1)根据单个条件标志的设置情况转移
这类条件转移指令包括10条,它们一般适用于测试某一次运算的结果并根据其不同特征产生程序分支作不同处理的情况。这10条指令分别是。
●JZ(或JE)
格式:JZ(或JE)opr
测试条件:ZF=1;结果为零(或相等)则转移(Jump if zero,or equal)。
●JNZ(或JNE)
格式:JNZ(或JNE)opr
测试条件:ZF=0;结果不为零(或不相等)则转移(Jump if not zero,or not equal)。
●JS
格式:JS opr
测试条件:SF=1;结果为负则转移(Jump if sign)。
●JNS
格式:JNS opr
测试条件:SF=0;结果为正则转移(Jump if not sign)。
●JO
格式:JO opr
测试条件:OF=1;溢出则转移(Jump if overflow)。
●JNO
格式:JNO opr
测试条件:OF=0;不溢出则转移(Jump if not overflow)。
●JP(或JPE)
格式:JP(或JPE)opr
测试条件:PF=1;奇偶位为1则转移(Jump if parity,or parity even)。
●JNP(或JPO)
格式:JNP(或JPO)opr
测试条件:PF=0;奇偶位为0则转移(Jump if not parity,or parity odd)。
●JC(或JNAE,JB)
格式:JC(或JNAE,或JB)opr
测试条件:CF=1;低于,或者不高于、等于,或进位为1则转移(Jump if carry,or not above or equal,or below)。
●JNC(或JAE,JNB)
格式:JNC(或JAE,或JNB)opr
测试条件:CF=0;不低于,或者高于、等于,或进位为零则转移(Jump if not carry,or above or equal,or not below)。
条件转移指令经常被用来实现分支结构和循环结构,下面是两个根据单个条件码进行转移的条件转移指令的例子。
例:
比较两个寄存器的内容,根据寄存器内容是否相等决定执行不同的操作。
例:
重复处理一串字符,直到遇到字符‘Q’为止。
例:
计算从1到100的和。
2)比较两个无符号数,并根据比较的结果转移
这类条件转移指令包括4条,它们用于对两个无符号数进行比较(一般用CMP指令)之后根据比较结果进行转移。这4条指令分别是。
●JB(或JNAE,JC)
格式:JB(或JNAE,JC)opr
测试条件:CF=1,低于,或者不高于、等于,或进位标志位为1则转移。
●JNB(或JAE,JNC)
格式:JNB(或JAE,JNC)opr
测试条件:CF=0,不低于,或者高于、等于,或进位标志位为0则转移。
●JBE(或JNA)
格式:JBE(或JNA)opr
测试条件:CFⅤZF=1,低于、等于,或者不高于则转移(Jump if below or equal,or a-bove)。
●JNBE(或JA)
格式:JNBE(或JA)opr
测试条件:CFⅤZF=0,不低于、等于,或者高于则转移(Jump if not below or equal,or above)。
这4条指令只用于对无符号数比较之后的结果进行判断转移,如果参加比较的两个数是带符号数,则不能使用这4条指令进行判断转移。
例:
计算从1到100的和。
3)比较两个带符号数,并根据比较的结果转移
这类条件转移指令同样包括4条,它们用于对两个带符号数进行比较(一般用CMP指令)之后根据比较结果进行转移。这4条指令分别是。
●JL(或JNGE)
格式:JL(或JNGE)opr
测试条件:SF XOR OF=1,小于,或者不大于、等于则转移(Jump if less,or not grea-ter or equal)。
●JNL(或JGE)
格式:JNL(或JGE)opr
测试条件:SF XOR OF=0,不小于,或者大于、等于则转移(Jump if not less,or grea-ter or equal)。
●JLE(或JNG)
格式:JLE(或JNG)opr
测试条件:(SF XOR OF)ⅤZF=1,小于、等于,或者不大于则转移(Jump if less or e-qual,or not greater)。
●JNLE(或JG)
格式:JNLE(或JG)opr
测试条件:(SF XOR OF)ⅤZF=0,不小于、等于,或者大于则转移(Jump if not less or equal,or greater)。
可以看出,对于带符号数和无符号数,其比较判断使用的测试条件是不同的,在设计程序的时候一定要注意区分使用。
4)根据CX的值是否为0判断转移
除了上面这些根据条件码进行判断转移的条件转移指令之外,X86微处理器还提供一种特别的条件转移指令,它根据CX的值是否为0来判断是否进行转移。其格式是:
●JCXZ
格式:JCXZ opr
测试条件:(CX)=0,CX寄存器的内容为零则转移(Jump if CX register is zero)。
JCXZ指令只测试CX寄存器的值,根据CX寄存器的内容是否为0,判断是否进行转移,与标志位的内容无关。
例:
计算从1到100的和。
所有的条件转移指令都不影响标志位。
3.循环指令
条件转移指令和无条件转移指令已经提供了一种实现循环结构的方法,这种方法在上面的例子中也有所涉及。为了简化循环结构的设计,X86微处理器还提供了一套专门用来实现循环结构的循环指令。
●LOOP
格式:LOOP opr
循环指令,测试条件:(CX)≠0
●LOOPZ/LOOPE
格式:LOOPZ(或LOOPE)opr
当为零或相等时的循环指令,测试条件:ZF=1且(CX)≠0
●LOOPNZ/LOOPNE
格式:LOOPNZ(或LOOPNE)opr
当不为零或不相等时的循环指令,测试条件:ZF=0且(CX)≠0
这三条指令的执行步骤是:
1)(CX)←(CX)-1;
2)检查是否满足测试条件,如满足则转移到opr指定的地址。
在汇编格式中opr必须指定一个表示转向地址的标号(符号地址),而在机器指令里则用8位位移量来表示转向地址与当前IP值的差。由于位移量只有8位,所以转向地址必须在该循环指令的下一条指令地址的-128~+127字节的范围之内,这和条件转移指令是一样的。当满足测试条件时就转向由opr指定的转向地址去执行,即实行循环,如不满足测试条件则IP值不变,即退出循环,程序继续顺序执行。
循环指令实质上就是用一条循环指令实现修改循环计数及判断转移条件两个功能,对于简化循环结构的设计很有好处。
循环指令不影响条件码。
例:
计算从1到100的和。
例:
有一串L个字符的字符串存储于首地址为ASCⅡ_STR的存储区中。如要求在字符串中查找“空格”(ASCⅡ码为20H)字符,找到则继续执行,如未找到则转到NOT_FOUND去执行。
在这段程序执行的过程中,有以下两种可能性。(www.xing528.com)
1)在查找中找到了“空格”,此时ZF=1,因此提前结束循环。在执行JNZ指令时,因不满足测试条件而顺序地继续执行。
2)如一直查找到字符串结束还未找到“空格”字符,此时因(CX)=0而结束循环,但在执行JNZ指令时因ZF=0而转移到NOT_FOUND去执行。
在程序继续执行的部分还可以打印出找到“空格”,并打印出(SI)以便确定它在字符串中的位置。在NOT_FOUND后可以打印出未找到“空格”等信息。
4.子程序调用及返回指令
在汇编语言程序设计中,子程序是一种使用非常广泛的编程结构。子程序通常是一段用来实现特定功能的代码段,可以供一个或多个主程序反复调用。子程序可以嵌套,即一个子程序可以再调用其他的子程序。在X86系列微处理器中,子程序的调用和返回是通过CALL和RET指令实现的。由于子程序和调用它的主程序可以在同一个代码段中,也可以在不同代码段中,所以CALL指令和RET指令也有近调用、近返回和远调用、远返回两类格式,其区别主要体现在指令的寻址方式上。再考虑到直接调用和间接调用的区别,CALL指令有四种不同的格式。
1)段内直接调用(近调用)
格式:CALL NEAR PTR dst
执行的操作:(SP)←(SP)-2
((SP)+1,(SP))←(IP)
(IP)←(IP)+16位目标地址偏移量
2)段内间接调用(近调用)
格式:CALL WORD PTR dst
执行的操作:(SP)←(SP)-2
((SP)+1,(SP))←(IP)
(IP)←(EA)
其中EA是由dst的寻址方式所确定的有效地址。
近调用是CALL指令的默认格式,可以缩写为“CALL dst”。在使用近调用的CALL指令时,子程序和它的调用者处于同一个代码段中,所以在调用过程中CS寄存器的值不会发生变化,只需要将子程序的入口地址写入IP寄存器。对于段内直接调用的CALL指令,指令的第2、3个字节是子程序入口和当前地址的偏移量。对于段内间接调用的CALL指令,指令的格式则与寻址方式相关。
3)段间直接调用
格式:CALL FAR PTR dst
执行的操作:(SP)←(SP)-2
((SP)+1,(SP))←(CS)
(SP)←(SP)-2
((SP)+1,(SP))←(IP)
(IP)←偏移地址(指令的第2、3个字节)
(CS)←段地址(指令的第4、5个字节)
4)段间间接调用
格式:CALL DWORD PTR dst
执行的操作:(SP)←(SP)-2
((SP)+l,(SP))←(CS)
(SP)←(SP)-2
((SP)+1,(SP))←IP)
(IP)←(EA)
(CS)←(EA+2)
远调用适用于子程序和调用者不在同一个代码段的情况,所以又叫段间调用。在远调用的CALL指令被执行时,CS和IP寄存器的值都会发生改变。对于段间直接调用的CALL指令,指令的第2~5字节是子程序入口的偏移地址和段地址。对于段间间接调用的CALL指令,指令的格式则与寻址方式相关。
和JMP指令相似,CALL指令执行一次程序控制流的无条件转移过程。但是在程序控制流转移的同时,CALL指令还将当前地址作为返回地址保存在堆栈之中。在子程序执行结束之后,返回指令就可以使用这些堆栈中保存的返回地址控制程序的执行过程返回到CALL指令的下一条指令继续执行。根据调用目标的不同,近调用和远调用所保存的返回地址是不同的,近调用时保存的返回地址只有IP寄存器的内容,而远调用时保存的返回地址则包括了CS和IP两个寄存器的内容。保存返回地址正是CALL指令执行的第一步。
CALL指令执行的第二步是进行程序控制流的转移,即将子程序的入口地址传送到IP寄存器(近调用)或CS:IP寄存器(远调用)。在用汇编语言书写程序的时候,CALL指令的操作数可以直接用标号符号来表示,汇编程序会自动根据标号符号和当前地址之间的关系生成机器码中的操作数。
对应于调用子程序的CALL指令,RET指令用来从子程序返回到主程序。RET指令有两种指令格式。
●RET
格式:RET
执行的操作:
近返回:(IP)←((SP)+1,(SP))
(SP)←(SP)+2
远返回:(IP)←((SP)+1,(SP))
(SP)←(SP)+2
(CS)←((SP)+1,(SP))
(SP)←(SP)+2
格式:RETexp
执行的操作:
近返回:(IP)←((SP)+l,(SP))
(SP)←(SP)+2
(SP)←(SP)+exp
远返回:(IP)←((SP)+l,(SP))
(SP)←(SP)+2
(CS)←((SP)+l,(SP))
(SP)←(SP)+2
(SP)←(SP)+exp
这里的exp是一个表达式,根据它的值计算出来的常数将被写入到机器指令中,作为栈顶指针调整的参数使用,决定栈顶指针调整的字节数。
虽然RET指令实际上包括近返回和远返回两种不同的返回方式,但是其汇编助记符是相同的,汇编程序会根据过程定义等相关信息自动生成合适的近返回或远返回指令。有一些汇编程序还支持用RETN表示近返回,用RETF表示远返回。
RET指令有两种不同的格式,其中第二种格式(即RET exp)除了和第一种格式(RET)一样实现将子程序调用时入栈的返回地址弹出并送到IP或CS:IP中以完成从子程序返回的功能之外,还要负责对栈顶指针SP进行调整,将其加上一个特定的立即数exp。这个功能主要用于在子程序返回时对栈顶指针进行调整。在C或Pascal这类高级语言编译程序的输出中,函数或子程序的临时变量都要存储在堆栈区,传递给函数或子程序的各种参数也要通过堆栈进行传递,当子程序返回的时候,就必须通过RET exp指令对堆栈指针进行调整,调整的距离由exp以字节数为单位表示,这样栈顶指针就可以恢复到参数入栈之前的值。下面这段汇编程序就是Borland公司的Turbo C 2.0集成开发工具中C语言编译器tcc以PASCAL调用模式编译后形成的结果片段。
在这个程序片段中包括了ADDP和MAIN两个子程序,其中MAIN子程序为C语言中main()函数编译的结果,即C程序的主程序,在这里也是作为调用者的角色而存在。ADDP子程序用来实现将传入的两个参数相加并在AX寄存器中返回的功能。假设在MAIN子程序的mov ax,1指令之前SP的值为10H,随着程序的执行,系统的堆栈将发生如下变化:
●在mov ax,1指令执行之前,堆栈的状态:
●两条MOV和PUSH指令执行之后的堆栈:
●CALL指令执行后的堆栈:
●ADDP子程序的PUSH指令和MOV BP,SP指令执行后的堆栈:
这时,BP+4和BP+6所指向的正好是MAIN子程序中两个PUSH指令入栈的两个参数,从而实现了参数的传递。
●ADDP子程序的POP BP指令执行后的堆栈:
●ADDP子程序的RET 4指令执行后的堆栈:
RET 4指令在实现从ADDP子程序返回的同时,还将SP加4,从而将MAIN子程序中两条PUSH指令压入堆栈的参数跳过,将栈顶指针回复到调用之前的值,完成参数的清除工作。被压入堆栈的参数、返回地址等数据这时还保留在堆栈之中,并未被破坏,直到下一次有数据入栈时才被覆盖。
CALL与RET指令都不影响标志位。
5.中断与中断返回指令
中断与中断返回指令用于触发中断(例外或陷入)和从中断处理程序返回。主要有以下三条指令。
●INT
格式:INT n
中断指令,用于触发中断,其中n为中断类型号,必须在0~255之间。如果不提供中断类型号,则默认中断类型号为3。执行的操作:
(SP)←(SP)-2
(SP)←(SP)-2
((SP)+1,(SP))←(CS)
(SP)←(SP)-2
((SP)+1,(SP))←(IP)
(IP)←(n*4)
(CS)←(n*4+2)
这段操作实际上完成两个功能。首先将标志位、当前的CS:IP入栈,保存处理器现场,供将来中断返回时IRET指令使用,然后根据中断类型号n的值计算出中断向量的位置,并将中断向量装入CS:IP,转移到系统的中断处理子程序继续执行。
●INTO
格式:INTO
INTO指令执行的操作与INT指令基本相同,区别主要有两点:①中断号n被确定为4,即溢出中断。②INTO指令的执行状态和OF状态位有关,只有当OF=1时中断才会被触发,所以INTO指令又被称作“溢出则中断”指令。
●IRET
格式:IRET
中断返回指令,负责从中断处理程序中返回。执行的操作:
(IP)←((SP)+1,(SP))
(SP)←(SP)+2
(CS)←((SP)+l,(SP))
(SP)←(SP)+2
(PSW)←((SP)+1,(SP))
(SP)←(SP)+2
实际上这些操作就是将INT或INTO指令所保存的处理器现场加以恢复,以返回到中断发生时的中断点继续执行原来的程序。
INT和INTO指令将IF和TF标志位置0,以避免再次陷入中断,不影响其他标志位。IRET指令使用堆栈中的内容覆盖标志位,对标志位的影响视堆栈中的内容而定。
6.处理机控制指令
处理机控制指令可以被粗略地分为两类:标志位设置指令和其他处理机控制指令。标志位设置指令用于将部分标志位置1或清0,包括以下几种。
●CLC
格式:CLC
进位位置0指令(Clear carry)CF←0
●CMC
格式:CMC
进位位求反指令(Complement carry)CF←NOT CF
●STC
格式:STC
进位位置1指令(Set carry)CF←1
●CLD
格式:CLD
方向标志位置0指令(Clear direction)DF←0
●STD
格式:STD
方向标志位置1指令(Set direction)DF←1
●CLI
格式:CLI
中断标志置0指令(Clear interrupt)IF←0
●STI
格式:STI
中断标志置1指令(Set interrupt)IF←1
这些指令只影响本指令指定的标志,不影响其他标志位。
其他处理机控制指令用于控制处理机状态,包括以下几种。
●NOP
格式:NOP
无操作指令,该指令不执行任何操作,其机器码占有一个字节单元,在调试程序时往往用这条指令占有一定的存储单元,以便在正式运行时用其他指令取代。
●HLT
格式:HLT
停机指令,该指令可使机器暂停工作,使处理机处于停机状态以便等待一次外部中断到来,中断结束后可继续执行下面的程序。
●WAIT
格式:WAIT
等待指令,该指令使处理机处于空转状态,它也可以用来等待外部中断发生,但中断结束后仍返回WAIT指令继续等待。
●ESC
格式:ESC mem
换码指令,其中mem指出一个存储单元,ESC指令把该存储单元的内容送到数据总线去。ESC指令不允许使用立即数和寄存器寻址方式。这条指令在使用协处理机(Coproces- sor)执行某些操作时,可从存储器取得指令或操作数。协处理机(如8087)是为提高速度而可以选配的硬件。
●LOCK
LOCK是一种指令前缀,它可与其他指令联合,用来维持总线的锁存信号直到与其联合的指令执行完为止。当CPU与其他处理机协同工作时,该指令可避免破坏有用信息。
这些处理机控制指令都不影响条件码。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。