Привет, %username%!
Декодирование IA-32 кода — задача архисложная. Чтобы в этом убедиться, можете обратиться к Intel Software Development Manual или к статьям, ранее написанным на хабре: Префиксы в системе команд IA-32, Правильно ли работает ваш дизассемблер?. Давайте посмотрим, как с этой задачей борется функционально точный полноплатформенный симулятор Wind River Simics, позволяющий создать высокопроизводительное виртуальное окружение, в котором любая электронная система, начиная с одной платы и заканчивая целыми многопроцессорными, многоядерными и даже многомашинными системами, может быть определена, разработана и запущена.
Большинство библиотек для декодирования IA-32 инструкций генерируют или используют таблицы соответствия между кодами операций и инструкциями. Пример использования данного подхода описан в статье Дизассемблер своими руками. Однако декодирование префиксов и аргументов обычно написано руками: libopcodes, metasm, beaengine, distorm. Данный подход обладает существенным недостатком — добавление поддержки новых наборов команд потребует большого количества ручной работы.
Существуют и другие способы создания декодеров, например с помощью языка GDSL. Данный подход является универсальным и позволяет создавать декодеры для любых архитектур.
Simics же использует совершенно другой не менее универсальный подход для работы с IA-32 инструкциями, названный раздельным декодированием. Также Simics имеет возможность использования внешних декодеров, но об этом немного позже.
Ввод и вывод процедуры декодера
В реальном процессоре за задачу декодирования отвечает отдельный блок логических элементов микросхемы. В симуляторе же ему соответствует некоторая процедура, написанная на языке программирования. Рассмотрим, что подаётся на её вход и какие результаты следует от нее ожидать.
Очевидно, что на вход декодера подаётся массив байт известной длины, полученный на фазе выборки команд (англ. fetch). Кроме того, ему должен быть известен текущий режим процессора (см. Префиксы в системе команд IA-32).
В результате работы декодер должен вернуть код ошибки и результаты анализа последовательности в виде списка полей результата. При этом возможны следующие значения для кода ошибки:
- Декодирование успешно (код возврата равен длине инструкции > 0). Массив байт был распознан как допустимая инструкция, и список полей содержит информацию о коде операции и её аргументах.
- Декодирование не успешно (код 0). Ни одна инструкция, определённая в архитектуре, не соответствует входному массиву байт. При этом содержимое полей результата не несёт смысла. Что происходит в этой ситуации дальше на этапе исполнения? Это зависит от архитектуры. Чаще всего невозможность декодировать ведёт к генерации исключения, а в некоторых случаях некорректная инструкция может быть воспринята как NOP — отсутствие операции.
- Для ISA с переменной длиной инструкций возможна третья ситуация — входных данных недостаточно для принятия однозначного решения (код < 0). Другими словами, на вход декодера передали только часть инструкции, и он, не имея информации о том, какие байты идут в памяти дальше, сообщает об этом.
На рисунке ниже приведён пример алгоритма, сочетающего в себе итерации фаз Fetch и Decode и позволяющего провести декодирование для инструкций с переменной длиной.
Раздельное декодирование
Основной идеей данного подхода является разделение фазы декодирования на две стадии:
- Декодирование префиксов. В эту стадию входит как декодирование всех префиксов, так и проверка конфликтов между ними.
- Декодирование кодов операции и операндов. Данная стадия подразумевает вызов декодера, генерированного с помощью SimGen.
Само собой разумеется, что вторая фаза зависит от результата, так как в системе команд IA-32 существует такое понятие, как обязательные префиксы, которые фактически являются частью кода операции (см. Префиксы в системе команд IA-32).
Использование внешних декодеров
Simics позволяет подключать дополнительные декодеры с помощью внешних интерфейсов, описанных в Model Builder User's Guide, который поставляется вместе с симулятором. Таким образом, можно подключить множество внешних декодеров и вызывать их по очереди до тех пор, пока какой-нибудь декодер не даст положительного результата или список декодеров не кончится. В этом случае можно будет сделать вывод, что в данной модели данный код операции считается недопустимым.
Для обеспечения гибкости внешние декодеры в Simics делятся на два типа:
- Пользовательские декодеры (англ. user decoders) — декодеры, который могут переопределить любой существующий код операции, а также, разумеется, могут добавить возможность декодирования новых инструкций.
- Расширяющие декодеры (англ. extension decoders) — декодеры, предназначенные для расширения способностей встроенного декодера, то есть для декодирования не поддерживаемых им инструкций.
Разница между предложенными типами декодеров заключается в том, что пользовательские декодеры запускаются первыми — еще до вызова встроенного, что позволяет переопределять результаты декодирования, зашитые в исходную модель. Расширяющие запускаются только в том случае, когда ни пользовательские, ни встроенный декодеры не смогли распознать инструкцию.
То есть пользователь, занимаясь разработкой какого-либо ISA, может просто подсунуть свой декодер и посмотреть что изменится, не меняя исходной модели процессора.
Кроме того, данный подход позволяется переиспользовать уже существующие декодеры вместо написания их с нуля, что значительно сокращает время разработки модели.
Автор: yulyugin