ПК-01 «ЛЬВОВ»

сайт эмулятора

РЕКОМПИЛЯЦИЯ ПРОГРАММ

Под термином «рекомпиляция» в данной статье понимается автоматическое или автоматизированное преобразование исполняемого кода одного микропроцессора (МП) в исполняемый код или текст на языке ассемблер другого микропроцессора. Существует схожий по смыслу термин — «адаптация», однако адаптация программ носит преимущественно ручной характер.

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

Следует особо отметить, что автоматическая рекомпиляция невозможна в общем случае, поскольку:

— существуют переходы по адресу, не указанному в команде;

— возможна модификация кода на стадии выполнения.

Приведенные соображения делают актуальной автоматизированную рекомпиляцию, первым этапом которой является автоматическое преобразование кода («узкие» места либо не рекомпилируются, либо помечаются комментариями), а вторым — доработка программы. В дальнейшем речь пойдет о рекомпиляции программ в кодах МП Intel 8080 (советский аналог: КР580ВМ80А) в текст на языке ассемблер МП Intel 8086. Рекомпиляция в текст на ассемблере существенно упрощает второй этап автоматизированной рекомпиляции.

1. ОГРАНИЧЕНИЯ РЕКОМПИЛЯЦИИ И ТЕХНИЧЕСКИЕ РЕШЕНИЯ

Основные ограничения рекомпиляции:

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

— организация пространств памяти и ввода-вывода в общем случае различна для исходного и целевого вычислительных устройств;

Исходя из приведенных ограничений, возможны следующие технические решения:

— разделение кода и данных в результирующей программе с полным ограничением доступа к адресному пространству кода;

— регистрам исходного МП должны быть поставлены в соответствие регистры целевого МП, причем часть регистров целевого МП должна оставаться свободной для реализации вызовов системных менеджеров;

— добавление менеджеров (драйверов) памяти и ввода-вывода к результирующей программе с заменой команд, использующих доступ к памяти и портам ввода-вывода, на системные вызовы менеджеров;

— отдельное хранение данных до старта программы и перемещение блоков данных на свои места до вызова первой команды рекомпилированного кода.

2. РАЗДЕЛЕНИЕ КОДА И ДАННЫХ

Адрес ячейки памяти в вычислительных устройствах, построенных на базе МП Intel 8086, состоит из двух составляющих: сегмента и смещения. Такая организация адресного пространства позволяет разделить код и данные в результирующей программе разнесением кода и данных по разным сегментам. Сегментные регистры целевого МП должны указывать на сегменты:

CS — кода;

DS — данных;

SS — данных.

Содержимое сегментного регистра ES безразлично, поскольку исходный МП не поддерживает сегментную адресацию, а набор сегментных регистров CS, DS, SS полностью обеспечивает разделение кода и данных.

3. ОТОБРАЖЕНИЕ (МАППИНГ) ИСХОДНЫХ И ЦЕЛЕВЫХ РЕГИСТРОВ

Отображение регистров выбирается, исходя из специфики системы адресации исходного и целевого МП.

A — al

F (флаги) — регистр флагов МП Intel 8086 *

B — ch

C — cl

D — dh

E — dl

H — bh

L — bl

SP — ss:sp

PC — cs:ip

* Операции помещения в стек и извлечения из стека регистровой пары PSW исходного МП рекомпилируются с использованием регистра ah и операций lahf и sahf.

Свободные регистры: ah, si, di, bp.

3. СИСТЕМНЫЕ МЕНЕДЖЕРЫ

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

3.1. Менеджер памяти

ReadMem

Функция: чтение байта памяти.

Вход: si — адрес.

Выход: ah — прочитанный байт.

ReadMemW

Функция: чтение слова памяти.

Вход: si — адрес.

Выход: di — прочитанное слово.

WriteMem

Функция: запись байта памяти.

Вход: si — адрес; ah — записываемый байт.

Выход: нет.

WriteMemW

Функция: запись слова памяти.

Вход: si — адрес; di — записываемое слово.

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

3.2. Менеджер ввода-вывода

InPort

Функция: чтение состояния порта ввода-вывода.

Вход: ah — номер (адрес) порта.

Выход: al — прочитанный байт.

OutPort

Функция: запись состояния порта ввода-вывода.

Вход: ah — номер (адрес) порта; al — записываемый байт.

Выход: нет.

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

4. ПЕРЕМЕЩАЕМЫЕ БЛОКИ ДАННЫХ

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

5. АЛГОРИТМ ПЕРВОГО ЭТАПА РЕКОМПИЛЯЦИИ

Первый этап рекомпиляции осуществляется в следующей последовательности (линейный алгоритм):

— загрузка в память исходной программы;

— определение областей кода и данных;

— определение оптимизируемых участков кода;

