Ожидайте..

 
 
Новости
Документы
Прочее
Авторизоваться
 
От переводчика.
Эта статья является переводом страницы с сайта http://www.spiralspace.com/Depot/Projects/Disassembler/disassembler_ia32.aspx. Здесь дается вводный материал в архитектуру процессоров Intel с точки зрения разбора команд процессора, т.е. дизассемблирование инструкций процессора.

Проблема дизайна, которую мы должны согласовать.

Прежде, чем мы пойдем дальше, у нас есть "проблема дизайна". Проблема - это:

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

  1. Анализатор обрабатывает поток байт, и поскольку он узнает единственную инструкцию, он добавляет вход в массив указателей на байт. В конце будет динамическое множество с размером элементов, являющийся равным числу инструкций во входном потоке байт.
  2. Анализатор обрабатывает поток байт, и поскольку он узнает единственную инструкцию, он копирует все байты инструкции в буфер. Этот буфер мог быть элементом динамически растущего массива.
  3. Анализатор обрабатывает поток байт, и поскольку он узнает единственную инструкцию, он возвращает указатель на начало текущей команды во входном потоке байт. Он также возвращает число байт, которые занимает текущая команда. Когда клиент говорит "давай, вперед", Анализатора начинает обрабатывать немедленно после последнего байта последней инструкции.

Чтобы сделать историю короткой, я использовал "дизайн #3". Причина - эффективность в пространстве, а так же время. Не будет никакого копирования (фактически, я действительно выполняю физическое копирование для того, чтобы скэшировать цель) данных в другое местоположение, которое, вероятно, должно быть динамически выделено. Будет не динамически растущий массив указателей. Указатель принимает 4 байта. Предположите, что есть 1000 инструкций, потребуется 4k памяти, которая эквивалентна из всему размеру страницы.

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

В любом случае, это - то как мой Анализатор собирается сделать. Теперь, приступим к теме, "как разобрать поток байт машинного кода Intel" так, чтобы он мог найти границу текущей команды.

Понимание архитектуры Intel.

Подзаголовок этой главы "Понимание архитектуры Intel (IA32) для парсинга". Следующая глава поведает о дизайне Анализатора. Почему мы нуждаемся в отдельной главе для того, чтобы понять архитектуру Intel? Поскольку наша задача "парсинга" требует, чтобы мы поняли это. Конечно, мы только должны понять очень маленький сегмент архитектуры Intel, чтобы сделать нашу работу.

Давайте нырнем в это. Готовы?

Это - формат инструкций Intel x86. Да, вероятно Вы не понимаете вещи как "ModR/M" и "SIB". У Вас могла бы быть некоторая идея относительно "Opcode" и "Displacement". Они подразумевают много вещей, но мы должны помнить это, в то время как мы изучаем формат инструкции Intel x86:

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

Другими словами, Анализатор хочет знать, где текущая команда заканчивается. Как он может убедиться, что инструкция заканчивается в особом местоположении? Это - то, что мы собираемся узнать в этой главе.

Префикс

Начнем с тем, что находится перед Opcode.

Тех подсвеченых с желтым фоном называют "prefix" в целом. Поскольку имя подразумевает, они могли бы появиться перед Opcode. Их работа состоят в том, чтобы "изменить" признак по умолчанию режима процессора. Например, когда процессор работает в 32-битовом режиме, по умолчанию размер адресов и данных (операндов), является 32-битовым. Скажите, если бы Вы хотели переместить только 16-битовые данные в регистр, только для той инструкции, то признак размера операнда по умолчанию должен быть изменен.

Размер адреса и размер операнда подобны. Их присутствие переключает признак между 16-битовым и 32-битовым. Давайте не заходить слишком далеко на этом. Наша цель состоит в том, чтобы понять, "сколько байт мы должны читать для инструкции". Если Вам интересно проверьте руководство процессора Intel. (Я не хочу убегать, мы только не должны знать об этом, пока мы не пишем Декодер).

Самые важные вещи, которые мы должны знать о префиксах:

  • Каждый перефикс - точно один байт
  • Каждый префикс является дополнительным - он может присутствовать, а может отсутствовать.
  • Порядок появления не установлен (это, я не полностью уверен, но руководство процессора Intel говорит так).

Как они выглядят? А вот так.

Префикс инструкции F0 F2 F3 F3
Префикс изменения размера/адреса 67
Префикс изменения размера операнда 66
Префикс изменения сегмента 2E 36 3E 26 64 65

Они - все в hex, а у Префикса инструкции и Префикса изменения сегмента есть больше чем один. Каждый байт означает что-то, но мы не заботимся, что они имеют в виду сейчас. (снова, проверьте и узнаете то, что они имеют в виду.)

Так, что это все значит для нашего анализатора? Это означает что:

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

Это все.

Opcode

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

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

Так или иначе наш Анализатор должен быть в состоянии прочитать или один байт или два байта в зависимости от того, является ли этот Opcode "Однобайтовым opcode" или "Двухбайтовым opcode". Как Вы можете сказать это? Легко. Если Вы видите 0F (hex), это - символ указатель для еще одного opcode байта.

Помимо непосредственно размера opcode, мы должны узнать, какой "операнд(ы)" особый opcode собирается принять. Некоторые операнды не имеют никаких операндов, другие имеют только байт ModR/M, некоторые имеют Immediate и т.д.

Красная линия со стрелкой в вышеупомянутом фигуре означает, что присутствие указанных областей диктует область, где линии красной стрелки начинается. Поэтому, байт ModR/M будет следовать или нет в зависимости от opcode. То же самое относится к Displacement и Immediate.

