Нахождение количества операндов в инструкции из кодов операций - PullRequest
4 голосов
/ 03 августа 2011

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

69 62 2f 6c 64 2d 6c

, которые должны соответствовать:

imul   $0x6c2d646c,0x2f(%edx),%esp

Теперь инструкция «imul» может иметь два или три операнда.Как мне выяснить это по имеющимся там кодам операций?

Он основан на наборе команд Intel i386.

Спасибо и С уважением,
Хришикеш Мурали

Ответы [ 5 ]

2 голосов
/ 19 августа 2013

Хотя набор инструкций x86 довольно сложный (в любом случае, это CISC), и я видел, что многие люди здесь препятствуют вашим попыткам понять это, я скажу наоборот: это все еще можно понять, и вы можете учиться на о почему так сложно и как Intel удалось расширить его несколько раз, начиная с 8086 и заканчивая современными процессорами.

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

Например, каждому операционному коду может предшествовать от 0 до 4 байтов префикса, которые являются необязательными. Обычно вам не нужно беспокоиться о них. Они используются для изменения размера операндов или в качестве управляющих кодов для «второго этажа» таблицы кодов операций с расширенными инструкциями современных процессоров (MMX, SSE и т. Д.).

Тогда есть фактический код операции, который обычно составляет один байт, но может быть до трех байтов для расширенных инструкций. Если вы используете только базовый набор инструкций, вам не нужно беспокоиться о них.

Далее, есть так называемый ModR/M байт (иногда также называемый mode-reg-reg/mem), который кодирует режим адресации и типы операндов. Он используется только кодами операций, у которых do есть такие операнды. Имеет три битовых поля:

  • Первые два бита (слева, старшие значащие) кодируют режим адресации (4 возможных комбинации битов).
  • Следующие три бита кодируют первый регистр (8 возможных битовых комбинаций).
  • Последние три бита могут кодировать другой регистр или расширять режим адресации, в зависимости от настроек первых двух битов.

После байта ModR/M может быть еще один необязательный байт (в зависимости от режима адресации), называемый SIB (S cale I ndex B ase). Он используется для более экзотических режимов адресации для кодирования коэффициента масштабирования (1x, 2x, 4x), базового адреса / регистра и индекса индекса. Он имеет структуру, аналогичную байту ModR/M, но первые два бита слева (наиболее значимые) используются для кодирования шкалы, а следующие три и последние три бита кодируют индекс и базовые регистры, как следует из названия .

Если используется какое-либо смещение, оно идет сразу после этого. Длина может быть 0, 1, 2 или 4 байта, в зависимости от режима адресации и режима выполнения (16-битный / 32-битный / 64-битный).

Последним всегда являются непосредственные данные, если таковые имеются. Также может быть длиной 0, 1, 2 или 4 байта.

Итак, теперь, когда вы знаете общий формат инструкций x86, вам просто нужно знать, каковы кодировки для всех этих байтов. И есть некоторые закономерности, противоречащие распространенным убеждениям.

Например, все кодовые регистры следуют аккуратному шаблону ACDB. То есть для 8-битных инструкций два младших бита кода регистра кодируют регистры A, C, D и B соответственно:

00 = A регистр (аккумулятор)
01 = C регистр (счетчик)
10 = D регистрация (данные)
11 = B регистр (база)

Я подозреваю, что их 8-битные процессоры использовали только эти четыре 8-битных регистра, закодированные таким образом:

       second
      +---+---+
f     | 0 | 1 |          00 = A
i +---+---+---+          01 = C
r | 0 | A : C |          10 = D
s +---+ - + - +          11 = B
t | 1 | D : B |
  +---+---+---+

Затем на 16-разрядных процессорах они удвоили этот банк регистров и добавили еще один бит в кодировку регистров, чтобы выбрать банк, таким образом:

       second                second         0 00  =  AL
      +----+----+           +----+----+     0 01  =  CL
f     | 0  | 1  |     f     | 0  | 1  |     0 10  =  DL
i +---+----+----+     i +---+----+----+     0 11  =  BL
r | 0 | AL : CL |     r | 0 | AH : CH |
s +---+ - -+ - -+     s +---+ - -+ - -+     1 00  =  AH
t | 1 | DL : BL |     t | 1 | DH : BH |     1 01  =  CH
  +---+---+-----+       +---+----+----+     1 10  =  DH
    0 = BANK L              1 = BANK H      1 11  =  BH

Но теперь вы также можете использовать обе половины этих регистров как полные 16-битные регистры. Это делается с помощью последнего бита кода операции (младший значащий бит, самый правый): если это 0, это 8-битная инструкция. Но если этот бит установлен (то есть код операции является нечетным числом), это 16-битная инструкция. В этом режиме два бита кодируют один из ACDB регистров, как и раньше. Шаблоны остаются прежними. Но теперь они кодируют полные 16-битные регистры. Но когда третий байт (самый старший) также установлен, они переключаются на целый другой банк регистров, называемый регистрами индекса / указателя, которые являются: SP (указатель стека), BP (указатель базы), SI (исходный индекс), DI (пункт назначения / индекс данных). Итак, адресация теперь следующая:

       second                second         0 00  =  AX
      +----+----+           +----+----+     0 01  =  CX
