Сколько способов установить регистр в ноль?

Мне интересно, сколько существует способов установить регистр в ноль в сборке x86. Используя одну инструкцию. Кто-то сказал мне, что ему удалось найти как минимум 10 способов сделать это.

Те, о которых я могу думать:

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

10 ответов

Решение

Существует много возможностей для перемещения 0 в топор под IA32...

    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

И, пожалуй, самое странное...:)

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

а также...

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

Редактировать:

И для 16-битного режима процессора x86, не проверенного...:

    lea  ax, [0]

а также...

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

Префикс cs: является необязательным, если регистр сегмента ds не равен регистру сегмента cs.

Посмотрите этот ответ для лучшего способа обнулить регистры: xor eax,eax (преимущества в производительности и меньшая кодировка).


Я рассмотрю только способы, которыми одна инструкция может обнулить регистр. Существует слишком много способов, если вы разрешаете загружать ноль из памяти, поэтому мы в основном исключим инструкции, загружаемые из памяти.

Я нашел 10 различных отдельных инструкций, которые обнуляют 32-битный регистр (и, следовательно, полный 64-битный регистр в длинном режиме), без предварительных условий или загрузок из любой другой памяти. Это не считая разных кодировок одного и того же insn или разных форм mov, Если вы посчитаете загрузку из памяти, которая, как известно, содержит ноль, или из регистров сегментов или чего-то еще, есть множество способов. Есть также миллионы способов обнуления векторных регистров.

Для большинства из них версии eax и rax являются отдельными кодировками для одной и той же функциональности, оба обнуляют полные 64-битные регистры, либо обнуляют верхнюю половину неявно, либо явно записывают полный регистр с префиксом REX.W.

Целочисленные регистры:

# 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

"Переместить все биты на один конец" невозможно для регистров GP обычного размера, только для частичных регистров. shl а также shr число сдвигов маскируется: count &= 31; эквивалентно count %= 32;, (Но 286 и более ранние 16-битные, так что ax это "полный" регистр. shr r/m16, imm8 Форма инструкции с переменным счетом была добавлена ​​286, поэтому были процессоры, в которых сдвиг может обнулять регистр полного целого числа.)

Также обратите внимание, что сдвиг учитывает векторы, насыщенные вместо переноса.

# 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

В зависимости от других существующих условий (кроме нуля в другом регистре):

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

векторные регистры:

Некоторые из этих целочисленных инструкций SSE2 также могут использоваться в регистрах MMX (mm0 - mm7). Опять же, лучший выбор - это некоторая форма xor. Или PXOR / VPXOR, или же XORPS / VXORPS,

AVX vxorps xmm0,xmm0,xmm0 нули полный ymm0/zmm0, и лучше, чем vxorps ymm0,ymm0,ymm0 на процессорах AMD. Эти инструкции обнуления имеют три кодировки: устаревшие SSE, AVX (префикс VEX) и AVX512 (префикс EVEX), хотя версия SSE обнуляет только нижние 128, что не является полным регистром на процессорах, которые поддерживают AVX или AVX512. В любом случае, в зависимости от того, как вы считаете, у каждой записи могут быть три разные инструкции (хотя один и тот же код операции, просто разные префиксы). Кроме vzeroall, который AVX512 не изменился (и не обнуляет zmm16-31).

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 и подобное не будет работать, потому что NaN-NaN = NaN, а не ноль.

Кроме того, инструкции FP могут вызывать исключения для аргументов NaN, поэтому даже CMPPS/PD безопасен только в том случае, если вы знаете, что исключения замаскированы и вам не нужно устанавливать биты исключений в MXCSR. Даже версия AVX, с расширенным выбором предикатов, поднимет #IA на СНАН. "Тихие" предикаты только подавляют #IA для QNaN. CMPPS/PD также может поднять Denormal исключение.

(См. Таблицу в записи insn set ref для CMPPD или, предпочтительно, в оригинальном PDF-файле Intel, поскольку извлечение HTML портит эту таблицу.)

AVX512:

Здесь, возможно, есть несколько вариантов, но мне сейчас недостаточно любопытно копаться в списке наборов инструкций и искать их все.

Однако стоит упомянуть один интересный момент: VPTERNLOGD / Q может вместо этого установить регистр для всех единиц с помощью imm8 = 0xFF. (Но имеет ложную зависимость от старого значения, от текущих реализаций). Поскольку все инструкции сравнения сравниваются в маске, VPTERNLOGD, по-видимому, является лучшим способом установить вектор для всех единиц на Skylake-AVX512 в моем тестировании, хотя он не использует специальный случай imm8 = 0xFF, чтобы избежать ложного зависимость.

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