Так, здесь становится немного сложно. Что мы делаем?

Это - часть, которая до сих пор брала большую часть моего времени в этом проекте (в стороне Декодер, который будет намного больше). Было бы хорошо, чтобы у руководства процессора Intel были таблицы, которые говорят, "это и это и та инструкция имеют ModR/M., который это и эти opcodes имеют immediate" и т.д. К несчастью, они не не имеют этого.

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

  • Таблица "Однобайтовых" opcodes, которые имеют область ModR/M.
  • Таблица "Двубайтовых" opcodes, которые имеют область ModR/M.
  • Таблица "Однобайтовых" opcodes, которые имеют 1 байт Displacement
  • Таблица "Однобайтовых" opcodes, которые имеют 2/4 байт Displacement
  • Таблица "Двубайтовых" opcodes, которые имеют 1 байт Displacement
  • Таблица "Двубайтовых" opcodes, которые имеют 2/4 байт Displacement
  • Таблица "Однобайтовых" opcodes, которые имеют 1 байт Immediate
  • Таблица "Однобайтовых" opcodes, которые имеют 2/4 байт Immediate
  • Таблица "Двубайтовых" opcodes, которые имеют 1 байт Immediate
  • Таблица "Двубайтовых" opcodes, которые имеют 2/4 байт Immediate

Если Вы запутались, это естественно.

"2/4-байтовая" часть означает, что размер составляет или 2 байта (16-битовые) или 4 байта (32-битовые). Как мы собираемся узнать, как измеряется операнд? Это - то, где "признак по умолчанию" и возможное присутствие префикса Размера операнда играют роль. Анализатор должен "помнить" о любой приставке, это мы, уже разобрали.

Строительство этих таблиц требовало времени, и так как я сделал это вручную, могут быть ошибки. Вдобавок ко всему, из-за моей нехватки полного понимания ассемблера (Я - C/C++ программист!), я, возможно, сделал некоторые ошибки. До сих пор мой результат испытаний говорит, что я разобрался в нем, но я не буду удивлен, если ошибка или две появляется из-за плохих таблиц.

Я не собираюсь показывать содержание этих таблиц здесь. Это не будет захватывающе в любом случае. Вы можете видеть таблицы в исходном файле, "IA32OpcodePart.cpp". Это находится в ".cpp" файле, потому что эти таблицы - статический член класса.

С точки зрения Анализатора он должен проверить поток opcode заного ради этих требований областей операнда, и если какой-либо совпадение найдено, то он должен не забыть разобрать области операнда. Размер области, возможно, придется определить, узнавая о размере операнда (и возможный отвергнуть сделанный приставкой). Например, если Immediate требуется особым opcode, после того, как мы читаем (или не читают), ModR/M, SIB, и области displacement, мы должны не забыть разобрать x число байт для Immediate операнда.

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

ModR/M

Эта область - выглядит страшно. Что &*^# - этот "ModR/M"? Короче говоря, эта область возможно кодирует один или два операнда, один из которых может быть данными памяти. Это также может закодировать своего рода "sub-opcode", где бесспорный opcode определяет "операнд группы", и фактический операнд определен, смотря какова часть этого байта ModR/M. Еще хуже, этот ModR/M может потребовать SIB байта, который следует за ModR/M. Снова деталь "значения" здесь не настолько важна.

Как Вы можете видеть с несколькими красными стрелками, ModR/M может сказать, что нуждается в SIB и/или области Displacement для полного описания операнда(ов).

Проверка этих условий соглашений не была трудной. Для данного байта ModR/M...

7 6 5 4 3 2 1 0

  • Если данные в позиции битов 3,4,5 будут равны "100" (например, 00100110), то SIB байт будет следует.
  • Если данные в позиции битов 6,7 будут равны "01", то будет 1-байтовое displacement.
  • Если данные в позиции битов 6,7 будут равны "10", и Размер адреса является 32-битовым, то будет 4-байтовое смещение.
  • Если данные в позиции битов 6,7 будут равны "10", и Размер адреса является 16-битовым, то будет 2-байтовое смещение.

С точки зрения анализатора он должен узнать бит в вышеупомянутых местоположениях, и запомнить область по требованию, если они происходят.

Руководство процессора Intel полностью описывает значения каждого возможного образца байта ModR/M, таким образом, это не должно быть запутывающим, осуществляя декодер.

SIB

Этот байт очень пассивен, и Вы ничего не должны сделать, смотря насколько парсинг затронут. Только пройдите правильно по SIB байту и идите в следующие области, (или до конца текущей команды, если никакие другие области не следуют).

Значения каждой возможности SIB байта полностью описаны руководством процессора Intel.

Displacement

Присутствие этой области уже определено или Opcode или ModR/M, включая размер displacement. Анализатор должен продвинуть свое местоположение, чтобы пройти по области displacement, если существует.

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

Immediate

Наконец, мы видим конец туннеля. Точно так же как Displacement, если требуется, мы знали бы к настоящему времени. Только разберите по этому для числа байтов для этой области.

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

Что теперь?

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

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

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

Например, Вы видите, что все области префикса слиты с единственной "частью приставки". Это имеет смысл из-за прочных отношений среди этих четырех приставок. Другое слияние компаний произошло между ModR/M и SIB. Так как SIB - пассивная область (оно не определяет другие области), это стало частью логики, которая заботится о ModR/M. Понятие "размера" менее строго в этом представлении.