Хотя набор инструкций 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-битных «длинные»). Больше из них вы можете увидеть на этой карте кодов операций, которую я сделал однажды для быстрой ссылки и ручной сборки / разборки вещей:
(Это еще не полная таблица, некоторые коды операций отсутствуют. Возможно, я когда-нибудь обновлю ее.)
Как вы можете видеть, арифметические и логические инструкции в основном расположены в верхней половине таблицы, а левая и правая ее половины следуют похожему расположению. Инструкции по перемещению данных находятся в нижней половине. Все инструкции ветвления (условные переходы) находятся в строке 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