简单条件转移指令
根据单个标志位的值(CF, SF,OF,PF,ZF)来确定是否转移, 如果条件成立,则(EIP) + 位移量 ➡ EIP,否则什么也不做。
注意,这里的EIP在执行本条指令时就已经变成当前指令的下一条指令的地址了,如下例, 0096827D是jnz l1的下一条指令地址,加上机器码75 07中表示偏移量的07就是l1所在处00968284
mov eax, x
00968270 A1 11 90 9D 00 mov eax,dword ptr [x (09D9011h)]
cmp eax, y
00968275 3B 05 15 90 9D 00 cmp eax,dword ptr [y (09D9015h)]
jnz l1
0096827B 75 07 jne l1 (0968284h)
mov ecx,1
0096827D B9 01 00 00 00 mov ecx,1
jmp l2
00968282 EB 05 jmp l1+5h (0968289h)
l1: mov ecx,0
00968284 B9 00 00 00 00 mov ecx,0
l2:
00968289 ……
JZ / JE ZF=1时,转移
JNZ / JNE ZF=0时,转移
JS SF=1时,转移
JNS SF=0时,转移
JO OF=1时,转移
JNO OF=0时,转移
JC CF=1时,转移
JNC CF=0时,转移
JP / JPE PF=1时,转移
JNP / JPO PF=0时,转移
无符号条件转移指令
JA / JNBE 标号 ( CF=0 且 ZF=0,转移)
JAE / JNB 标号 ( CF=0 或 ZF=1,转移)
JB / JNAE 标号 ( CF=1 且 ZF=0,转移)
JBE / JNA 标号 ( CF=1 或 ZF=1,转移)
有符号条件转移指令
JG / JNLE 标号:当 SF=OF 且 ZF=0时,转移
JGE / JNL 标号:当 SF=OF 或者 ZF=1时,转移
JL / JNGE 标号:当 SF≠OF 且 ZF=0时,转移
JLE / JNG 标号:当 SF≠OF 或者 ZF=1时,转移
两种JMP格式
间接转移方式中,除了立即数寻址方式外,其它方式均可以使用。
功能等价的转移指令:
1. JMP L1
2. JMP BUF
3. LEA EBX,BUF
JMP DWORD PTR [EBX]
4. MOV EBX,BUF
JMP EBX
指令地址列表
如果要根据不同的输入跳转执行不同的程序片段,如果要JMP来写会非常麻烦。采用的方法是构造指令地址列表。
比如,
FUNCTAB DD LP1, LP2, LP3
JMP FUNCTAB[EBX*4]
(EBX)=0;跳转到 LP1处
(EBX)=1;跳转到 LP2处
又如,
void arraysubtract_colsfirst( ) {……}
void arraysubtract_rowsfirst( ) {……}
void arraysubtract_onedim ( ) {……}
int main()
{
int i;
void (*funcp[3])() = { arraysubtract_colsfirst ,
arraysubtract_rowsfirst,
arraysubtract_onedim };
funcp[i](); // i=0,1,2 会执行不同的函数
…….
}
这样也做到了从多分支到无分支的转化,比如下面例子
6.3.1 多分支向无分支的转化
例:当x==1时,显示‘Hello,One’;
当x==2时,显示‘Two’;
当x==3时,显示‘Welcome,Three’,……,
即x为不同的值,显示不同的串。
void myprint() { int x; char msg1[] = "Hello,One"; char msg2[] = "Two"; char msg3[] = "Welcome, Three"; char *p[3] = { msg1,msg2,msg3 }; printf("please input 0,1,2 \n"); scanf("%d", &x); printf("%s\n", p[x]); }
编译优化上的利用
编译层面上可以利用这种转化实现优化:
比如对下面这个子函数的优化:
int absdiff(int x, int y) { int result; if (x < y) result = y - x; else result = x - y; return result; }
无分支的写法:
int absdiff(int x, int y)
; _x$ = ecx
; _y$ = edx
push esi
mov esi, ecx
mov eax, edx
sub esi, edx
sub eax, ecx
cmp ecx, edx
cmovge eax, esi
pop esi
ret 0
这样,先分别计算出x - y 和 y - x, 然后通过cmp、cmovge来实现选择。
Switch语句
Switch语句就是采用这种方式,如下例
#include <stdio.h> int main(int argc, char* argv[]) { int x = 3, y = -1, z; char c; c = getch(); switch (c) { case '+': case 'a': // 用 字符’a’来表示‘+’ z = x + y; break; case '-': case 's': // 用 字符’s’来表示‘-’ z = x - y; break; default: z = 0; } printf(" %d %c %d = %d \n", x,c,y,z); return 0; }
可以看出,是通过几种选择的值与其中最小值的差作为偏移量从内存中取数,取出的数作为在一个数组取数的下标,取出的数就是跳转到的地址,实际上就是用指令地址列表来实现的。
与转移指令功能类似的指令
带条件的数据传输指令
上一篇详细写过。
语句格式:cmov*** r32,r32/m32
功 能:在条件“***”成立时,
传送数据,即(r32/m32)→r32。
cmov 是Conditional MOVe的缩写。
要 求:
① r32 表示一个32位的寄存器;
② m32位表示一个内存地址;
m32对应直接、间接、变址、基址加变址寻址;
m32对应的单元的数据类型是双字,即32位。
字节指令
语句格式:set*** opd
功 能:在条件“***”成立时,(opd)⬅ 1,否则 (opd)⬅ 0 。
opd 一般为 一个字节寄存器
如:
cmp eax, ebx
setg cl
seta cl
sete cl
使用单个标志位 设置sete/setz、setc、sets、seto、setp
条件:ZF=1 CF=1 SF=1 OF=1 PF=1
setne/setnz、setnc、setns、setno、setnp
条件:ZF=0 CF=0 SF=0 OF=0 PF=0
使用多个标志位组合设置seta、setb、setg、setl
setae、setbe、setge、setle