在IA-32指令集中没有高级的逻辑结构,但无论多么复杂的结构,都可以使用比较和跳转指令组合来实现。执行条件语句包括两个步骤:首先,使用CMP,AND,SUB之类的指令修改CPU标志六七次,使用条件跳转指令测试并导致向新地址的分支转移。
例子1:使用CMP指令比较AL和0,如果CPU指令设置了零标志,那么JZ(为0则跳转)指令就跳转到标号L1处:
cmp al ,0
jz L1
.
.
L1:
条件跳转指令在标志条件为真时分支跳转到新的目的标号处,如果条件标志为假,那么执行紧跟在跳转跳转指令之后的指令。格式如下:
jcond 目标地址
格式中的cond指的是一个标志条件,用来表示一个或多个标志的状态。例如:
jc 如果进位则跳转
jnc 如果无进位则跳转
jz 如果为零则跳转
jnz 如果不为零则跳转
我们已经知道算数指令。比较指令和布尔指令几乎重视会设置标志位、条件跳转指令检查标志位的状态并且根据标志位的状态决定是否跳转。
限制:MASM在默认情况下要求跳转的目的地址在当前的过程之内,为了突破这种限制,可以声明一个全局标号(标号后面跟”::”):
jc MyLabel
.
.
MyLabel::
通常,应尽量避免跳转到当前的过程之外,否则调试程序时会比较困难。
在Intel386之前,跳转的目标地址被限制在跳转指令后的第一条指令+128~-127个字节范围之内。IA-32处理器可跳转到当前段内的任何地址。
使用CMP指令:假设我们想在AX等于5时跳转到位置L1处。假设AX等于5,CMP指令设置了零标志,由于零标志置位了,执行JE指令就会发生跳转:
cmp ax,5
je L1 ;相等则跳转
如果AX不等于5,CMP就会清楚零标志,执行JE指令就不会发生跳转,在下面的例子中,由于AX小于6而发生了跳转:
mov ax ,5
cmp ax,6
jl L1 ;如果小于则跳转大于是(jg)
6.3.3 条件跳转指令的类型
IA-32指令集中跳转中的数目惊人地多,支持根据有符号、无符号整数的比较以及对CPU状态标志的检查进行跳转的一系列指令,跳转跳转指令可分成下面四类:
1.基于特定的标志值。
2.根据两个操作数是否相等,或根据(E)CX的值的。
3.基于无符号操作数的比较结果的。
4.基于有符号操作数的比较结果的。
下表列出了基于特定CPU标志:零标志、进位标志、溢出标志、奇偶标志和符号标志的跳转指令。
基于恒等性比较的跳转指令
下表列出了基于两个操作数是否相等或CX,EXC值是否为零的跳转指令。
CMP leftOp ,rightOp
JE指令和JZ指令是等价的,JNZ指令和JNE指令时等价的。
基于无符号数比较的跳转指令
基于有符号数比较的跳转指令
6.3.4 条件跳转的应用
测试状态位
AND,OR,CMP,NOT和TEST指令后面常跟能够改变程序流程的条件跳转指令,条件跳转指令通常要测试CPU状态标志位的值。例如,假设8位的内存操作数status中存放着同计算机相连的外部设备信息,下面的指令在位5置位时跳转到某标号处,表示机器处于脱机状态:
mov al ,status
test al,00100000b ;测试位5
jnz EquipOffline
下面的语句在位0位1位4中的任何一位置位时跳转到另一个标号处:
mov al ,status
test al ,00010011b ;测试位0,1,4
jnz InputDataByte
如果想在2,3,7全部置位时跳转某标号处,需要使用AND和CMP两条指令:
mov al ,status
and al ,10001100b
cmp al ,10001100b
je ResetMachine
取两个整数中的较大的值:下面的指令比较AX和BX中的无符号整数并把其中的较大者送DX寄存器:
mov dx ,ax ;假设AX较大
cmp ax ,bx
jae L1 ;如果A>=B 就直接跳转到L1,否则就把较大的值B放到结果dx
mov dx ,bx
L1:
取三个整数中的较小值:下面的指令比较V1,V2和V3三个无符号变量的值,并把其中的最小者送至AX寄存器:
.data
V1 WORD ?
V2 WORD ?
V3 WORD ?
.code
mov ax ,V1 ;假设V1是最小的
cmp ax ,V2
jbe L1 ;小于等于则跳转
mov ax ,V2
L1: cmp ax ,V3
jbe L2
mov ax ,V3
L2:
应用:数组的顺序查找
在数组中找到第一个非零值。
应用:字符串加密(XOR)
TITLE Ecnryption Program (Encrypt.asm)
INCLUDE Irvine32.inc
KEY = 239
BUFMAX = 128
.data
sPrompt BYTE "Enter the plain text:" ,0
sEncrypt BYTE "Cipher text: " ,0
sDecrypt BYTE "Decrypted: " ,0
buffer BYTE BUFMAX+1 DUP(0)
bufSize DWORD ?
.code
main PROC
call InputTheString
call TranslateBuffer
mov edx ,OFFSET sEncrypt
call DisplayMessage
call TranslateBuffer
mov edx ,OFFSET sDecrypt
call DisplayMessage
exit
main ENDP
;-----------------------------------
InputTheString PROC
;
;Prompts user for a plaintext string.Saves the string
;and its length
;Receives:nothing
;Returns: nothing
;-----------------------------------
pushad
mov edx ,OFFSET sPrompt
call WriteString
mov ecx ,BUFMAX
mov edx ,OFFSET buffer
call ReadString
mov bufSize ,eax
call Crlf
popad
ret
InputTheString ENDP
;-----------------------------------
DisplayMessage PROC
;
;Display the ecnrypted or decrypted message.
;Receives :EDX points to the message
;Returns :nothing
;-----------------------------------
pushad
call WriteString
mov edx ,OFFSET buffer
call WriteString
call Crlf
call Crlf
popad
ret
DisplayMessage ENDP
;-----------------------------------
TranslateBuffer PROC
;
;Translates the string by exclusive-ORing each
;byte with the encryption key buye.
;Receives :nothing
;Returns :nothing
pushad
mov ecx ,bufSize
mov esi ,0
L1:
xor buffer[esi] ,KEY
inc esi
loop L1
popad
ret
TranslateBuffer ENDP
END main
6.3.5 位测试指令
BT ,BTC ,BTR和BTS指令统称为位测试(bit testing)指令,这些指令很重要,因为他们可以在单挑院子指令内执行很多个步骤。为测试指令对多线程程序非常有用,对多线程程序而言,在不冒被其他线程中断的危险的情况下对重要的标志(称为信号量)进行测试、清除、设置或求反是非常重要的。
BT指令
BT(位测试,bit test)指令选择第一个操作数位n并把它复制到进位标志中:
BT bitBase,n
第一个操作数成为位基(bitBase),它不会被指令所修改。BT指令允许以下类型操作数:
BT r/m16 ,r16
BT r/m32 ,r32
BT r/m16 ,imm8
BT r/m32 ,imm8
在下例中,进位标志等于变量semaphone第七位的值:
.data
semaphone WORD 10001000b
.code
BT semaphone ,7 ;CF = 1
在Intel指令集引入BT指令之前,我们不得不把变量复制到寄存器中,然后再通过移位把第7位送到进位标志中:
mov ax ,semaphone
shr ax ,8 ;CF = 1
BTC指令
BTC(位测试并取反,bit test and complement)指令选择第一个操作数的位n并把它复制到标志位中,同时对位n取反。
BTR指令
BTR(位测试并复位,bit test and reset)指令选择第一个操作数位n并把它复制到进位标志中,同时位n清零。
BTS指令
BTS指令(位测试并置位,bit test and set)指令选择第一个操作数的位n并把它复制到进位标志中,同时位n置位。
LOOPZ和LOOPE指令:ecx大于0,并且零标志位置位则循环。
LOOPNZ和LOOPNE指令:ecx大于0,并且零标志位复位则循环。
条件结构可认为是在不同的逻辑分支之间引发选择的一个或多个条件表达式,每个分支都会执行不同的指令序列。
这节不想细总结了,书上很大的篇幅就是为了解释用cmp和j*来对应解释高级语言里那些IF语句什么的。没什么新东西。
这节是说在高级语言中的类似if(A And B) [A&&B] if(A OR B) [A||B]等在汇编里怎么对应翻译,其实比较简单,比如A&&B 可以判断A满足然后跳转到B继续判断,有一个不满足那么直接jmp到其他地方,A||B的话就是先判断A,如果满足 那么jmp到满足,否则判断B如果满足那么jmp到满足,否则jmp到不满足,这样也就明白了为什么在高级语言中 a = 0 ,b = 0 if(++a || ++b)..之后b没有被自加的原因(a = 1 ,b = 0)。比较简单,这一节也不细总结了。
额...这节是说高级语言中的while怎么解析成汇编,比较简单,我们直接mark一个地方,然后满足条件就直接往上跳回去就行了,如果不满足那么就不跳,这样就自动往下执行,相当于while完了,当然也可以增加其他cmp,j*指令实现break等。
原理是对于多层条件语句嵌套那种,翻译成汇编会很乱,很麻烦,同时如果我们用汇编开发过程中出现了太多层的条件嵌套写着也麻烦,有一种方法是我们把每个条件和执行地址看成一个”结构体”,然后在创建一个”结构体数组”,例如
条件A执行00000120处的函数,或者跳转到那里,条件B则是00000130...这样我们每次只要线性扫描这个表就行了,这个姿势叫做以表格驱动的分支选择。
例子程序:用户从键盘输入一个字符,程序使用一个循环将该字符同表中每个项比较,对于找到的第一个匹配项,紧跟在待查找值其后存储的过程将被调用。每个过程使用EDX来装入不同的字符串偏移,然后在循环中显示该字符串:
TITLE Table of Procedure Offsets (ProcTable.asm)
;This program contains a table with offsets of procedures.
;It uses the table to execute indirect procedure calls.
INCLUDE Irvine32.inc
.data
CaseTable BYTE 'A'
DWORD Process_A
EntrySize = ($ - CaseTable)
BYTE 'B'
DWORD Process_B
BYTE 'C'
DWORD Process_C
BYTE 'D'
DWORD Process_D
NumberOfEntries = ($ - CaseTable) / EntrySize
prompt BYTE "Press capital A,B,Cor D:" ,0
msgA BYTE "Process_A" ,0
msgB BYTE "Process_B" ,0
msgC BYTE "Process_C" ,0
msgD BYTE "Process_D" ,0
.code
main PROC
mov edx ,OFFSET prompt
call WriteString
call ReadChar
mov ebx ,OFFSET CaseTable
mov ecx ,NumberOfEntries
L1:
cmp al ,[ebx]
jne L2
call NEAR PTR [ebx + 1]
call WriteString
call Crlf
jmp L3
L2:
add ebx ,EntrySize
loop L1
L3:
exit
main ENDP
Process_A PROC
mov edx ,OFFSET msgA
ret
Process_A ENDP
Process_B PROC
mov edx ,OFFSET msgB
ret
Process_B ENDP
Process_C PROC
mov edx ,OFFSET msgC
ret
Process_C ENDP
Process_D PROC
mov edx ,OFFSET msgD
ret
Process_D ENDP
END main
同时上面有几个个地方需要注意一下:
1.一个是函数名字直接可以当变量用了:
2.A=B不占用.data定义东西的时候的地址空间(不然的话,上面那么写代码就算错了)
3.CALL指令调用存储在EBX+1内存地质处的过程地址,这种间接调用格式要使用NEAR PTR运算符。