wake-up-neo.com

Wie viele Möglichkeiten, ein Register auf Null zu setzen?

Ich bin gespannt, wie viele Möglichkeiten es gibt, ein Register in der x86-Assembly auf Null zu setzen. Mit einer Anweisung. Jemand hat mir erzählt, dass er es geschafft hat, mindestens 10 Möglichkeiten zu finden.

Die, an die ich denken kann, sind:

xor ax,ax
mov ax, 0
and ax, 0
26
user173973

Es gibt viele Möglichkeiten, wie man unter IA32 0 zu Axt bewegen kann ...

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

Und vielleicht das seltsamste ... :)

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

und...

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

Bearbeiten:  

Und für 16-Bit-x86-CPU-Modus, nicht getestet ...:

    lea  ax, [0]

und...

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

Das Präfix cs: ist optional, wenn das ds segment-Register nicht dem cs-Segmentregister entspricht.

12
GJ.

Siehe diese Antwort für die Register " best way to zero"): xor eax,eax (Leistungsvorteile und kleinere Codierung).


Ich werde nur die Möglichkeiten in Betracht ziehen, wie ein einzelner Befehl ein Register auf Null setzen kann. Es gibt viel zu viele Möglichkeiten, wenn Sie das Laden einer Null aus dem Speicher zulassen. Daher werden Anweisungen, die aus dem Speicher geladen werden, größtenteils ausgeschlossen.

Ich habe 10 verschiedene Einzelbefehle gefunden, die ein 32-Bit-Register (und damit das vollständige 64-Bit-Register im Long-Modus) auf Null setzen, ohne Vorbedingungen oder lädt von einem anderen Speicher. Dabei werden nicht verschiedene Kodierungen desselben Inn oder die verschiedenen Formen von mov gezählt. Wenn Sie das Laden aus einem Speicher zählen, von dem bekannt ist, dass er eine Null enthält, oder aus Segmentregistern oder was auch immer, gibt es eine Vielzahl von Möglichkeiten. Es gibt auch eine Vielzahl von Möglichkeiten, Vektorregister auf Null zu setzen.

Bei den meisten dieser Versionen handelt es sich bei den Versionen eax und rax um separate Codierungen für die gleiche Funktionalität. Beide setzen die vollständigen 64-Bit-Register auf Null, entweder implizit auf die obere Hälfte nullen oder das vollständige Register explizit mit einem REX.W schreiben Präfix.

Ganzzahlige Register:

# 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

@movzx:
  movzx eax, byte ptr[@movzx + 6]   //Because the last byte of this instruction is 0.  neat hack from GJ.'s answer

.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

"Alle Bits aus einem Ende verschieben" ist für reguläre GP-Register nicht möglich, nur für Teilregister. shl und shr Schiebezählungen werden maskiert: count &= 31;, äquivalent zu count %= 32;. (Aber bei 286 und früheren Versionen handelt es sich nur um 16-Bit-Werte, daher ist ax ein "full" -Register. Die shr r/m16, imm8-Variable-Count-Form des Befehls wurde hinzugefügt 286, daher gab es CPUs, bei denen eine Verschiebung ein ganzes Ganzzahlregister auf Null setzen kann.)

Beachten Sie auch, dass die Verschiebungszählungen für Vektoren anstelle von Wrapping sättigen.

# 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

Abhängig von anderen vorhandenen Bedingungen (außer einer Null in einem anderen Register):

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

vektorregionen:

Einige dieser SSE2-Integeranweisungen können auch in MMX-Registern verwendet werden (mm0 - mm7). Wieder ist die beste Wahl eine Form von Xor. Entweder PXOR/VPXOR oder XORPS/VXORPS.

AVX vxorps xmm0,xmm0,xmm0 setzt den gesamten Wert von ymm0/zmm0 auf Null und ist besser als vxorps ymm0,ymm0,ymm0 bei AMD-CPUs . Diese Nullstellungsbefehle verfügen über drei Kodierungen: Legacy SSE, AVX (VEX-Präfix) und AVX512 (EVEX-Präfix), obwohl die Version SSE nur die untersten 128-Werte auf Null setzt AVX oder AVX512. Jedenfalls kann jeder Eintrag, abhängig davon, wie Sie zählen, drei verschiedene Anweisungen sein (gleicher Opcode, nur verschiedene Präfixe). Außer vzeroall, an dem sich der AVX512 nicht geändert hat (und zmm16-31 nicht auf null setzt).

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

PXOR       xmm0, xmm0
XORPD      xmm0, xmm0
XORPS      xmm0, xmm0

VZEROALL

# 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 fro the same VCMPPD xmm,xmm,xmm, imm8 instruction.  Same exception behaviour as LT_OQ.

