assembly 有多少种方法可以将寄存器设置为零?

to94eoyn  于 2023-04-21  发布在  其他
关注(0)|答案(9)|浏览(137)

我很好奇在x86汇编中有多少种方法可以将寄存器置零。使用一条指令。有人告诉我,他设法找到了至少10种方法来做到这一点。
我能想到的有:

xor ax,ax
mov ax, 0
and ax, 0
wdebmtf2

wdebmtf21#

在IA32下,有很多可能性如何将0移动到ax ...

lea eax, [0]
    mov eax, 0FFFF0000h         //All constants form 0..0FFFFh << 16
    shr  ax, 16                 //All constants form 16..31
    shl eax, 16                 //All constants form 16..31

也许是最奇怪的...:)

@movzx:
    movzx eax, byte ptr[@movzx + 6]   //Because the last byte of this instruction is 0

并且还在32位模式中(较长的指令将最后(最高有效)地址字节放置在后面)…

@movzx:
    movzx ax, byte ptr[@movzx + 7]

编辑:

对于16位x86 cpu模式,未测试...:

lea  ax, [0]

然后...

@movzx:
    movzx ax, byte ptr cs:[@movzx + 7]   //Check if 7 is right offset

如果ds段寄存器不等于cs段寄存器,则**cs:**前缀是可选的。

cnjp1d6j

cnjp1d6j2#

请参阅以下答案,了解将寄存器归零的最佳方法: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

xor reg,regcan be encoded two different ways这样的指令。在GAS AT&T语法中,我们可以请求汇编器选择哪个操作码。这只适用于允许两种形式的reg,reg整数指令,即可以追溯到8086。所以不是SSE/AVX。

{load}  xor %eax, %eax           # 31 c0
  {store} xor %eax, %eax           # 33 c0

对于常规大小的GP寄存器,“将所有位移出一端”是不可能的,只有部分寄存器。shlshr移位计数被屏蔽(在286及更高版本上):count & 31;,即mod 32。
(立即计数移位在186中是新的(以前只有CL和隐式-1),因此有一些CPU具有未屏蔽的立即移位(也包括NEC V30)。此外,286和更早的版本仅为16位,因此ax是“完整”寄存器。有些CPU的移位可以将完整的整数寄存器归零。)
还要注意,向量的移位计数饱和而不是环绕。

# 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

Vector regs

**这些SSE 2整数指令中的一些也可以在MMX寄存器(mm0-mm7)上使用。**我不打算单独展示。

同样,最好的选择是某种形式的xor。PXOR/VPXORXORPS/VXORPS。请参阅在x86汇编中将寄存器设置为零的最佳方法:xor、mov或和?以了解详细信息。
AVX vxorps xmm0,xmm0,xmm0将完整的ymm 0/zmm 0置零,并且是better than vxorps ymm0,ymm0,ymm0 on AMD CPUs

这些归零指令各有三种编码:传统SSE、AVX(VEX前缀)和AVX 512(EVEX前缀),尽管SSE版本仅将底部128置零,但这不是支持AVX或AVX 512的CPU上的完整寄存器。无论如何,根据您的计数方式,每个条目可以是三个不同的指令(相同的操作码,只是不同的前缀)。除了vzeroall,AVX 512没有改变(也没有零zmm 16 -31)。

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。)

VPXOR      xmm15, xmm0, xmm0      ; AVX1 VEX
VPXOR      ymm15, ymm0, ymm0      ; AVX2 VEX, less efficient on some CPUs
VPXORD     xmm31, xmm0, xmm0      ; AVX512VL EVEX
VPXORD     ymm31, ymm0, ymm0      ; AVX512VL EVEX 256-bit
VPXORD     zmm31, zmm0, zmm0      ; AVX512F EVEX 512-bit

VPXORQ     xmm31, xmm0, xmm0      ; AVX512VL EVEX
VPXORQ     ymm31, ymm0, ymm0      ; AVX512VL EVEX 256-bit
VPXORQ     zmm31, zmm0, zmm0      ; AVX512F EVEX 512-bit

不同的矢量宽度在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

VPTERNLOGD zmm0, zmm0,zmm0, 0     ; inputs can be any registers you like.

**掩码寄存器(k0..k7)归零:**掩码指令,矢量比较到掩码

kxorB/W/D/Q     k0, k0, k0     ; narrow versions zero extend to max_kl
kshiftlB/W/D/Q  k0, k0, 100    ; kshifts don't mask/wrap the 8-bit count
kshiftrB/W/D/Q  k0, k0, 100
kandnB/W/D/Q    k0, k0, k0     ; x & ~x