x87 FP:

Только один выбор (потому что sub не работает, если старое значение было бесконечностью или NaN).

FLDZ    ; push +0.0

Еще пара возможностей:

sub ax, ax

movxz, eax, ah

Изменить: я должен отметить, что movzx не обнуляет все eax - это просто ноль ah (плюс 16 верхних битов, которые сами по себе не являются регистром).

Что касается того, чтобы быть самым быстрым, если память служит sub а также xor эквивалентны. Они быстрее, чем (большинство), потому что они достаточно распространены, чтобы разработчики ЦП добавили для них специальную оптимизацию. Конкретно с нормальным sub или же xor результат зависит от предыдущего значения в реестре. Процессор распознает xor-with-self и subtract-from-self специально, поэтому он знает, что цепочка зависимостей там нарушена. Любые инструкции после этого не будут зависеть от какого-либо предыдущего значения, поэтому он может выполнять предыдущие и последующие инструкции параллельно, используя регистры переименования.

Особенно на старых процессорах мы ожидаем, что 'mov reg, 0' будет медленнее просто потому, что он содержит дополнительные 16 бит данных, а большинство ранних процессоров (особенно 8088) были ограничены в первую очередь своей способностью загружать поток из памяти -- на самом деле, на 8088 вы можете довольно точно оценить время выполнения с любыми ссылочными листами и просто обратить внимание на количество задействованных байтов. Это ломает для div а также idiv инструкции, но это все. OTOH, я, вероятно, должен замолчать, так как 8088 действительно мало интересует многих (по крайней мере, в течение десятилетия).

Вы можете установить регистр CX на 0 с помощью LOOP $,

Эта тема старая, но есть несколько других примеров. Простые:

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
not eax    ;++eax = 0

Конечно, в определенных случаях есть дополнительные способы установить регистр в 0: например, если у вас есть eax установить положительное целое число, вы можете установить edx до 0 с cdq/cltd (этот прием используется в известном 24-байтовом шелл-коде, который появляется в "Небезопасном программировании на примере").

Согласно DEF CON 25 - XlogicX - слишком высокий уровень языка ассемблера:

AAD с непосредственной базой 0 всегда будет обнулять AH и оставить AL без изменений. Из псевдокода Intel для него:
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

Очевидно (по крайней мере, на некоторых процессорах) префикс размера операнда 66 перед bswap eax (т.е. 66 0F C8 как попытка закодировать bswap ax) нулей AX.

В комментарии OP пишет, что сдвиги не могут использовать немедленный подсчет (введено с 80186/80286). Следовательно, целевой процессор x86 должен быть 8086/8088. (10 лет назад этот вопрос, безусловно, лучше был помечен тегом [8086], а не недавно (5 лет?) Введенным [x86-16])

Архитектура 8086 предоставляет 14 базовых регистров выполнения программы для использования в общем системном и прикладном программировании. Эти регистры можно сгруппировать следующим образом:

• The AX, BX, CX, DX, SI, DI, BP, а также SPрегистры общего назначения. Эти восемь регистров доступны для хранения операндов и указателей.
• The CS, DS, ES, а также SSсегментные регистры. Эти регистры позволяют адресовать более 64 КБ памяти.
• Реестр. Этот регистр сообщает о состоянии выполняемой программы и позволяет управлять процессором на уровне прикладной программы.
• The IPрегистр. Этот регистр указателя инструкции содержит 16-битный указатель на следующую команду, которая должна быть выполнена.

Ответ на вопрос об освобождении в реестре на x86, таким образом , может иметь дело с обнулением любого из вышеперечисленных регистров, за исключением, конечно, 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

Этот список показывает, что технически возможно, но не то, что вам следует использовать. Инструкции, отмеченные знаком (*), очень опасны или могут использоваться только с осторожностью.
Само собой разумеется, что для call а также jmp для работы вам нужен исполняемый код в целевом местоположении.

Лучший способ очистить регистр общего назначения - использовать xor reg, reg и если вы не хотите изменять какие-либо флаги, используйте mov reg, 0.

Если вы работаете с 8-битными значениями на 8086, то самый быстрый способ очистить al — это «mov al, ah», что составляет 2 цикла. «xor al, al» и «xor ax, ax» — это 3 цикла. Конечно, вы должны быть уверены, что ah уже равен 0.

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
...
Другие вопросы по тегам