f     | 0  | 1  |     f     | 0  | 1  |     0 10  =  DX
i +---+----+----+     i +---+----+----+     0 11  =  BX
r | 0 | AX : CX |     r | 0 | SP : BP |
s +---+ - -+ - -+     s +---+ - -+ - -+     1 00  =  SP
t | 1 | DX : BX |     t | 1 | SI : DI |     1 01  =  BP
  +---+----+----+       +---+----+----+     1 10  =  SI
    0 = BANK OF           1 = BANK OF       1 11  =  DI
  GENERAL-PURPOSE        POINTER/INDEX
     REGISTERS             REGISTERS

При введении 32-разрядных процессоров они снова удвоили эти банки. Но картина остается прежней. Просто нечетные коды операций означают 32-битные регистры, а четные коды операций, как и прежде, 8-битные регистры. Я бы назвал нечетные коды операций «длинными» версиями, потому что 16/32-битная версия используется в зависимости от процессора и его текущего режима работы. Когда он работает в 16-битном режиме, нечетные («длинные») коды операций означают 16-битные регистры, но когда он работает в 32-битном режиме, нечетные («длинные») коды операций означают 32-битные регистры. Его можно перевернуть, добавив префикс всей инструкции с префиксом 66 (переопределение размера операнда). Четные коды операций («короткие») всегда 8-битные. Итак, в 32-битном процессоре регистровые коды:

0 00 = EAX      1 00 = ESP
0 01 = ECX      1 01 = EBP
0 10 = EDX      1 10 = ESI
0 11 = EBX      1 11 = EDI

Как видите, шаблон ACDB остается прежним. Также шаблон SP,BP,SI,SI остается прежним. Он просто использует более длинные версии регистров.

В кодах операций также есть некоторые шаблоны. Один из них я уже описал (четные против нечетных = 8-битные «короткие» против 16/32-битных «длинные»). Больше из них вы можете увидеть на этой карте кодов операций, которую я сделал однажды для быстрой ссылки и ручной сборки / разборки вещей: enter image description here (Это еще не полная таблица, некоторые коды операций отсутствуют. Возможно, я когда-нибудь обновлю ее.)

Как вы можете видеть, арифметические и логические инструкции в основном расположены в верхней половине таблицы, а левая и правая ее половины следуют похожему расположению. Инструкции по перемещению данных находятся в нижней половине. Все инструкции ветвления (условные переходы) находятся в строке 7*. Также есть одна полная строка B*, зарезервированная для инструкции mov, которая является сокращением для загрузки непосредственных значений (констант) в регистры. Все они являются однобайтовыми кодами операций, за которыми сразу же следует немедленная константа, поскольку они кодируют регистр назначения в коде операции (они выбираются по номеру столбца в этой таблице) в трех младших байтах (самых правых) , Они следуют той же схеме для кодирования регистров. И четвертый бит - это «короткий» / «длинный» выбор. Вы можете видеть, что ваша инструкция imul уже есть в таблице, точно в позиции 69 (да ...; J).

Для многих инструкций бит непосредственно перед битом «короткий / длинный» предназначен для кодирования порядка операндов: какой из двух регистров, закодированных в байте ModR/M, является источником, а какой - целевым (это относится к инструкциям с двумя операндами регистра).

Что касается поля режима адресации байта ModR/M, его можно интерпретировать следующим образом:

  • 11 является самым простым: он кодирует регистр-регистр передач. Один регистр кодируется тремя последующими битами (поле reg), а другой регистр - другими тремя битами (поле R/M) этого байта.
  • 01 означает, что после этого байта будет присутствовать однобайтовое смещение.
  • 10 означает то же самое, но используемое смещение составляет четыре байта (на 32-разрядных процессорах).
  • 00 - самое хитрое: это косвенная адресация или простое смещение, в зависимости от содержимого поля R/M.

Если присутствует байт SIB, он сигнализируется битовой комбинацией 100 в битах R/M.Также есть код 101 для 32-битного режима только смещения, который вообще не использует байт SIB.

Вот сводка всех этих режимов адресации:

Mod R/M
 11 rrr = register-register  (one encoded in `R/M` bits, the other one in `reg` bits).
 00 rrr = [ register ]       (except SP and BP, which are encoded in `SIB` byte)
 00 100 = SIB byte present
 00 101 = 32-bit displacement only (no `SIB` byte required)
 01 rrr = [ rrr + disp8 ]    (8-bit displacement after the `ModR/M` byte)
 01 100 = SIB + disp8
 10 rrr = [ rrr + disp32 ]   (except SP, which means that the `SIB` byte is used)
 10 100 = SIB + disp32

