РЕЛОЦИРУЕМОСТЬ ПРОГРАММ
© Артем Зайцев, г. Чита, 1994.
После публикации в РЕВЮ-94 № 1 на стр.51 статьи Александра Гмарь "Memory Editor" нас буквально захлестнула волна писем по поводу устранения недостатка программы - её нерелоцируемости (было письмо и от автора программы). Два наиболее удачных варианта усовершенствования программы мы приводили в третьем номере журнала за этот год (стр. 66). Считаем, что тема эта в основном, исчерпана, но, в заключение, приводим статью, в которой, на наш взгляд, содержится наиболее систематизированный подход к проблеме релоцируемости любой программы.
Я предлагаю Вашему вниманию две процедуры на ассемблере, которые помогут сделать релоцируемой любую программу. Для этого их нужно снабдить таблицами указания на те адреса, в которых содержатся команды абсолютного перехода, обращения к подпрограммам и программным переменным и разместить их непосредственно перед основной программой. Первая процедура использует таблицу смещений, то есть каждый последующий адрес вычисляется путем прибавления к предыдущему адресу соответствующего смещения из таблицы. Вторая процедура использует таблицу абсолютных адресов нужных ячеек. Это единственное отличие этих двух процедур. В остальном принцип их работы одинаков и заключается в следующем. Процедура определяет адрес, в котором она находится в данный момент, вычитает из него ADDR=A-LEN-47 для первой и ADDR=A-2*LEN-44 для второй процедуры. Здесь A - адрес, под который была ассемблирована исходная программа, LEN - количество изменяемых ячеек (компонентов таблицы). После вычисления разности процедура при помощи таблицы находит адреса в тексте программы, которые требуют корректировки, увеличивает содержащиеся в них двухбайтные значения на разность и переходит к следующей ячейке таблицы. После того, как вся программа будет скорректирована под адрес загрузки, ей передается управление.
Приводимый в листинге блок является как бы заголовком и подготавливает данные для основного блока. После его работы регистры содержат следующие значения (см. на следующей странице):
Процедура 1.
CALL #007С ; По адресу #007С в ПЗУ находится команда
DEC SP ; RET, т.е. программа возвращается, ничего
DEC SP ; не сделав, но на стеке останется при этом
POP HL ; адрес на 3 меньший, чем адрес загрузки программы, который
мы и помещаем в регистр HL. Помещаем в DE адрес. Вычисляем разность. Теперь разность в DE. B ВС количество ячеек таблицы. B HL адрес начала таблицы.
Подгоняем адрес начала таблицы под адрес загрузки. Копируем HL в IX.
LOOP PUSH DE ; Два раза сохраняем разность на стеке.
Помещаем в DE смещение из текущей ячейки таблицы.
Останавливаем в HL адрес следующей изменяемой ячейки. B DE помещаем двухбайтный адрес из программы, подлежащий изменению.
Помещаем в HL разность, а старое значение HL временно сохраняем на стеке. B DE измененное значение адреса.
Возвращаем измененное значение в программу.
Вспоминаем в DE разность. Переходим к следующей ячейке таблицы. Количество оставшихся ячеек уменьшаем на 1. Проверяем ВС на ноль.
Зацикливание.
Если таблица исчерпана, то IX указывает
на адрес сразу после таблицы. Теперь переход на IX
запустит программу.
LD |
DE,ADDR |
SBC |
HL, DE |
EX |
DE, HL |
LD |
BC,LEN |
LD |
HL,BEGIN |
ADD |
HL, DE |
PUSH |
HL |
POP |
IX |
PUSH |
DE |
PUSH |
DE |
LD |
D,0 |
LD |
Е,(IX+0) |
ADD |
HL, DE |
LD |
E,(HL) |
INC |
HL |
LD |
D,(HL) |
EX |
(SP),HL |
ADD |
HL, DE |
EX |
DE, HL |
POP |
HL |
LD |
(HL),D |
DEC |
HL |
LD |
(HL),E |
POP |
DE |
INC |
IX |
DEC |
BC |
LD |
А, В |
OR |
С |
JR |
NZ,LOOP |
PUSH |
IX |
RET |
|
DE - разность.
ВС - количество разрядов таблицы.
IX - начало таблицы смещений. В дальнейшем IX будет указывать на текущий адрес в таблице.
HL - первоначально также указывает на начало таблицы, но в дальнейшем, в отличие от IX, будет указывать на адрес текущей изменяемой ячейки. Таким образом, первое смещение осуществляется относительно начала таблицы, но оно может осуществляться и от произвольного адреса, например, от начала программы. Для этого нужно добавить строки:
LD HL,адрес
ADD HL,DE
Нужно также заметить, что все смещения считаются положительными (от 0 до 255).
Вторая процедура, как уже говорилось, отличается от первой тем, что использует таблицу не однобайтных смещений, а двухбайтных абсолютных ссылок.
Процедура 2.
#007C SP SP HL
DE,ADDR HL, DE DE, HL BC,LEN
Эта часть аналогична представленной в процедуре 1.
CALL
DEC
DEC
POP
LD
SBC
EX
LD
Следует помнить, что здесь каждая ячейка таблицы занимает два байта, поэтому LEN будет в два раза меньше, чем длина самой таблицы.
Переброска значений HL и DE в регистры HL и BC альтернативные.
HL,BEGIN HL, DE DE HL
HL BC A,2 E,(HL) HL
D,(HL) DE, HL HL, BC A
NZ,LOOP_2 DE, HL (HL),D HL
(HL),E
HL HL BC A, B C
NZ,LOOP_1 HL
LD ADD LOOP_1 PUSH PUSH EXX POP POP LD
LOOP_2 LD INC LD EX ADD DEC JR EX LD DEC LD EXX INC INC DEC LD OR JR
PUSH RET
2 проход Принимаем в DE измененный адрес и увеличиваем его на разность.
1 проход Принимаем в DE ад-pec из таблицы, перебрасываем его в HL и увеличиваем на разность.
Помещаем измененный адрес назад в программу.
Переключаемся на основной набор. Переходим к следующей ячейке таблицы.
Уменьшаем количество оставшихся ячеек и проверяем их на ноль.
Зацикливание. Запуск программы.