Сколько способов установить регистр в ноль?
Мне интересно, сколько существует способов установить регистр в ноль в сборке 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 действительно мало интересует многих (по крайней мере, в течение десятилетия).
Эта тема старая, но есть несколько других примеров. Простые:
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
...