如何在 x86 程序集中设置或清除溢出标志?

How can I set or clear overflow flag in x86 assembly?

我想为 set/clear 溢出标志编写一个简单的代码(或算法)。对于设置 OF,我知道我可以使用带符号的值。但是我怎样才能清除它呢?

有很多可能的解决方案。

例如,test al, al 将在不影响寄存器内容的情况下清除 OF 标志。


或者,如果不想影响其他标志位,直接修改*FLAGS寄存器即可。例如,在 32 位中,这看起来像:

pushfd                   ; Push EFLAGS onto the stack
and dword [esp], ~0x800  ; Clear bit 11 (OF)
popfd                    ; Pop the modified result back into EFLAGS

编辑:根据 Peter Cordes 的建议将 or al, al 更改为 test al, al。 (效果是一样的,但出于性能原因,后者更好)

popf is quite slow (like one per 20 cycles on Skylake);如果您需要清除或设置 OF,那么理想情况下将其作为 ALU 指令的 side-effect 执行,尤其是无论如何您将要用于有用计算的指令,您知道不会或将会溢出。 (一个会溢出的通常很难找到,不像 CF,在 CF 中你总是可以 sub 而不是 add 并且有一个常量几乎所有输入都环绕除了非常小的范围之外) .

如果出于某种原因你需要set/clear 只是 OF而不影响其他condition-codes,那么是的,pushf/popf 是要走的路。 lahf / sahf 没有得到 OF,因为 OF​​ 是 EFLAGS 中的第 11 位,在低位 8 之外。


test al,al(或任何相同的寄存器)清除 OF 和 CF,就像 。其他标志根据值设置有用。

xor eax,eax清除EAX,清除OF/SF/CF,设置ZF/PF。无论如何你经常需要一个归零的寄存器,所以如果你需要 OF 清除(例如 adox extended-precision chain 的开始),然后用一块石头杀死 2 只鸟并安排你的代码所以最后的 flag-setting 指令是xor-zeroing.

在 x86-64 中,您还可以相信在指针 + 长度上使用 add 不会越过无符号虚拟地址 space 的中间,从而清除 OF.但是这种假设可能会在未来具有完全 64 位虚拟地址的 CPU 上被打破,因为那样就不会有 ,因此单个连续数组可以跨越它。这可能已经发生在 32 位代码中,运行 在 64 位内核或不使用 2G:2G kernel:user 虚拟地址分割的 32 位内核中 space.


xor eax, eax / cmp al, -128组OF,只占用4字节码。这可能是最便宜的方式,与 sub 或其他方式不同,它不写入任何部分寄存器(或任何完整寄存器)。它仍然使 EAX 归零。

0 - -128 wraps to -128, i.e. signed OF. An 8-bit 2's complement integer can only represent values from -128..+127. The most-negative number is a special case, and has no proper inverse. It's its own absolute value / negative, or more properly those functions overflow. (Or you could treat the absolute value operation as having signed input and unsigned output, so the result is +128, i.e. 0x80. x86 doesn't have an integer abs instruction (prepare a -x, then test/cmov), but with SSSE3 it does have vector integer pabsb)

对于 AL 中除 -1 之外的任何已知值,都有一个 cmp al, imm8 将设置 OF。对于 0..127 中的任何值,cmp al, -128 回绕。对于 -2..-128 中的任何值,cmp al, +127 回绕并因此设置 OF。对于 -1,减去 127 只会带你到 -128。减去 -128 得到 +127。不幸的是,我认为没有 single-instruction 方法可以在寄存器中没有已知值的情况下设置 OF。

没有al,但是有cmp al,imm8的2字节特殊编码。其他8位或32位寄存器可以使用正常的3字节编码。


没有破坏任何寄存器,也没有已知常量,这是 6 个字节:

push   rax
xor    eax,eax
cmp    al, -128
pop    rax

这确实破坏了其他条件代码,但它比 pushf/popf 更快。不过,通常你可以破坏某些东西,否则你不能破坏堆栈。


关闭

setno al              # OF=0 -> AL=1           OF=1 -> AL=0
cmp   al, -127        # 1 - -127 = 128 = -128     0 - -127 = +127

提供:

  • 您有一个您不关心其内容的寄存器,
  • 你必须保留 CF-Flag

清除 OF (sar) 的最佳解决方案:

假设寄存器是al。 (setc 仅适用于字节寄存器 r/8)

; clear OF-Flag, preserve CF
setc al
sar al, 1

注意:这很好,因为它没有部分标志更新,这可能会导致停顿。 (sar xx, 1 写入所有标志,不保留任何未修改的标志,这与 inc/dec 不同)c.f。 Intel Optimization Guide, 3.5.2.6: Partial Flag Register Stalls, but note that modern Intel CPUs don't have partial-flag stalls or flag-merging at all: instructions that read FLAGS just read either or both of CF or the SPAZO group as 2 separate inputs. (That's why cmovbe is still 2 uops on Broadwell and later: it needs CF and ZF. https://uops.info/)

来源:Intel Documentation SAR p.1234

一般解(inc/dec):

假设寄存器是al。 (适用于 r/8、r/16、r/32、r/64)

; set OF-Flag, preserve CF
mov al, 0x7F
inc al

; clear OF-Flag, preserve CF
mov al, 0x0
inc al

来源:Intel Documentation INC p.551

或者 (adox):

不同的方法,如果你可以假设:

  • 启用了 adx 的处理器(您使用 grep adx /proc/cpuinfo 检查 cpu 标志)

假设寄存器是eax。 (需要r64/r32)

; clear OF-Flag, preserve CF
mov eax, 0x0
adox eax, eax

; set OF-Flag, preserve CF
mov eax, 0xFFFFFFFF
adox eax, eax 

注意:不要尝试将 mov 替换为 xor(或类似的),因为那样会清除 CF

来源:Intel Documentation ADOX p.150