; compare into mask
vpcmpB/W/D/Q    k0, x/y/zmm0, x/y/zmm0, 3    ; predicate #3 = always false; other predicates are false on equal as well
vpcmpuB/W/D/Q   k0, x/y/zmm0, x/y/zmm0, 3    ; unsigned version

vptestnmB/W/D/Q k0, x/y/zmm0, x/y/zmm0       ; x & ~x test into mask

x87 FP:

只有一个选择(因为如果旧值是infinity或NaN,sub将不起作用)。

FLDZ    ; push +0.0
wydwbb8l

wydwbb8l3#

还有几种可能性:

sub ax, ax

movxz, eax, ah

编辑:我应该注意到,movzx并没有将所有eax置零--它只是将ah置零(加上本身不能作为寄存器访问的前16位)。
至于最快,如果内存足够,subxor是等价的。它们比(大多数)其他的,因为它们是足够常见的CPU设计师添加了特殊的优化。具体来说,对于普通的subxor,结果取决于寄存器中的前一个值。CPU识别与自身的异或和从之后的任何指令都不会依赖于任何先前的值,因此它可以使用重命名寄存器并行执行先前和随后的指令。
特别是在较老的处理器上,我们预计'mov reg,'会更慢,仅仅是因为它有额外的16位数据,而大多数早期的处理器(尤其是8088)的限制主要在于它们从内存加载流的能力--事实上,在8088上,您可以使用任何参考表非常准确地估计运行时间只需要注意所涉及的字节数。这确实会对dividiv指令造成影响,但仅此而已。OTOH,我可能应该闭嘴,因为8088真的没有多少人感兴趣(至少十年了)。

toe95027

toe950274#

当然,在特定情况下,还有其他方法可以将寄存器设置为0:例如,如果您将eax设置为正整数,则可以使用cdq/cltdedx设置为0(此技巧用于著名的24字节shellcode,出现在“不安全编程示例”中)。

iszxjhcz

iszxjhcz5#

您可以使用LOOP $将寄存器CX设置为0。

g52tjvyc

g52tjvyc6#

这个线程是旧的,但其他一些例子.简单的:

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
qgzx9mmu

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源代码中:

AAD 0         ; assemblers like NASM accept this

db 0xd5,0x00  ; others many need you to encode it manually

显然(至少在某些CPU上),bswap eax前面的66个操作数大小的前缀(即66 0F C8作为对bswap ax进行编码的尝试)会使AX为零。

e5nqia27

e5nqia278#

在评论中,OP写道移位不能使用立即计数(80186/80286引入)。因此目标x86 CPU必须是8086/8088。(10年前这个问题肯定更好地标记为[8086]而不是最近(5年?)引入的[x86-16])
8086体系结构提供14个基本程序执行寄存器,用于一般系统和应用程序编程。这些寄存器可按如下方式分组:
·AXBXCXDXSIDIBPSP通用寄存器。这八个寄存器可用于存储操作数和指针。
·CSDSESSS段寄存器。这些寄存器允许寻址超过64 KB的内存。
·FLAGS寄存器。该寄存器报告正在执行的程序的状态,并允许对处理器进行应用程序级控制。
·IP寄存器。此指令指针寄存器包含指向要执行的下一条指令的16位指针。
因此,关于清除x86上的a寄存器的问题的答案可以处理上述任何寄存器的归零,当然除了FLAGS寄存器,该寄存器在架构上定义为始终在其第二位位置保持1。
接下来是可以在8086和上清除寄存器而不依赖于任何预先存在的条件的单个指令列表。该列表按字母顺序排列:

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

此列表显示了技术上可行的内容,当然不是您应该使用的内容。标有(*)的说明非常危险或只能谨慎使用。
不言而喻,要使calljmp工作,您需要在目标位置上有可执行代码。
清除通用寄存器的最佳方法是使用xor reg, reg,如果不想更改任何标志,则使用mov reg, 0

cx6n0qe3

cx6n0qe39#

mov eax,0  
shl eax,32  
shr eax,32  
imul eax,0 
sub eax,eax 
xor eax,eax   
and eax,0  
andn eax,eax,eax 

loop $ ;ecx only  
pause  ;ecx only (pause="rep nop" or better="rep xchg eax,eax")

;twogether:  
push dword 0    
pop eax

or eax,0xFFFFFFFF  
not eax

xor al,al ;("mov al,0","sub al,al",...)  
movzx eax,al
...

相关问题