Лисья Нора

Оглавление


§ Отличия от 16-битного режима

В предыдущей главе я рассмотрел структуру и раскодирование операндов при помощи 16-битной адресации. Я говорю именно адресации, потому что касаемо размера самих операндов, они могут быть 8/16/32-битными вне зависимости от того, из какого адреса памяти они будут извлечены. В этой же главе речь пойдет о раскодировке 32-битного адреса, указателя на память, откуда и будут извлекаться операнды.
Думаю, что читая о том, как формируется эффективный адрес, было замечено о скудном наборе возможных методов адресации. В основном это сложение двух индексных регистров SI, DI, BP и BX, причем, только 16-битных. Данное ограничение наложено тем фактом, что в поле MEM байта ModRM всего лишь 8 вариантов вычисления эффективного адреса, так что был разработан некий оптимальный вариант, который бы подходил под базовые потребности.
В 386-м процессоре с приходом 32-битной архитектуры модуль декодирования эффективного адреса был пересмотрен. Точнее, не так – он был дополнен, поскольку старый, 16-битный вариант остался как есть и вполне себе успешно работает, хоть в современных программах особо уже и не используется по причине и морального устаревания, и малой степени применимости, к тому же, уже даже и 32-битный метод адресации сейчас перестал использоваться, заменившись на 64-х битный. Да, не стоит отвлекаться, сосредоточимся только на 32-битном методе.
Самое важное и главное отличие от 16-битного способа стало то, что теперь в формировании эффективного адреса участвует весь набор регистров, причем, именно 32-битный набор. Это значит что помимо того что мы сможем указать регистры от EAX до EDI в качестве косвенного адреса, также мы можем сложить два регистра друг с другом, при этом используя некоторое кратное умножение на 1,2,4 и 8:
MOV EAX, [EAX]
MOV SP, [EBX + ECX]
MOV [2*EDX + EBP], AL
MOV [ESI + 4*ESI], BYTE 80h
MOV [8*EDI + 12345678h], EDI
Выше приведены немногочисленные примеры того как можно сочетать между собой слагаемые при вычислениях эффективного адреса. Если привести общую схему, то она будет такой:
REGA [+(1|2|4|8)*REGB] [+OFFSET]
Тем самым, такой способ дает достаточно широкие возможности, гораздо более широкие, чем 16-битный режим адресации.

§ 32-битный ModRM

Да, с приходом 32 бит байт ModRM все еще остался, но принцип вычисления адреса изменился, однако изменений не было в REG-части, там всё так же: либо выбирается один из 8 доступных регистров (зависит от опкода), либо номер функции для групповых инструкции.
MEM | 00 | 01 | 10
====+=====+=======+=======
000 | EAX | EAX+b | EAX+d
001 | ECX | ECX+b | ECX+d
010 | EDX | EDX+b | EDX+d
011 | EBX | EBX+b | EBX+d
100 | SIB | SIB+b | SIB+d => нужен SIB
101 | A32 | EBP+b | EBP+d
110 | ESI | ESI+b | ESI+d
111 | EDI | EDI+b | EDI+d
Как и в случае с 16-битным ModRM, поле MOD также добавляет смещение после вычисления эффективного адреса путем либо указания одного регистра, либо сложения со вторым регистром, используя байт SIB, который, если есть указание для его использования, идет сразу же за байтом ModRM.
Здесь также остается +b – смещение адреса в пределах [-128,127], но появился +d, это смещение 32-битного адреса в диапазоне [-2147483648,2147483647]. Смещения на 16 бит уже нет, либо 8 бит, либо же 32 бита.
Оценивая таблицу, что можно сказать? Для одного лишь байта ModRM без SIB можно вычислить адрес при помощи почти любого регистра кроме регистра ESP, поскольку его место занял байт SIB (акроним от Scale-Index-Base). Также можно явно указать 32-битное значение адреса в памяти.
Но это не все возможности. Что, если нам нужен ESP или сложить два регистра? Для этой цели как раз и предназначен байт SIB.

§ Байт SIB