SUBPS xmm0, xmm0 und ähnliches funktionieren nicht, weil NaN-NaN = NaN, nicht Null.

Außerdem können FP -Anweisungen Ausnahmen für NaN-Argumente auslösen, sodass auch CMPPS/PD nur dann sicher ist, wenn Sie wissen, dass Ausnahmen maskiert sind, und Sie möglicherweise keine Ausnahmebits in MXCSR setzen. Sogar die AVX-Version mit ihrer erweiterten Auswahl an Prädikaten erhöht #IA auf SNaN. Die Prädikate "quiet" unterdrücken nur #IA für QNaN. CMPPS/PD kann auch die Denormal-Ausnahme auslösen.

(Siehe die Tabelle in der Eintrag für ref-Satz von Insn für CMPPD oder vorzugsweise in Intels ursprünglichem PDF, da der HTML-Extrakt diese Tabelle durcheinander bringt.)

AVX512:

Es gibt wahrscheinlich mehrere Optionen hier, aber ich bin jetzt nicht neugierig genug, um die Anweisungssatzliste durchzugehen und nach allen zu suchen.

Es gibt jedoch einen interessanten Punkt, den es zu erwähnen gilt: VPTERNLOGD/Q kann ein Register auf all-one setzen, stattdessen mit imm8 = 0xFF. (Hat aber eine falsche Abhängigkeit vom alten Wert, von aktuellen Implementierungen). Da die Compare-Anweisungen alle in einer Maske vergleichen, scheint VPTERNLOGD der beste Weg zu sein, um bei Skylake-AVX512 einen Vektor in meinem Test auf All-Eins zu setzen, obwohl es handelt sich nicht um den Fall imm8 = 0xFF Vermeiden Sie eine falsche Abhängigkeit .

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

x87 FP:

Nur eine Wahl (da sub nicht funktioniert, wenn der alte Wert unendlich oder NaN war).

FLDZ    ; Push +0.0
6
Peter Cordes

Ein paar mehr Möglichkeiten:

sub ax, ax

movxz, eax, ah

Edit: Ich sollte beachten, dass movzxnicht alle eaxauf Null setzt - es ist nur die ahder Nullen (plus der oberen 16 Bits, die nicht als Register in sich selbst zugänglich sind).

Wenn es um den schnellsten Speicher geht, sind subund xoräquivalent, wenn der Speicher dient. Sie sind schneller als die (meisten) anderen, da sie häufig genug sind, dass die CPU-Entwickler spezielle Optimierungen für sie vorgenommen haben. Insbesondere bei einem normalen suboder xorhängt das Ergebnis vom vorherigen Wert im Register ab. Die CPU erkennt das Xor-with-self und das Subtrahieren von sich selbst, sodass sie weiß, dass die Abhängigkeitskette dort unterbrochen ist. Alle Anweisungen danach hängen nicht von früheren Werten ab, sondern können vorherige und nachfolgende Anweisungen unter Verwendung von Umbenennungsregistern parallel ausführen.

Insbesondere bei älteren Prozessoren erwarten wir, dass 'mov reg, 0' langsamer ist, einfach weil es zusätzliche 16 Bit an Daten hat, und die meisten frühen Prozessoren (insbesondere der 8088) waren hauptsächlich durch ihre Fähigkeit begrenzt, den Stream aus dem Speicher zu laden. Auf einem 8088 können Sie die Laufzeit mit allen Referenzblättern ziemlich genau abschätzen, und achten Sie einfach auf die Anzahl der betroffenen Bytes. Bei den Anweisungen divund idivist das nicht der Fall, aber das ist es auch schon. OTOH, ich sollte wahrscheinlich den Mund halten, da der 8088 wirklich für viele von wenig Interesse ist (seit nunmehr einem Jahrzehnt).

4
Jerry Coffin

Sie können das Register CX mit LOOP $ auf 0 setzen.

3
I. J. Kennedy

Natürlich haben spezielle Fälle zusätzliche Möglichkeiten, ein Register auf 0 zu setzen: z. Wenn Sie eax auf eine positive ganze Zahl gesetzt haben, können Sie edx mit einem cdq/cltd auf 0 setzen (dieser Trick wird bei einem bekannten 24-Byte-Shellcode verwendet, der unter "Unsichere Programmierung durch Beispiel" angezeigt wird).

1
ninjalj

Dieser Thread ist alt, aber einige andere Beispiele. Einfache:

xor eax,eax

sub eax,eax

and eax,0

lea eax,[0] ; it doesn't look "natural" in the binary

komplexere Kombinationen:

; 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
not eax    ;++eax = 0
1
Bartosz Wójcik
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
...
0
ARISTOS