请参阅以下答案,了解将寄存器归零的最佳方法:xor eax,eax(性能优势和较小的编码)。 我将只考虑一条指令可以将寄存器置零的方法,如果允许从内存中加载零,则有太多的方法,因此我们将主要排除从内存中加载的指令。 我发现有10条不同的单指令可以将32位寄存器归零(因此长模式下的完整64位寄存器),没有任何先决条件或来自任何其他内存的加载。这不包括相同insn的不同编码,或mov的不同形式。如果您计算从已知保存零的内存或段寄存器或其他内存的加载,有很多种方法。2也有无数种方法可以将向量寄存器归零。 对于其中的大多数,eax和rax版本是相同功能的单独编码,两者都将整个64位寄存器置零,要么是zeroing the upper half implicitly,要么是用REX.W前缀显式写入整个寄存器。
整数寄存器(NASM语法):
# Works on any reg unless noted, usually of any size. eax/ax/al as placeholders
and eax, 0 ; three encodings: imm8, imm32, and eax-only imm32
andn eax, eax,eax ; BMI1 instruction set: dest = ~s1 & s2
imul eax, any,0 ; eax = something * 0. two encodings: imm8, imm32
lea eax, [0] ; absolute encoding (disp32 with no base or index). Use [abs 0] in NASM if you used DEFAULT REL
lea eax, [rel 0] ; YASM supports this, but NASM doesn't: use a RIP-relative encoding to address a specific absolute address, making position-dependent code
mov eax, 0 ; 5 bytes to encode (B8 imm32)
mov rax, strict dword 0 ; 7 bytes: REX mov r/m64, sign-extended-imm32. NASM optimizes mov rax,0 to the 5B version, but dword or strict dword stops it for some reason
mov rax, strict qword 0 ; 10 bytes to encode (REX B8 imm64). movabs mnemonic for AT&T. normally assemblers choose smaller encodings if the operand fits, but strict qword forces the imm64.
sub eax, eax ; recognized as a zeroing idiom on some but maybe not all CPUs
xor eax, eax ; Preferred idiom: recognized on all CPUs
; 2 same-size encodings each: r/m, r vs. r, r/m
@movzx:
movzx eax, byte ptr[@movzx + 6] //Assuming the high byte of the absolute address is 0. Not position-independent, and x86-64 RIP+rel32 would load 0xFF
.l: loop .l ; clears e/rcx... eventually. from I. J. Kennedy's answer. To operate on only ECX, use an address-size prefix.
; rep lodsb ; not counted because it's not safe (potential segfaults), but also zeros ecx
# Zeroing methods that only work on 16bit or 8bit regs:
shl ax, 16 ; shift count is still masked to 0x1F for any operand size less than 64b. i.e. count %= 32
shr al, 16 ; so 8b and 16b shifts can zero registers.
# zeroing ah/bh/ch/dh: Low byte of the reg = whatever garbage was in the high16 reg
movxz eax, ah ; From Jerry Coffin's answer
取决于其他现有条件(除了在另一个reg中具有零):
bextr eax, any, eax ; if al >= 32, or ah = 0. BMI1
BLSR eax, src ; if src only has one set bit
CDQ ; edx = sign-extend(eax)
sbb eax, eax ; if CF=0. (Only recognized on AMD CPUs as dependent only on flags (not eax))
setcc al ; with a condition that will produce a zero based on known state of flags
PSHUFB xmm0, all-ones ; xmm0 bytes are cleared when the mask bytes have their high bit set
PXOR xmm0, xmm0 ;; recommended
XORPS xmm0, xmm0 ;; or this
XORPD xmm0, xmm0 ;; longer encoding for zero benefit
PXOR mm0, mm0 ;; MMX, not show for the rest of the integer insns
ANDNPD xmm0, xmm0
ANDNPS xmm0, xmm0
PANDN xmm0, xmm0 ; dest = ~dest & src
PCMPGTB xmm0, xmm0 ; n > n is always false.
PCMPGTW xmm0, xmm0 ; similarly, pcmpeqd is a good way to do _mm_set1_epi32(-1)
PCMPGTD xmm0, xmm0
PCMPGTQ xmm0, xmm0 ; SSE4.2, and slower than byte/word/dword
PSADBW xmm0, xmm0 ; sum of absolute differences
MPSADBW xmm0, xmm0, 0 ; SSE4.1. sum of absolute differences, register against itself with no offset. (imm8=0: same as PSADBW)
; shift-counts saturate and zero the reg, unlike for GP-register shifts
PSLLDQ xmm0, 16 ; left-shift the bytes in xmm0
PSRLDQ xmm0, 16 ; right-shift the bytes in xmm0
PSLLW xmm0, 16 ; left-shift the bits in each word
PSLLD xmm0, 32 ; double-word
PSLLQ xmm0, 64 ; quad-word
PSRLW/PSRLD/PSRLQ ; same but right shift
PSUBB/W/D/Q xmm0, xmm0 ; subtract packed elements, byte/word/dword/qword
PSUBSB/W xmm0, xmm0 ; sub with signed saturation
PSUBUSB/W xmm0, xmm0 ; sub with unsigned saturation
;; SSE4.1
INSERTPS xmm0, xmm1, 0x0F ; imm[3:0] = zmask = all elements zeroed.
DPPS xmm0, xmm1, 0x00 ; imm[7:4] => inputs = treat as zero -> no FP exceptions. imm[3:0] => outputs = 0 as well, for good measure
DPPD xmm0, xmm1, 0x00 ; inputs = all zeroed -> no FP exceptions. outputs = 0
VZEROALL ; AVX1 x/y/zmm0..15 not zmm16..31
VPERM2I/F128 ymm0, ymm1, ymm2, 0x88 ; imm[3] and [7] zero that output lane
# Can raise an exception on SNaN, so only usable if you know exceptions are masked
CMPLTPD xmm0, xmm0 # exception on QNaN or SNaN, or denormal
VCMPLT_OQPD xmm0, xmm0,xmm0 # exception only on SNaN or denormal
CMPLT_OQPS ditto
VCMPFALSE_OQPD xmm0, xmm0, xmm0 # This is really just another imm8 predicate value for the same VCMPPD xmm,xmm,xmm, imm8 instruction. Same exception behaviour as LT_OQ.
SUBPS xmm0, xmm0和类似的不起作用,因为NaN-NaN = NaN,而不是零。 此外,FP指令可以在NaN参数上引发异常,因此即使是CMPPS/PD也只有在您知道异常被屏蔽时才是安全的,并且您不关心可能在MXCSR中设置异常位。将在SNaN上引发#IA。“quiet” predicate 仅抑制QNaN的#IA。CMPPS/PD也可以引发Denormal异常。(AVX 512 EVEX编码可以抑制512位向量的FP异常,沿着覆盖舍入模式) (See the insn set ref entry for CMPPD中的表格,或者最好是Intel的原始PDF,因为HTML提取会破坏该表格。) AVX 1/2和AVX 512 EVEX形式,仅用于PXOR:这些都归零完整的ZMM目的地。PXOR具有两个EVEX版本:VPXORD或VPXORQ,允许使用dword或qword元素进行掩码。(XORPS/PD已经在助记符中区分元素大小,因此AVX 512没有改变这一点。在传统SSE编码中,XORPD总是在所有CPU上浪费代码大小(更大的操作码)与XORPS。)
xor eax,eax
sub eax,eax
and eax,0
lea eax,[0] ; it doesn't look "natural" in the binary
更复杂的组合:
; flip all those 1111... bits to 0000
or eax,-1 ; eax = 0FFFFFFFFh
not eax ; ~eax = 0
; XOR EAX,-1 works the same as NOT EAX instruction in this case, flipping 1 bits to 0
or eax,-1 ; eax = 0FFFFFFFFh
xor eax,-1 ; ~eax = 0
; -1 + 1 = 0
or eax,-1 ; eax = 0FFFFFFFFh or signed int = -1
inc eax ;++eax = 0
encoding instruction register cleared displacement
-------------- --------------- ----------------------- ------------
25 00 00 and ax, 0 AX
83 E0 00 and ax, 0 AX BX CX DX SI DI BP SP
81 E0 00 00 and ax, 0 AX BX CX DX SI DI BP SP
E8 -- -- call 0000h IP -($+3)
9A 00 00 xx yy call yyxxh:0000h IP
9A xx yy 00 00 call 0000h:yyxxh CS
9A 00 00 00 00 call 0000h:0000h (*) IP and CS
E9 -- -- jmp 0000h IP -($+3)
EA 00 00 xx yy jmp yyxxh:0000h IP
EA xx yy 00 00 jmp 0000h:yyxxh CS
EA 00 00 00 00 jmp 0000h:0000h (*) IP and CS
8D 06 00 00 lea ax, [0000h] AX BX CX DX SI DI BP SP
F3 AC rep lodsb CX
F3 AD rep lodsw CX
E2 FE loop $ CX
B8 00 00 mov ax, 0 AX BX CX DX SI DI BP SP
C7 C0 00 00 mov ax, 0 AX BX CX DX SI DI BP SP
F3 A4 rep movsb (*) CX
F3 A5 rep movsw (*) CX
F3 AA rep stosb (*) CX
F3 AB rep stosw (*) CX
29 C0 sub ax, ax AX BX CX DX SI DI BP SP
2B C0 sub ax, ax AX BX CX DX SI DI BP SP
31 C0 xor ax, ax AX BX CX DX SI DI BP SP
33 C0 xor ax, ax AX BX CX DX SI DI BP SP
9条答案
按热度按时间wdebmtf21#
在IA32下,有很多可能性如何将0移动到ax ...
也许是最奇怪的...:)
并且还在32位模式中(较长的指令将最后(最高有效)地址字节放置在后面)…
编辑:
对于16位x86 cpu模式,未测试...:
然后...
如果ds段寄存器不等于cs段寄存器,则**cs:**前缀是可选的。
cnjp1d6j2#
请参阅以下答案,了解将寄存器归零的最佳方法:
xor eax,eax
(性能优势和较小的编码)。我将只考虑一条指令可以将寄存器置零的方法,如果允许从内存中加载零,则有太多的方法,因此我们将主要排除从内存中加载的指令。
我发现有10条不同的单指令可以将32位寄存器归零(因此长模式下的完整64位寄存器),没有任何先决条件或来自任何其他内存的加载。这不包括相同insn的不同编码,或
mov
的不同形式。如果您计算从已知保存零的内存或段寄存器或其他内存的加载,有很多种方法。2也有无数种方法可以将向量寄存器归零。对于其中的大多数,eax和rax版本是相同功能的单独编码,两者都将整个64位寄存器置零,要么是zeroing the upper half implicitly,要么是用REX.W前缀显式写入整个寄存器。
整数寄存器(NASM语法):
像
xor reg,reg
can be encoded two different ways这样的指令。在GAS AT&T语法中,我们可以请求汇编器选择哪个操作码。这只适用于允许两种形式的reg,reg整数指令,即可以追溯到8086。所以不是SSE/AVX。对于常规大小的GP寄存器,“将所有位移出一端”是不可能的,只有部分寄存器。
shl
和shr
移位计数被屏蔽(在286及更高版本上):count & 31;
,即mod 32。(立即计数移位在186中是新的(以前只有CL和隐式-1),因此有一些CPU具有未屏蔽的立即移位(也包括NEC V30)。此外,286和更早的版本仅为16位,因此
ax
是“完整”寄存器。有些CPU的移位可以将完整的整数寄存器归零。)还要注意,向量的移位计数饱和而不是环绕。
取决于其他现有条件(除了在另一个reg中具有零):
Vector regs
**这些SSE 2整数指令中的一些也可以在MMX寄存器(
mm0
-mm7
)上使用。**我不打算单独展示。同样,最好的选择是某种形式的xor。
PXOR
/VPXOR
或XORPS
/VXORPS
。请参阅在x86汇编中将寄存器设置为零的最佳方法:xor、mov或和?以了解详细信息。AVX
vxorps xmm0,xmm0,xmm0
将完整的ymm 0/zmm 0置零,并且是better thanvxorps ymm0,ymm0,ymm0
on AMD CPUs。这些归零指令各有三种编码:传统SSE、AVX(VEX前缀)和AVX 512(EVEX前缀),尽管SSE版本仅将底部128置零,但这不是支持AVX或AVX 512的CPU上的完整寄存器。无论如何,根据您的计数方式,每个条目可以是三个不同的指令(相同的操作码,只是不同的前缀)。除了
vzeroall
,AVX 512没有改变(也没有零zmm 16 -31)。SUBPS xmm0, xmm0
和类似的不起作用,因为NaN-NaN = NaN,而不是零。此外,FP指令可以在NaN参数上引发异常,因此即使是CMPPS/PD也只有在您知道异常被屏蔽时才是安全的,并且您不关心可能在MXCSR中设置异常位。将在SNaN上引发
#IA
。“quiet” predicate 仅抑制QNaN的#IA
。CMPPS/PD也可以引发Denormal异常。(AVX 512 EVEX编码可以抑制512位向量的FP异常,沿着覆盖舍入模式)(See the insn set ref entry for CMPPD中的表格,或者最好是Intel的原始PDF,因为HTML提取会破坏该表格。)
AVX 1/2和AVX 512 EVEX形式,仅用于PXOR:这些都归零完整的ZMM目的地。PXOR具有两个EVEX版本:VPXORD或VPXORQ,允许使用dword或qword元素进行掩码。(XORPS/PD已经在助记符中区分元素大小,因此AVX 512没有改变这一点。在传统SSE编码中,XORPD总是在所有CPU上浪费代码大小(更大的操作码)与XORPS。)
不同的矢量宽度在Intel's PXOR manual entry中以单独的条目列出。
你可以对任何你想要的掩码寄存器使用零掩码(但不是合并掩码);不管你是从掩码中得到一个零还是从向量指令的正常输出中得到一个零。但这不是一个不同的指令。例如:
VPXORD xmm16{k1}{z}, xmm0, xmm0
AVX512:
这里可能有几个选项,但我现在还没有足够的好奇心去挖掘指令集列表,寻找所有的选项。
不过,有一个有趣的例子值得一提:VPTERNLOGD/Q可以将寄存器设置为all-ones,imm 8 = 0xFF。(但在当前实现中,对旧值的依赖性为false)。由于比较指令都比较到掩码中,因此VPTERNLOGD似乎是在我的测试中将Skylake-AVX 512上的向量设置为全1的最佳方法,尽管it doesn't special-case the imm8=0xFF case to avoid a false dependency。
**掩码寄存器(k0..k7)归零:**掩码指令,矢量比较到掩码
x87 FP:
只有一个选择(因为如果旧值是infinity或NaN,sub将不起作用)。
wydwbb8l3#
还有几种可能性:
编辑:我应该注意到,
movzx
并没有将所有eax
置零--它只是将ah
置零(加上本身不能作为寄存器访问的前16位)。至于最快,如果内存足够,
sub
和xor
是等价的。它们比(大多数)其他的,因为它们是足够常见的CPU设计师添加了特殊的优化。具体来说,对于普通的sub
或xor
,结果取决于寄存器中的前一个值。CPU识别与自身的异或和从之后的任何指令都不会依赖于任何先前的值,因此它可以使用重命名寄存器并行执行先前和随后的指令。特别是在较老的处理器上,我们预计'mov reg,'会更慢,仅仅是因为它有额外的16位数据,而大多数早期的处理器(尤其是8088)的限制主要在于它们从内存加载流的能力--事实上,在8088上,您可以使用任何参考表非常准确地估计运行时间只需要注意所涉及的字节数。这确实会对
div
和idiv
指令造成影响,但仅此而已。OTOH,我可能应该闭嘴,因为8088真的没有多少人感兴趣(至少十年了)。toe950274#
当然,在特定情况下,还有其他方法可以将寄存器设置为0:例如,如果您将
eax
设置为正整数,则可以使用cdq/cltd
将edx
设置为0(此技巧用于著名的24字节shellcode,出现在“不安全编程示例”中)。iszxjhcz5#
您可以使用
LOOP $
将寄存器CX设置为0。g52tjvyc6#
这个线程是旧的,但其他一些例子.简单的:
更复杂的组合:
qgzx9mmu7#
根据DEF CON 25 - XlogicX - Assembly Language is Too High Level:
直接基数为0的AAD将始终归零AH,并保持AL不变。从Intel's pseudocode中可以得到:
AL ← (oldAL + (oldAH ∗ imm8)) AND FFH;
在asm源代码中:
显然(至少在某些CPU上),
bswap eax
前面的66个操作数大小的前缀(即66 0F C8
作为对bswap ax
进行编码的尝试)会使AX为零。e5nqia278#
在评论中,OP写道移位不能使用立即计数(80186/80286引入)。因此目标x86 CPU必须是8086/8088。(10年前这个问题肯定更好地标记为[8086]而不是最近(5年?)引入的[x86-16])
8086体系结构提供14个基本程序执行寄存器,用于一般系统和应用程序编程。这些寄存器可按如下方式分组:
·
AX
、BX
、CX
、DX
、SI
、DI
、BP
和SP
通用寄存器。这八个寄存器可用于存储操作数和指针。·
CS
、DS
、ES
和SS
段寄存器。这些寄存器允许寻址超过64 KB的内存。·
FLAGS
寄存器。该寄存器报告正在执行的程序的状态,并允许对处理器进行应用程序级控制。·
IP
寄存器。此指令指针寄存器包含指向要执行的下一条指令的16位指针。因此,关于清除x86上的a寄存器的问题的答案可以处理上述任何寄存器的归零,当然除了
FLAGS
寄存器,该寄存器在架构上定义为始终在其第二位位置保持1。接下来是可以在8086和上清除寄存器而不依赖于任何预先存在的条件的单个指令列表。该列表按字母顺序排列:
此列表显示了技术上可行的内容,当然不是您应该使用的内容。标有(*)的说明非常危险或只能谨慎使用。
不言而喻,要使
call
和jmp
工作,您需要在目标位置上有可执行代码。清除通用寄存器的最佳方法是使用
xor reg, reg
,如果不想更改任何标志,则使用mov reg, 0
。cx6n0qe39#