X86汇编条件码详解
本文同时发布于我的博客(http://ilovestudy.wikidot.com/assembly-conditional-code)。
条件码(conditional code)寄存器描述了最近的算术或逻辑操作的属性,可以检测这些条件码来执行条件分支指令。事实上,条件码的用法十分灵活,所以需要剖析得更仔细一些。
条件码寄存器详解
EFLAGS寄存器用于存储条件码,主要用于提供程序的状态及进行相应的控制。[1]这一32位寄存器的每一位有不同的名称和用途,其中名称为0和1的位是保留位,其值不应该被改变。下表[2]给出了每一位的相关信息:
第?位 | 名称 | 作用 |
---|---|---|
0 | CF | 进位标志(Carry Flag)。若算术操作产生的结果在最高有效位(most-significant bit)发生进位或借位则将其置1,反之清零。这个标志指示无符号整型运算的溢出状态,这个标志同样在多倍精度运算(multiple-precision arithmetic)中使用。 |
1 | 1 | - |
2 | PF | 奇偶标志(Parity Flag)。如果结果的最低有效字节(least-significant byte)包含偶数个1位则该位置1,否则清零。 |
3 | 0 | - |
4 | AF | 调整标志(Adjust Flag)。如果算术操作在结果的第3位发生进位或借位则将该标志置1,否则清零。这个标志在BCD(binary-code decimal)算术运算中被使用。 |
5 | 0 | - |
6 | ZF | 零标志(Zero Flag)。若运算结果为0则将该位置1,反之清零。 |
7 | SF | 符号标志(Sign Flag)。该标志被设置为有符号整型的最高有效位。即如果结果是负数则置为1。 |
8 | TF | 陷阱标志(Trap Flag)。将该位设置为1以允许单步调试模式,清零则禁用该模式。 |
9 | IF | 中断标志(Interruption Flag)。该标志用于控制处理器对可屏蔽中断请求(maskable interrupt requests)的响应。置1以响应可屏蔽中断,反之则禁止可屏蔽中断。 |
10 | DF | 方向标志(Direction Flag)。设置DF标志使得串指令自动递减(从高地址向低地址方向处理字符串),清除该标志则使得串指令自动递增。STD以及CLD指令分别用于设置以及清除DF标志。 |
11 | OF | 溢出标志(Overflow Flag)。当带符号整数运算溢出时置1。 |
12-13 | IOPL | I/O特权标志(I/O Privilege Level field)。指示当前运行任务的I/O特权级(I/O privilege level),正在运行任务的当前特权级(CPL)必须小于或等于I/O特权级才能允许访问I/O地址空间。 |
14 | NT | 嵌套任务标志(Nested Task flag)。这个标志控制中断链和被调用任务。若当前任务与前一个执行任务相关则置1,反之则清零。 |
15 | 0 | - |
16 | RF | 恢复标志(Resume Flag)。控制处理器对调试异常的响应。 |
17 | VM | 虚拟8086模式标志(Virtual-8086 Mode)。置1以允许虚拟8086模式,清除则返回保护模式。 |
18 | AC | 对齐检查标志(Alignment Check)。该标志以及在CR0寄存器中的AM位置1时将允许内存引用的对齐检查,以上两个标志中至少有一个被清零则禁用对齐检查。 |
19 | VIF | 虚拟中断模式标志(Virtual Interrupt Flag)。该标志是IF标志的虚拟镜像(Virtual image),与VIP标志结合起来使用。使用这个标志以及VIP标志,并设置CR4控制寄存器中的VME标志就可以允许虚拟模式扩展(virtual mode extensions)。 |
20 | VIP | 虚拟中断挂起标志(Virtual Interrupt Pending flag)。该位置1以指示一个中断正在被挂起,当没有中断挂起时该位清零。与VIF标志结合使用。 |
21 | ID | ID标志(Identification Flag)。程序能够设置或清除这个标志指示了处理器对CPUID指令的支持。 |
22 | 0 | - |
23 | 0 | - |
24 | 0 | - |
25 | 0 | - |
26 | 0 | - |
27 | 0 | - |
28 | 0 | - |
29 | 0 | - |
30 | 0 | - |
31 | 0 | - |
事实上,我几乎不明白系统标志以及IOPL域(见[1])那堆标志位的意义,只是为了整齐起见抄了一遍。我们接下来要关心的只有与算逻指令相关那些状态标志,包括CF、PF、AF、ZF、SF和OF。
常见算术逻辑指令对条件码的影响
众所周知,很多算术和逻辑指令都会影响条件码。下面给出常见算术逻辑指令对条件码的改变[3]:
指令 | 影响的条件码 | 具体效果 |
---|---|---|
LEA | - | 因为是用于地址计算的,所以不会改变条件码。 |
ADD | OF, SF, ZF, AF, CF, PF | 根据加法运算的结果设置条件码。带符号整数加法溢出时CF置1,无符号整数加法溢出时OF置1。 |
SUB | OF, SF, ZF, AF, PF, CF | 根据减法运算的结果设置条件码。 |
IMUL | OF, CF | 对于单操作数的IMUL指令,当运算结果进位时,OF和CF置1,否则清零。对于其他形式的IMUL指令,当运算结果需要截断时,OF和CF置1,否则清零。SF, ZF, AF, PF的状态无定义。 |
XOR | OF, SF, ZF, PF, CF | OF和CF清零,SF,ZF和PF根据运算结果置位,AF状态无定义。 |
OR | OF, SF, ZF, PF, CF | OF和CF清零,SF,ZF和PF根据运算结果置位,AF状态无定义。 |
AND | OF, SF, ZF, PF, CF | OF和CF清零,SF,ZF和PF根据运算结果置位,AF状态无定义。 |
INC | OF, SF, ZF, AF, PF | CF不受影响,OF, SF, ZF, AF, PF根据运算结果置位。 |
DEC | OF, SF, ZF, AF, PF | CF不受影响,OF, SF, ZF, AF, PF根据运算结果置位。 |
NEG | OF, SF, ZF, AF, CF, PF | 如果源操作数为0,则CF置0;否则CF置1。OF, SF, ZF, AF, PF根据运算结果置位。 |
NOT | - | 不会改变条件码。 |
SAL/SAR/SHL/SHR | OF, SF, ZF, AF, CF, PF | CF置为被最后移出目标操作数的一位;当指令为SHL或SHA且移动位数大于等于目标操作数的位数时,CF的状态无定义。OF只在移动一位时会被改变:左移时,如果源操作数的最高两位相同,OF置0,否则置1;指令为SAR时,OF始终清0;指令为SHR时,OF置为源操作数的最高位。SF,ZF和PF根据运算结果置位。如果移动位数为0,则各标志位不受影响;当移动位数大于0时,AF状态无定义。 |
移位不愧是全部指令中最复杂的。我记得曾经做过一道题,利用SHR指令设置OF的特性和跳转指令来正确地用右移来实现负数除法。这些未必一定要记得,但在不明所以的时候有据可查还是很重要的。
只设置条件码的指令
有两类指令只根据运算结果设置条件码而不保存运算结果:CMP和TEST。
指令 | 影响的条件码 | 具体效果 |
---|---|---|
TEST | OF, SF, ZF, AF, CF, PF | SF,ZF和PF根据两个操作数逻辑与的结果来设置。CF和OF清0,AF无定义。 |
CMP | OF, SF, ZF, AF, CF, PF | 根据两个操作数的差结果来设置条件码(请注意,AT&T语法是第二个减第一个,Intel语法是第一个减第二个)。 |
一般来说,这类指令后面都会跟着各种与条件码的状态相关的指令。
根据条件码的状态而动作的指令[4]
SET、JUMP和条件移动这些指令的运行结果总是与前一指令设置的条件码相关。当它们前面是CMP时,程序语义非常好理解,是TEST时程序语义不太好理解,是其他算术逻辑指令时就很难理解(比如http://blog.csdn.net/ms2146/article/details/5279442 中所举的test和je的例子),需要根据具体情况来具体分析程序的实际语义。我并没有找到完整介绍这些指令及其意义的帖子。http://ekd123.is-programmer.com/posts/28244.html 和http://unixwiz.net/techtips/x86-jumps.html 比较全面地介绍了JUMP系列指令,但是并没有做过多的解释;http://blog.csdn.net/voissurtonchemin/article/details/54173743 里有一些解释,但不太足够。
所以下面是我做的一个在我看来比较全面的总结。
SET系列指令
作用:每条指令根据条件码的某种组合,将一个字节设置为0或1。有些指令是“同义名”,即同一条机器指令的不同名字。
指令 | 英文原义 | 效果 | 设置条件 | 解释 |
---|---|---|---|---|
sete/setz D | set when equal/zero | D = ZF | 相等/零 | 零比较好理解。相等是因为,如果前一个指令是CMP且两个数相等,则结果为0,ZF置位。 |
setne/setnz D | set when not equal/zero | D = ~ZF | 不等/非零 | 与sete/setz的思路相同。 |
sets D | set when sign | D = SF | 负数 | 当运算结果为负时成立。 |
setns D | set when not sign | D = ~SF | 非负数 | 当运算结果非负时成立。 |
setg/setnle D | set when greater than / set when not less than or equal | D = ~(SF ^ OF) & ~ZF | 大于(有符号>) | 如果前一指令为CMP且两(符号)数关系为大于,则减法结果仅有两种可能性:(1)不溢出,结果为正数,SF=0,OF=0;(2)溢出,结果为负数,SF=1,OF=1,即SF与OF相等。若两数相等,则SF=OF=0,仍需加上条件& ~ZF。 |
setge/setnl D | set when greater than or equal / set when not less than | D = ~(SF ^ OF) | 大于等于(有符号>=) | 从setg的条件中去掉结果不为0的要求就行。 |
setl/setnge D | set when less / set when not greater than or equal | D = SF ^ OF | 小于(有符号<) | 如果前一指令为CMP且两(符号)数关系为小于,则减法结果仅有两种可能性:(1)不溢出,结果为负数,SF=1,OF=0;(2)溢出,结果为正数,SF=0,OF=1,即SF与OF不等,且两数不等。不用特意加上两数相等的判断,因为此时SF=OF。 |
setle/setng D | set when less than or equal / set when not greater than | D = (SF ^ OF) | ZF | 小于等于(有符号<=) | 在setl的条件中加上两数相等的可能性,即| ZF。 |
seta/setnbe D | set when above / set when not below or equal | D = ~CF & ~ZF | 超过(无符号>) | 如果前一指令为CMP且两(无符号)数关系为大于,则必然有无符号减法结果不溢出(~CF)和减法结果不为0(~ZF)。 |
setae/setnb D | set when above or equal / set when not below | D = ~CF | 超过或相等(无符号>=) | 从seta的条件中去掉对结果不为0的约束即可。 |
setb/setnae D | set when below / set when not above or equal | D = CF | 低于(无符号<) | 如果前一指令为CMP且两(无符号)数关系为小于,则必然有无符号减法结果溢出。 |
setbe/setna D | set when below or equal / set when not above | D = CF | ZF | 低于或相等(无符号<=) | 在setb的条件中加上两数相等的可能性,即| ZF。 |
其实我写得并不够严谨:要证明条件表示了某一关系,除了说明这一关系符合这一条件,还需要说明其他关系与这一条件均不符合。但是这一点可以通过互补的条件来意会。
因为JUMP和CMOV指令的条件码与SET的大同小异(不,其实根本就是没有差异),所以下面的两个系列指令就只列举指令和描述了。
JUMP系列指令
作用:当跳转条件满足时,这些指令会跳转到一条带标号的目的地。有些指令是“同义名”,即同一条机器指令的不同名字。
指令 | 英文原义 | 跳转条件 | 描述 |
---|---|---|---|
jmp Label | jump | 直接跳转 | 1 |
jmp *Operand | jump | 间接跳转 | 1 |
je/jz Label | jump if equal/zero | ZF | 相等/零 |
jne/jnz Label | jump if not equal/zero | ~ZF | 不等/非零 |
js Label | jump if sign | SF | 负数 |
jns Label | jump if not sign | ~SF | 非负数 |
jg/jnle Label | jump if greater than | ~(SF ^ OF) & ~ZF | 大于(有符号>) |
jge/jnl Label | jump if greater than or equal | ~(SF ^ OF) | 大于等于(有符号>=) |
jl/jnge Label | jump if less | SF ^ OF | 小于(有符号<) |
jle/jng Label | jump if less than or equal | (SF ^ OF) | ZF | 小于等于(有符号<=) |
ja/jnbe Label | jump if above | ~CF & ~ZF | 超过(无符号>) |
jae/jnb Label | jump if above or equal | ~CF | 超过或相等(无符号>=) |
jb/jnae Label | jump if below | CF | 低于(无符号<) |
jbe/jna Label | jump if below or equal | CF | ZF | 低于或相等(无符号<=) |
CMOV系列指令
作用:当传送条件满足时,指令把源值S复制到目的R。有些指令是“同义名”,即同一条机器指令的不同名字。
指令 | 英文原义 | 跳转条件 | 描述 |
---|---|---|---|
cmove/cmovz S, R | move if equal/zero | ZF | 相等/零 |
cmovne/cmovnz S, R | move if not equal/zero | ~ZF | 不等/非零 |
cmovs S, R | move if sign | SF | 负数 |
cmovns S, R | move if not sign | ~SF | 非负数 |
cmovg/cmovnle S, R | move if greater than | ~(SF ^ OF) & ~ZF | 大于(有符号>) |
cmovge/cmovnl S, R | move if greater than or equal | ~(SF ^ OF) | 大于等于(有符号>=) |
cmovl/cmovnge S, R | move if less | SF ^ OF | 小于(有符号<) |
cmovle/cmovng S, R | move if less than or equal | (SF ^ OF) | ZF | 小于等于(有符号<=) |
cmova/cmovnbe S, R | move if above | ~CF & ~ZF | 超过(无符号>) |
cmovae/cmovnb S, R | move if above or equal | ~CF | 超过或相等(无符号>=) |
cmovb/cmovnae S, R | move if below | CF | 低于(无符号<) |
cmovbe/cmovna S, R | move if below or equal | CF | ZF | 低于或相等(无符号<=) |
参考文献
- x86—EFLAGS寄存器详解. http://blog.csdn.net/jn1158359135/article/details/7761011
- EFLAGS Register. https://en.wikibooks.org/wiki/X86_Assembly/X86_Architecture#EFLAGS_Register
- x86 Instruction Set Reference. http://x86.renejeschke.de/
- 《深入理解计算机系统》第三章第6节.