如何设置或清除x86汇编中的溢出标志?

时间:2016-04-22 16:05:59

标签: assembly x86 eflags

我想写一个简单的代码(或算法)来设置/清除溢出标志。对于设置OF,我知道我可以使用有符号值。但我怎么能清楚呢?

3 个答案:

答案 0 :(得分:2)

提供:

  • 您有一个注册簿,您不关心其内容,
  • 您必须保留CF-旗帜

清除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,但请注意,现代Intel CPU根本没有部分标志停顿或标志合并的内容:读取标志just read either or both of CF or the SPAZO group as 2 separate inputs的指令。 (这就是为什么cmovbe在Broadwell及更高版本上仍然是2 oups:它需要CF和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

或者(反面):

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

  • 启用了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

答案 1 :(得分:1)

有许多可能的解决方案。

例如, 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建议。 (效果相同,但后者因性能原因更好)

答案 2 :(得分:0)

popf is quite slow (like one per 20 cycles on Skylake);如果你需要清除或设置OF然后理想地将它作为ALU指令的副作用,特别是你将要使用的一个有用的计算,你知道不会或将溢出。 (一个会溢出的通常很难找到,不像CF那样你总是可以只用sub而不是add一个常量几乎一直包围所有输入,除了非常小的范围)

如果您因某种原因需要设置/清除 OF而不影响其他条件代码,那么是,pushf / popf是可行的方法。 lahf / sahf不会获得OF,因为OF在EFLAGS中位于第11位,低于8位。

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

xor eax,eax清除EAX,清除OF / SF / CF,设置ZF / PF 。无论如何你经常需要一个归零的寄存器,所以如果你需要OF清除(例如adox extended-precision chain的开头),那么一石二鸟并安排你的代码,这样最后的标志设置指令就是xor-调零。

在x86-64中,您还可以相信在指针+长度上使用add并不会跨越无符号虚拟地址空间的中间,从而清除OF。但是这个假设可能会破坏未来拥有完全64位虚拟地址的CPU,因为那时就没有hole in virtual address space around the signed-wraparound boundary,所以一个连续的数组可以跨越它。这已经发生在32位代码中,在64位内核或32位内核下运行,不使用2G:2G内核:用户拆分虚拟地址空间。

xor eax, eax / cmp al, -128设置OF,只需要4个字节的代码。它可能是最便宜的方式,与sub或其他什么不同,它不会写任何部分寄存器(或任何完整的寄存器)。它仍然使EAX归零。

0 - -128 wraps to -128, i.e. signed OF。 8位2的补码整数只能表示来自-128..+127的值。最负数是特殊情况,没有正确的反转。它有自己的绝对值/负值,或者更恰当的是这些函数溢出。 (或者您可以将绝对值操作视为具有有符号输入和无符号输出,因此结果为+128,即0x80。x86没有整数abs指令(准备-x,然后测试/ cmov),但是对于SSSE3,它确实有向量整数pabsb

对于除-1以外的AL中的任何已知值,有一个cmp al, imm8将设置OF。对于0..127的任何值,cmp al, -128换行。对于-2 ..- 128的任何值,cmp al, +127包装并因此设置OF。对于-1,减去127只会将您带到-128。减去-128可以达到+127。不幸的是,我不认为在寄存器中没有已知值的情况下设置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