В настоящее время я пытаюсь написать программу, которая декодирует инструкции x86-64 в ассемблер, но я застрял в определении размера операнда памяти/регистра по умолчанию при работе с инструкциями, операнды которых не являются явными (т. е. они меняют размер в зависимости от текущего режим работы).
Я осознаю тот факт, что каждый сегмент памяти имеет свою собственную таблицу дескрипторов, которая описывает, среди прочего, поля EFER.LMA
, CS.L
и CS.D
. Различные их комбинации сигнализируют, в каком режиме работы находится конкретный сегмент.
Я знаю, что каждый рабочий режим требует своего собственного размера данных/адреса по умолчанию, который может быть изменен в зависимости от префиксов переопределения операнда/адреса в кодировке инструкции.
Мой вопрос: как мне узнать, какой режим работы сегмента нужно проверить, чтобы определить размер операнда для данной инструкции?
Я приведу пример:
Вариант инструкции add
с opcode = 0x03
имеет символы Gv, Ev
.
Согласно третьему тому руководства AMD x64, G/E означает регистр общего назначения, а символ v означает «слово, двойное или четверное слово (в 64-битном режиме), в зависимости от эффективного размера операнда».
Я знаю, что эффективный размер операнда — это размер операнда по умолчанию, указанный в рабочем режиме некоторого сегмента (игнорируя переопределения размера), но какой сегмент я должен здесь проверить? На странице, подробно описывающей инструкцию add
, не упоминается конкретный сегмент, поэтому я предполагаю, что это не предусмотрено для каждой инструкции (за исключением крайних случаев).
Вот что я собрал воедино, чтобы понять, как к этому подойти:
Предположим, что инструкция нацелена на сегмент по умолчанию. Возможно, это DS для операндов регистров и SS для операндов памяти. Если существует префикс переопределения сегмента, используйте вместо него этот сегмент.
Получите запись дескриптора этого сегмента.
Используйте поля записи дескриптора, чтобы определить эффективный размер операнда. Эффективный размер операнда будет использоваться по умолчанию, если не указан префикс переопределения размера.
Это будет размер, используемый для операнда.
Правильный ли такой подход? Не стесняйтесь исправлять что-либо в моем подходе/понимании. Большое спасибо.
Только сегмент CS влияет на размеры операнда и адреса. DS, SS, ES, FS и GS ни на что не влияют.
Существует 3 различных режима: 16-битный, 32-битный и 64-битный. Они выбираются по битам D и L в дескрипторе, загруженном в CS:
66
перед тем, как инструкция меняет размер операнда на 32-битный, префикс 67
изменяет размер адреса на 32-битный. 64-битные размеры недоступны.66
изменяет размер операнда на 16-битный, префикс 67
изменяет размер адреса на 16-битный. 64-битные размеры недоступны.66
изменяет размер операнда на 16-битный, префикс REX.W изменяет размер операнда на 64-битный, префикс 67
изменяет размер адреса на 32-битный. 16-битный размер адреса недоступен.EFER.LMA — это глобальное значение (не для каждого сегмента), оно влияет на то, обращает ли ЦП внимание на бит L. Если EFER.LMA=0, бит L игнорируется (предполагается равным 0) и 64-битный режим недоступен.
Бит D в сегментах данных не влияет на размер операнда по умолчанию. Это влияет только на максимальный адрес в сегментах роста (0xFFFF или 0xFFFFFFFF), а для сегмента SS влияет на то, будут ли push
и pop
использовать регистр SP или ESP. В 64-битном режиме этим регистром всегда является RSP.
Обратите внимание: если код работает под ОС, таблица дескрипторов обычно недоступна для пользовательских программ. Вам нужно использовать какой-то специфичный для ОС способ узнать, в каком режиме работает другой процесс - если такой способ вообще предусмотрен ОС. Однако большинство программ запускаются в одном режиме и никогда его не меняют. Режим запуска обычно указывается в заголовке исполняемого файла.
Спасибо за полезный ответ! Я почему-то подумал, что нужно проверять режимы дизассемблера(моей программы), а не той программы, которую он на самом деле дизассемблирует, упс. В любом случае я планирую сделать это статическим дизассемблером, поэтому думаю, что посмотрю информацию в заголовке файла. Еще раз спасибо.
Префиксы (REX.W или
66h
) могут отличать эффективный размер операнда от значения по умолчанию для режима. Вы пишете отладчик? Если вы просто пишете дизассемблер, обычным способом является определение режима на основе метаданных объекта/исполняемого файла и/или предоставление пользователю возможности указать его в командной строке (единственный вариант для плоских двоичных файлов). напримерndisasm -b 64 foo.o