Хотя набор инструкций x86 довольно сложен (в любом случае это CISC) и я видел здесь много людей, которые отговаривают ваши попытки понять его, я скажу наоборот: его все же можно понять, и вы можете узнать по пути о почему он такой сложный и как Intel удалось несколько раз расширить его от 8086 до современных процессоров.
Инструкции x86 используют кодировку переменной длины, поэтому они могут состоять из нескольких байтов. Каждый байт предназначен для кодирования разных вещей, и некоторые из них являются необязательными (в коде операции кодируется независимо от того, используются эти необязательные поля или нет).
Например, каждому коду операции может предшествовать от нуля до четырех байтов префикса, которые являются необязательными. Обычно вам не нужно беспокоиться о них. Они используются для изменения размера операндов или как escape-коды на «второй этаж» таблицы кодов операций с расширенными инструкциями современных ЦП (MMX, SSE и т. д.).
Затем идет фактический код операции, который обычно составляет один байт, но может быть до трех байтов для расширенных инструкций. Если вы используете только базовый набор инструкций, вам не нужно беспокоиться и о них.
Далее идет так называемый байт ModR/M
(иногда также называемый mode-reg-reg/mem
), который кодирует режим адресации и типы операндов. Он используется только теми кодами операций, у которых есть подобные операнды. Он имеет три битовых поля:
- Первые два бита (слева, самые значащие) кодируют режим адресации (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
person
Community
schedule
19.08.2013
ib/ld-l
— предполагает, что они могут быть чем-то другим. - person Marcelo Cantos   schedule 03.08.2011