Структура байта:
7..6 | 5..3 | 2..0 БИТЫ
========+========+======
Масштаб | Индекс | База ЗНАЧЕНИЕ
SS | SI | REG32
В качестве базы (биты 2..0) выступают следующие регистры:
REG | 00 | 01 | 10
====+=====+=======+=======
000 | EAX | EAX+b | EAX+d
001 | ECX | ECX+b | ECX+d
010 | EDX | EDX+b | EDX+d
011 | EBX | EBX+b | EBX+d
100 | ESP | ESP+b | ESP+d
101 | d32 | EBP+b | EBP+d
110 | ESI | ESI+b | ESI+d
111 | EDI | EDI+b | EDI+d
Вот тут очень интересный момент. Дело в том что MOD, который приходит от ModRM тоже играет значение и в байте SIB. Если в качестве базы выбрать 101, то тогда способ формирования базового адреса будет иным. Для 00 в качестве базы выбирается 32-битное смещение адреса, для 01 это будет сумма регистра EBP и 8-битного знакового смещения, а для 10 сумма с 32-битным смещением адреса. То есть, тут, как и в 16-битном ModRM, нет явного указания на EBP без смещения, так что для того чтобы получить просто [EBP], то необходимо использовать [EBP+0].
С индексным регистром (SI=биты 5..3, SS=7..6) все проще. Он представляет из себя простой выбор регистров:
SI | 00 | 01 | 10 | 11 SS
====+=======+=======+=======+=======
000 | 1*EAX | 2*EAX | 4*EAX | 8*EAX
001 | 1*ECX | 2*ECX | 4*ECX | 8*ECX
010 | 1*EDX | 2*EDX | 4*EDX | 8*EDX
011 | 1*EBX | 2*EBX | 4*EBX | 8*EBX
100 | 0 | 0 | 0 | 0
101 | 1*EBP | 2*EBP | 4*EBP | 8*EBP
110 | 1*ESI | 2*ESI | 4*ESI | 8*ESI
111 | 1*EDI | 2*EDI | 4*EDI | 8*EDI
Как обычно, везде есть нюансы. Тут вместо SI=100 для всех SS значений будет везде 0. Дело в том что там должен быть регистр ESP, но из-за того что не всегда необходимо вычислять адрес в виде сложений двух регистров, для этой цели как раз и оставлено место, где один из слагаемых в вычислении эффективного адреса был бы равен 0. Если бы этого не было, то нам бы пришлось, при использовании SIB всегда складывать каких-то два регистра. Так что инженеры убрали возможность умножать ESP на масштабирующий коэффициент, оставив использование этого регистра в качестве указателя адреса только в базе байта SIB.
По факту, уже зная все что было ранее написано, можно написать полноценный раскодировщик 16/32 битной адресации.

§ Примеры кодирования

Такая сложная тема и без примеров не может обойтись. Сейчас разберем некоторые варианты составления 32-битного адреса.
MOV [EAX + 2*ECX – 1], EBX
Опкод этой инструкции равен 66 67 89 вместе с префиксом. Интересный факт: при переключении процессора в 32-битный и в защищенный режим, с назначением "дефолтного" сегмента кода как 32-битного, префиксы 66h и 67h уже писать не нужно. Это как факт, до которого мы еще далеко не скоро дойдем.
Задача в том чтобы грамотно и без ошибок составить операнды в виде как байта ModRM, так и SIB. Из самого операнда видно, что используется там 3 слагаемых – базовый регистр EAX, регистр ECX с масштабированием 2, и смещение -1, и это значит, что мы должны использовать байт SIB, а также MOD=01, поскольку смещение -1 укладывается в диапазон знакового 8-битного значения.
Раз в качестве базы выступает EAX, то биты байта SIB[2:0]=000. В качестве индекса выступает здесь ECX (номер SIB[5:3]=001), а масштабирующим фактором SS=2 будет SIB[7..6]=01, что дает двоичное значение 01 001 000 или, если переписать в 16-ричном виде, то 48h.
Байт ModRM содержит в битах [2:0] значение 100 – это указание на то что мы используем SIB, в битах [7:6] значение MOD=01 и в регистровой части [5:3] будет 011 (операнд EBX): это дает двоичное значение 01 011 100 или 5Ch.
И, конечно же, не забываем про смещение, которое будет равно байту FFh (или -1). Итоговый машинный код получится следующим:
66 67 89 5C 48 FF
^ ^ ^ ^-- СМЕЩЕНИЕ (-1)
| | +----- SIB (EAX+2*ECX)
| +-------- MODRM
+----------- ОПКОД