Длина инструкции - PullRequest
       98

Длина инструкции

5 голосов
/ 31 декабря 2010

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

Это то, что вы должны узнать из опыта или есть способ узнать, какая комбинация операнд / оператор занимает сколько байтов?

Например:

push %ebp ; takes up one byte
mov %esp, %ebp ; takes up two bytes

Итак, вопрос:

Увидев данную инструкцию, как я могу определить, сколько байтов потребует ее код операции?

Ответы [ 5 ]

7 голосов
/ 31 декабря 2010

Не существует жесткого и быстрого правила для x86 без базы данных, поскольку кодировка инструкций довольно сложна (а сам код операции может варьироваться от 1 до 3 байтов). Вы можете обратиться к документу Intel® 64 и IA-32 для разработчиков программного обеспечения 2A (глава 2: Формат инструкции), чтобы узнать, как кодируются инструкции и их операнды:

enter image description here

2 голосов
/ 19 февраля 2018

Терминология: «код операции» - это часть инструкции, которая выбирает операцию, не включая операнды, или необязательные префиксы, которые изменяют операцию (например, размер операнда).Использование «кода операции» для ссылки на всю инструкцию некорректно, хотя довольно часто некоторые люди говорят о шелл-коде.

Это то, что вы должны знать из опыта

Имея опыт изучения машинного кода или особенно опыт оптимизации под размер кода, тогда да, вы начнете вспоминать вещи, которые вы неоднократно искали, и научитесь смотреть на строку 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 nopfrom: краткая форма 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 поля префикса.

2 голосов
/ 31 декабря 2010

Длина кода операции строится с учетом (как минимум) двух критериев

  • частота оп-кода (укажите его в 1 байт, если он часто используется в программах и, если возможно)
  • информация, необходимая для работы кода операции (если вам нужен абсолютный адрес, код не может быть закодирован в уникальный байт)

Также

  • между начальными 8088 и последними процессорами Intel (3 десятилетия) было создано много новых инструкций, и некоторые из них, хотя и часто встречающиеся в программах, не могли быть закодированы в одном байте, потому что все 256 значений были зарезервированы .

Помимо ссылки, приведенной в другом ответе (в которой указан размер кода), см. Также история процессоров .

1 голос
/ 31 декабря 2010

С моих 6510 дней сборки ответ обычно относился к адресам и смещениям операндов. Операционные коды всегда были 1 байтом для 6510. Адреса всегда были двумя байтами. Если для кода операции требовался один адрес, то я знал, что общий размер составляет три байта. Если были указаны два адреса, то я знал, что общий размер был 5 байтов.

Что касается смещений, занимаемое ими пространство зависело от длины ветви. Так что подумайте:

bne FooBar

Если смещение «Foobar» указывало на адрес, который находился на расстоянии менее 128 байтов, то операндом был один байт. Если смещение указывало на адрес, превышающий это значение, тогда требовался полный адрес. Полный адрес больше не был смещением, и, конечно, адреса занимали два байта.

Так что в этом последнем случае может быть нелегко определить, требуется ли операнду opcode + два или три байта.

Так что, я думаю, иногда вы можете сказать, а иногда это не так очевидно.

1 голос
/ 31 декабря 2010

Обычно это не то, что вам нужно знать от одной инструкции к другой при программировании на ассемблере. Если это когда-либо имеет значение (например, если вы пытаетесь разместить какой-то конкретный код в ограниченном пространстве), вы можете посмотреть на выходные данные листинга ассемблера или листинг дизассемблирования.

...