X86汇编条件码详解

作者: 张慕晖 | 来源:发表于2017-09-24 20:58 被阅读1592次

    本文同时发布于我的博客(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.htmlhttp://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 低于或相等(无符号<=)

    参考文献

    1. x86—EFLAGS寄存器详解. http://blog.csdn.net/jn1158359135/article/details/7761011
    2. EFLAGS Register. https://en.wikibooks.org/wiki/X86_Assembly/X86_Architecture#EFLAGS_Register
    3. x86 Instruction Set Reference. http://x86.renejeschke.de/
    4. 《深入理解计算机系统》第三章第6节.

    相关文章

      网友评论

        本文标题:X86汇编条件码详解

        本文链接:https://www.haomeiwen.com/subject/ygllextx.html