Итак, давайте теперь расшифруем ваш imul:

69 - его код операции.Он кодирует версию imul, которая не расширяет 8-битные операнды.Версия 6B действительно расширяет их.(Они отличаются на бит 1 в коде операции, если кто-нибудь спросит.)

62 - это байт RegR/M.В двоичном виде это 0110 0010 или 01 100 010.Первые два байта (поле Mod) означают режим косвенной адресации, и это смещение будет 8-разрядным.Следующие три бита (поле reg) равны 100 и кодируют регистр SP (в данном случае ESP, поскольку мы находимся в 32-битном режиме) в качестве регистра назначения.Последние три бита - это поле R/M, и у нас есть 010, который кодирует регистр D (в данном случае EDX) в качестве другого используемого (исходного) регистра.

Теперь мыожидать 8-битное смещение.И вот оно: 2f - смещение, положительное (+47 в десятичном виде). ​​

Последняя часть - это четыре байта непосредственной константы, что требуется для инструкции imul.В вашем случае это 6c 64 2d 6c, что в младшем порядке это $6c2d646c.

И это то, как печенье крошится; -J

2 голосов
/ 03 августа 2011

Несколько советов, сначала получите все документы с инструкциями, которые вы можете получить.для этого случая x86 попробуйте некоторые старые руководства 8088/86, а также более новые, от Intel, а также множество таблиц кодов операций в сети.Различные интерпретации и документация могут сначала иметь незначительные ошибки или различия в документации, а во-вторых, некоторые люди могут представлять информацию другим и более понятным способом.

Во-вторых, если это ваш первый дизассемблер, я рекомендую избегать x86, этоочень сложно.Поскольку ваш вопрос подразумевает, что наборы команд переменной длины слова трудны, чтобы сделать удаленно успешный дизассемблер, вам нужно следовать коду в порядке выполнения, а не в порядке памяти.Таким образом, ваш дизассемблер должен использовать какую-то схему, чтобы не только декодировать и печатать инструкции, но и декодировать инструкции перехода и помечать адреса назначения как точки входа в инструкцию.например, ARM, это фиксированная длина инструкции, вы можете написать дизассемблер ARM, который начинается в начале оперативной памяти и разбирает каждое слово сразу (при условии, конечно, что это не смесь кода руки и большого пальца).thumb (не thumb2) может быть разобран таким образом, поскольку существует только один вариант 32-битной инструкции, все остальные - 16-битные, и этот один вариант может быть обработан в простом автомате, так как эти две 16-битные инструкции отображаются в виде пар.

Вы не сможете разобрать все (с набором команд переменной длины), и из-за нюансов некоторого ручного кодирования или преднамеренной тактики для предотвращения разборки вашего предварительного кода, который обходит код в порядке выполнения, возможно,есть то, что я бы назвал столкновением, например ваши инструкции выше.Скажем, что один путь приводит вас к 0x69, являющемуся точкой входа в инструкцию, и вы определяете из этого, что это 7-байтовая инструкция, но говорите, что где-то еще есть инструкция ветвления, назначение которой вычисляется как 0x2f, являющийся кодом операции для инструкции, хотяочень умное программирование может вызвать что-то подобное, более вероятно, что дизассемблер был приведен к дизассемблированию данных.например,

clear condition flag
branch if condition flag clear
data

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

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

короткий ответ, Intel раньше публиковала и, возможно, все еще делает, технические справочные руководства для процессоров, у меня все еще есть мои руководства 8088/86, аппаратные для электрических компонентов и программные для набора инструкций и как оно работает. У меня 486 и, вероятно, 386. Снимок в ответе Игоря прямо напоминает руководство по Intel. Потому что набор инструкций со временем сильно изменился, что делает x86 в лучшем случае трудным зверем. В то же время, если сам процессор может просматривать эти байты и выполнять их, вы можете написать программу, которая может делать то же самое, но декодировать их. разница в том, что вы, вероятно, не собираетесь создавать симулятор и любые ветви, которые вычисляются кодом и не указаны явно в коде, который вы не сможете увидеть, и назначение этой ветви может не отображаться в вашем списке байтов для разобрать.

2 голосов
/ 03 августа 2011

В руководствах описывается, как различать один, два или три варианта операнда.

IMUL instruction

F6 / F7: один операнд;0F AF: два операнда;6В / 69: три операнда.

1 голос
/ 05 августа 2011

Это не инструкция машинного кода (которая будет состоять из кода операции и нуля или более операндов).

Это часть текстовой строки, которая переводится как:

$ echo -e "\x69\x62\x2f\x6c\x64\x2d\x6c"
ib/ld-l

которая, очевидно, является частью строки "/lib/ld-linux.so.2".

0 голосов
/ 03 августа 2011

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

...