如何在 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
我想为 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 上被打破,因为那样就不会有
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