— рекомпиляция кода с оптимизацией и записью в выходной файл;

— запись участков данных в выходной файл.

5.1. Загрузка в память исходной программы

Данный пункт является аппаратно– и форматно-зависимым. Целесообразно выделить непрерывный участок памяти размера, равного объему оперативной памяти исходного вычислительного устройства. В случае невозможности выделения памяти такого размера либо следует использовать смещенные адреса, либо задействовать механизм подкачки данных из файла с исходной программой.

5.2. Определение областей кода и данных

Для определения областей кода в исходной программе может использоваться рекурсивная процедура, имеющая один параметр — адрес, изначально представляющий собой стартовый адрес исходной программы. Процедура проходит все ветви программы, вызывая себя в случае нахождения команд переходов (ветвлений) исходного МП, входящих в интервал адресов исходной программы. Условные переходы должны учитываться наравне с безусловными.

Ограничение рекурсии реализуется ведением журнала (списка или массива) участков кода, а также немедленным возвратом в случае нахождения команд возврата исходного МП или пройденных участков. Команды условного возврата (если таковые представлены в системе команд исходного МП) должны пропускаться.

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

Формат записи журнала областей:

0 — данные;

1 — код.

Формат расширенной записи журнала областей (реализуется доработкой вышеприведенного алгоритма):

0 — данные;

1 — код;

2 — код с меткой (обнаружен переход по данному адресу);

3 — подпрограмма (обнаружен вызов подпрограммы по данному адресу).

5.3. Определение оптимизируемых участков кода

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

MOV A, C

RAR

MOV C, A

может быть рекомпилирована двумя командами вместо трех:

rcr cl, 1

mov al, cl

Это лишь один из примеров, когда оптимизация возможна, однако следует ввести проверку на выполнение переходов внутрь оптимизируемого участка. При использовании модернизированной процедуры обнаружения участков кода (см. 5.2) переход определяется условием «запись журнала > 1».

5.4. Рекомпиляция кода

Рекомпиляция осуществляется путем прохождения участков кода от младших адресов к старшим с пропуском участков данных. Примеры рекомпиляции некоторых команд:

— MOV A,M рекомпилируется как:

mov si, bx

call ReadMem

mov al, ah

— CC XXXX рекомпилируется как:

jnc $+5

call XXXX

— JC XXXX рекомпилируется как:

jc XXXX,

если разница между текущим адресом и адресом перехода не больше константы DeltaShortJump, значение которой принимается равным 0x40, исходя из того, что относительные переходы не могут быть выполнены на адресное расстояние, превышающее 0x80 вверх и 0x7F вниз, а целевая программа по размеру больше исходной в полтора-два раза, или:

jnc loc_YYYY

jmp XXXX

loc_YYYY: …

в противном случае.

5.5. Рекомпиляция данных

В выходной файл поочередно записываются непрерывные блоки данных (области, представленные в журнале нулями); после всех областей дописывается их количество (константа DataBlocksCount, объявляемая посредством директивы EQU) и дескрипторы областей:

— смещение блока в целевой программе;

— адрес блока в исходной программе;

— размер блока в байтах.

Данные могут располагаться как в одном файле с рекомпилированным кодом, так и в отдельном файле. В последнем случае следует подключить его директивой INCLUDE.

6. ДОРАБОТКА ЦЕЛЕВОЙ ПРОГРАММЫ

Проведенные исследования показали, что:

— подавляющее большинство программ используют стандартные подпрограммы базовой системы ввода-вывода (БСВВ), расположенной в ПЗУ исходного вычислительного устройства (если таковое имеется);

— многие программы модифицируют свой код на стадии выполнения;

— некоторые программы используют недокументированные функции БСВВ;

— некоторые программы используют переход по команде PCHL, рекомпиляция которой рассмотренными алгоритмами невозможна.

Ниже предлагаются пути решения названных проблем.

— построение библиотеки стандартных функций БСВВ путем рекомпиляции содержимого ПЗУ исходного вычислительного устройства и/или написания собственных функций;

— замена модификации кода на модификацию значения переменной или регистра с последующей проверкой или использованием данного значения, например:

MVI A, 1

STA XXXX+1



XXXX:MVI A,?



реализуется как:

data_XXXY db (?)

mov data_XXXY, 1



mov al, data_XXXY

….

— рекомпиляция и/или написание подпрограмм, соответствующих недокументированным функциям БСВВ;

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

Заключительной фазой рекомпиляции должна стать оптимизация критических по времени исполнения участков целевой программы с заменой вызовов системных менеджеров на непосредственные обращения к ячейкам памяти и устройствам ввода-вывода (или же функциям БСВВ, ОС, драйверов устройств).


»» Загрузить примеры к статье

Антон ИГНАТИЧЕВ.



главная

f.a.q.

гостевая книга

почта