Терминология: «код операции» - это часть инструкции, которая выбирает операцию, не включая операнды, или необязательные префиксы, которые изменяют операцию (например, размер операнда).Использование «кода операции» для ссылки на всю инструкцию некорректно, хотя довольно часто некоторые люди говорят о шелл-коде.
Это то, что вы должны знать из опыта
Имея опыт изучения машинного кода или особенно опыт оптимизации под размер кода, тогда да, вы начнете вспоминать вещи, которые вы неоднократно искали, и научитесь смотреть на строку asm и узнаете, как долго будет выполняться инструкция.без запоминания что будут байты.
Правила кодирования операнда не зависят от кода операции, поэтому вам просто нужно запомнить длину кода операции и короткий специальный случайформы, которые не используют байт ModR / M для кодирования операндов.А потом отдельно запомните правила кодирования операндов.
Лично мне лично нравится отвечать на вопросы code-golf, подобные этому , с машинным кодом x86.( См. Также Советы по игре в гольф в машинном коде x86 / x64 ).Я пишу в NASM, планируя / зная, сколько будет длиться каждая инструкция, и позволяю ассемблеру генерировать шестнадцатеричный код фактического машинного кода в виде списка.Что касается коротких инструкций, которые полезны для code-golf, я не помню, чтобы когда-то ошибался в отношении длины инструкций в последнее время, но мне повезло, что у меня есть хорошая память для деталей (таких как набор инструкций x86), которые мне интересныили использовать много.(Мне пришлось попробовать rorx
, чтобы увидеть, как долго это было.)
Я сам не печатаю байты машинного кода;чтобы сделать это вручную, я должен был посмотреть каждую инструкцию в руководстве.В x86 нет коротких кодировок для относительной к ПК адресации, поэтому поиск / создание полезных констант внутри машинного кода (которые могут удваиваться в виде данных) - это не вещь, поэтому для кода-гольфа вообще не полезно запоминать любое из числовых значений.подробности кодирования инструкций.
При оптимизации производительности меньший обычно лучше, когда все остальные равны, поэтому забота о размере кода и особенно выравнивании определенно является частью производительности.
или есть способ узнать, какая комбинация операнд / оператор занимает сколько байтов?
Это хорошо задокументировано в руководствах.За исключением нескольких однобайтовых инструкций специального случая, кодирование операнда одинаково для (почти) всего.
Машинное кодирование большинства инструкций x86 следует этому шаблону (более точная версия этой схемы)от Intel в ответе @ Mehrdad ):
[prefixes] opcode ModR/M [extra addressing-mode bytes] [immediate]
(инструкции без явных операндов не имеют байта ModR / M, только байт (ы) кода операции).
x86-коды операций - это 1 байт для наиболее распространенных инструкций, особенно инструкций, которые существовали с 8086 года. Инструкции, добавленные позже (например, такие как bsf
и movsx
в 386), часто используют 2-байтовые операционные коды с экранированием 0f
.байт.Если вы зависаете на SO, вы увидите много вопросов, касающихся конкретно 8086 (особенно emu8086
);это основная причина, по которой я знаю кое-что о том, какие инструкции не были доступны на 8086. Если вы хотите просто вспомнить, какие инструкции имеют 2-байтовые коды операций без исторических подробностей, это совершенно нормально.Или просто ищите его каждый раз в руководстве: P
например 0f b6 c0 movzx eax,al
, поэтому 0F B6 - это код операции для mov r32, r/m8
, а C0 - байт ModR / M, который кодирует eax как пункт назначения (/r
поле = 0), зарегистрируйте прямой режим для источника (старшие 2 бита = 11) и al
в качестве регистра источника (/m
поле = 0).
Я использую синтаксис Intel для всех своих примеров (mnemonic dst, src1 [,src2, ...]
), потому что это соответствует тому, что вы найдете в руководствах Intel и AMD.AFAIK, нет никаких подробных инструкций по кодированию инструкций, использующих синтаксис AT & T.Я также использую 32- или 64-битные примеры, даже когда говорю о том, что было у 8086.Конечно, у 8086 был только 16-битный реальный режим, но тот же код операции и кодировка используются в 64-битном режиме (что нас волнует в наши дни).
Набор инструкций Intelссылкаmanual (SDM vol.2) имеет карты кодов операций для 1, 2, 3-байтовых кодов операций (приложение A.3), так что вы можете увидеть некоторые шаблоны при выборе кодировки кода операции.Или для любой конкретной инструкции посмотрите кодировку, указанную вместе с полным описанием в этом руководстве.(Также посмотрите некоторые интересные онлайн-выдержки с одной страницей на инструкцию, например https://github.com/HJLebbink/asm-dude/wiki и http://felixcloutier.com/x86/. HJ Lebbink помечает страницу каждой инструкцией, когда она была введена, так что вы можете увидеть 8086 для add
, или386 для новых форм сдвигов и для movzx
).
Обратите внимание, что некоторые инструкции с одним операндом, такие как shl
или not
, используют поле /r
байт ModR / M в качестве дополнительных битов кода операции.Кроме того, большинство инструкций с немедленным действием по-прежнему разрушительны, поскольку они используют поле /r
в качестве битов кода операции.imul r32, r/m32, imm32
(386) - исключение из этого правила, имеющее немедленный и использующий полный байт ModR / M для обоих операндов.(Обратите внимание, что ModR / M может сигнализировать только операнды регистра или памяти; кодировка для add r/m32, imm8
использует код операции, чтобы указать, что есть немедленный. Но основной байт кода операции используется несколькими инструкциями, поэтому используется поле /r
как часть кода операции, и , поэтому , поэтому у нас нет add r/m32, r32, imm8
. Но для ADD / SUB мы можем использовать lea ecx, [rax + 1]
как копирование и добавление.)
Кодировка операнда:
Большинство инструкций с непосредственным операндом имеют ту же длину, что и версия регистра / источника памяти, плюс байты для непосредственного кодирования.Непосредственные значения - либо imm8, либо imm32, поэтому значения из -128..127 более компактны.(В 16-битном режиме это либо imm8, либо imm16).
Байт ModR / M - это все, что нужно для прямого регистра или простой режим адресации с одним регистром без смещения.(Кроме [esp]
).Так что add eax, ecx
имеет длину 2 байта, как и add eax, [ecx]
.Для режимов индексированной адресации (и режимов с esp
/ rsp
в качестве базового регистра) требуется байт SIB (Scale / Index / Base).
Постоянные смещения в режимах адресации требуют дополнительных 1 или 4 байта (disp8 или disp32 с расширенным знаком) поверх дополнительной SIB ModR / M +.
AVX512 EVEX с disp8 масштабирует disp8 по ширине вектора, поэтому vaddps zmm31, zmm30, [rsi + 256]
составляет всего 7 байтов(4-байтовый EVX + код операции = 0x58 + modrm + disp8), но vaddps zmm31, zmm30, [rsi + 16]
равен 11 байтам: он должен использовать disp32 для кодирования +16
, потому что он не кратен 64. Но та же инструкция с xmm
регистры могут использовать disp8
.
Подробные сведения см. в руководствах Intel.
Специальные краткие формы наиболее распространенных инструкций
Для сохранения кода-size, 8086 (и более поздние x86) предоставляют специальные кодировки без байта ModR / M для некоторых очень распространенных инструкций.Если инструкция не одна из них, она использует байт ModR / M
- add / adc / sub / cmp / test / и / или / xor / etc.AL / AX / EAX с немедленным размером того же размера, что и регистр.например,
and eax, imm32
(5 байтов) или and al,imm8
(2 байта).Но особой кодировки для and eax, imm8
нет;это все еще должно использовать 3-байтовую кодировку and r/m32, imm8
.Использование al
может быть очень полезно для размера кода при работе с 8-битными данными, особенно если вы избегаете или не беспокоитесь о частичных регистрах или ложных зависимостях , вызывающих проблемы с производительностью. сдвиг / вращение со счетом 1: 8086 не имеет вращения imm8, только на cl
или неявным 1, поэтому существуют коды операций, такие как shl r/m32,1
, где 1
неявное.
Использование кодировки imm8
влияет на производительность: потенциальные сбои в семействе P6 , поскольку он не проверяет, равен ли imm8 нулю до выполнения.Но короткая форма rol r32,1
составляет 2 мопа, против 1 для rol r32, imm8
(даже если imm8 равно 1) в семействе Сэндибридж, включая Скайлэйк.Короткая форма rcl r32,1
намного быстрее, чем с imm8.( 3 мопа против 8 на Skylake ).
И несколько, где регистр закодирован в младших 3 битах байта инструкции эффективно выделяя 8 байт пространства кодирования кода операции для того, чтобы сделать форму регистра-операнда этих команд на 1 байт короче.
mov r8, imm8
: 2 байта вместо 3 для общего кодирования mov r/m8, imm8
. mov r32, imm32
: 5 байтов вместо 6 байтов для mov r/m32, imm32
.Забавный факт: в x86-64 версия краткого кода операции REX.W = 1 является единственной инструкцией, которая может использовать 64-битную немедленную обработку.10 байт mov r64, imm64
.Версия кода операции r/m32
в REX.W = 1 по-прежнему использует 32-битный непосредственный код (с расширенным знаком, как обычно), поэтому mov rax, -1
лучше всего кодировать таким образом, принимая 7 байтов против 5-байтового mov eax,-1
.(Или, если выполняется оптимизация под размер кода, см. Также Установите все биты в регистре ЦП на 1 эффективно .) push
/ pop
регистр , 1байт против 2 байт для кодировки pop r/m32
. push
/ pop
регистры сегментов (кроме FS / GS).Хотя для них нет кодировки ar / m16. inc r32
/ dec r32
(только 16/32-битный режим: байты 0x4XПрефиксы REX в x86-64, поэтому inc eax
должен использовать 2-байтовую кодировку inc r/m32
). xchg eax, reg
: это то, куда приходит 0x90 nop
from: краткая форма xchg eax,eax
(или в 16-битном режиме, xchg ax,ax
).В x86-64 значение 90 nop
также не равно xchg eax,eax
, поскольку это приведет к расширению EAX до RAX.Вместо этого он имеет свой собственный набор команд для ввода инструкций .
xchg reg,reg
никогда не используется компиляторами и обычно обычно не быстрее чем 3 mov
инструкций так что было бы хорошо, если бы мы вернули эти 7 байт кода операции для более полезных расширений в будущем.(Или 8, если nop
был перемещен в другой код операции ...).Это было более полезным в 8086 году, когда аккумулятор был «более особенным», например, cbw
для расширения знака AL в AX был единственным (хорошим) способом, потому что movsx
не существовало.И был доступен только 1-операнд mul
/ imul
.
xchg eax, r32
по-прежнему отлично подходит для код-гольфа, например, GCD в 8 байтах x86 32-бит машинный код .См. Также мои другие ответы о коде-гольфе для различных трюков с размером кода (в основном за счет производительности; в этом смысл код-гольфа).
Я думаю, что это охватывает все однобайтовые специальныеслучаи инструкций, которые также имеют r/m32
кодировки.
Этот ответ не является исчерпывающим .Я не много говорил о последних инструкциях, и есть много особых случаев для редких инструкций.Правила того, когда требуется префикс REX или префикс размера операнда, довольно просты.Вот некоторые более общие правила:
- SSE1 / SSE3
ABCps
инструкции имеют 2-байтовые коды операций (0F xx) - SSE2 целочисленные / команды двойной точности, как правило, имеют 3 байтакоды операций (66 0F xx или аналогичные)
- Инструкции SSSE3 / SSE4.x имеют 4-байтовые коды операций (3 обязательных префикса)
В инструкциях с кодировкой VEX можно использовать 2-байтный префикс VEX , если версия SSE - SSE3 или более ранняя, и , 2-й регистр источника не является регистром "high" (xmm / ymm8-15).Версии XMM и YMM одной и той же инструкции всегда имеют одинаковый размер.(Но предпочитают xmm с неявным расширением нуля вместо явного ymm, когда вам все равно или вы хотите, чтобы верхняя половина обнулялась.)
vpxor ymm8,ymm8,ymm5 ; 2-byte VEX
vpxor ymm7,ymm7,ymm8 ; 3-byte VEX
vpxor ymm7,ymm8,ymm7 ; 2-byte VEX
Таким образом, мы можем использовать "высокие" регистрыв качестве пункта назначения или первого источника без необходимости использования 3-байтового VEX, но не в качестве 2-го источника (3-й операнд в целом).Для коммутативных операций вы можете сохранить размер, указав в качестве второго источника значение low8.
Обратите внимание, что для 4-операндных инструкций, таких как vblendvps
, 4-й операнд кодируется в imm8
. Таким образом, это все еще 3-й операнд (2-й источник), , а не последний операнд, который влияет на размер префикса VEX. Но blendvps
- это SSE4.1, поэтому ему всегда требуется 3-байтовый префикс VEX для представления кодировки 66.0F3A
поля префикса.