Автоматизация создания релоцируемых программ
(С) Дмитрий Козлов, Николай Шустов, Н.Новгород, 1994.
В первом номере "ZX-РЕВЮ" за 1994 год ИНФОРКОМ бросил клич: "Все на создание релоцируемой версии MEMORY EDITOR-a!". Идея тоже могут воспользоваться этими процедурами, так как прошивки ПЗУ Бейсика для 48К и 128К по адресам
#1FC3-#1FC8 совпадают.
* * *
автоматического создания релоцируемых программ уже давно бродила в наших головах. И вот, наконец, ей надоело там бродить: на свет появился RPMU (Relocatable Program Make Utility). Но на самом деле это громкое название не совсем точно отражает ее возможности. Программу, которую Вы хотите сделать перемещаемой. Вам все же придется писать самим. Но Вы можете не ломать голову, руки, ноги и другие части тела в поисках ответа на вопрос: "Куда бы засунуть этот CALL ?". Ведь ужасно обидно, что есть команда относительного перехода JR, но нет команды относительного вызова подпрограммы, что-нибудь вроде CALLR—
"Да, а собственно к чему весь этот сыр-бор вокруг релоцируемых (слово-то какое выдумали!) программ?" - скажет какой-нибудь пользователь SPECCY, с трудом отрываясь от экрана, на котором его COBRA МК III, после тяжелых и продолжительных боев, добила на-конец-то несчастный PHYT0N. Но те люди, которые используют ZX-SPECTRUM не только как игровую приставку к телевизору, не раз кусали себе локти, пытаясь использовать две утилиты одновременно, которые используют для своей работы одни и те же адреса. Однако, если бы эти программы были релоцируемыми (слово-то какое замечательное!), таких проблем не возникало бы.
Как Вы знаете, есть два пути создания релоцируемых программ. Первый - это писать ее перемещаемой с самого начала, то есть отказаться от команд, использующих абсолютную адресацию: CALL addr, ЭР addr, LD reg_pair,(addr) и т.п. (где addr - адрес чего-ни-будь в Вашей программе). Если относительного смещения aR не хватает, то приходится организовывать цепочки aR-ов, где первый aR указывает на второй, тот на следующий и т.д. И это еще не все проблемы, готовые свалиться на Вашу голову.
Очевидно, что больших и серьезных программ таким путем создать нельзя. Поэтому в большинстве случаев используется другой подход, примерами реализации которого могут служить такие известные программы, как GENS и MOWS. В них к исполняемому коду добавлены таблица настройки и специальная процедура, которая по этой таблице осуществляет привязку программы к конкретным адресам. Метод, конечно, хороший, но создавать таблицу настройки вручную довольно утомительно. Может быть из-da всех этих сложностей и написано так мало релоцируемых программ.
Вот мы и подумали: "А почему бы не свалить все заботы по созданию настроечной таблицы на SINCLAIR?". Это была наша первая гениальная идея. Она нам понравилась и мы подумали: "А как же выделить те байты, которые необходимо изменить, чтобы программа смогла работать в других адресах ОЗУ?". И тут нас осенила вторая гениальная идея: "А ведь это очень просто узнать, если сравнить между собой два экземпляра одной и той же программы, ассемблированных по разным адресам (с разными значениями директивы ORG). И по этим данным таблица строится элементарно!".
Это послужило отправной точкой для написания нашей программы. То что получилось Вы можете увидеть ниже:
ПРОФЕССИОНАЛЬНЫЙ ПОДХОД
Relocatable Program Make Utility Copyright 1994 by LEGION SOFT vl.00 APR-01-94
ORG aR
30000 START
PART3
значение ORG 1-го экз. адрес 1-го экземпляра адрес 2-го экземпляра длина экземпляра
0RG1 ADR1 ADR2 LEN
DEFW DEFW DEFW DEFW
ПРОФЕССИОНАЛЬНЫЙ ПОДХОД
LD ВС,OFFSET ; подготовительные
LD B,H ; операции
EXX ; сохраняем HL* для кор-
PUSH HL ; рентного возврата в
LD ВС,(LEN) ; по адресу CD_LEN :
LD (CD_LEN),BC ; длина экземпляра
LD HL,Pl_LEN-OFFSET; по адресу SC_LEN :
ADD HL,BC ; длина экземпляра +
LD (SC_LEN),HL ; скорректированная
длина PARTI
LD HL,(ADR2)
LD DE,(ADR1)
PUSH HL ; в IX - адрес начала
POP IX ; настроечной
ADD IX,ВС ; таблицы
LOOP3 LD А,(DE)^ ; основной цикл поиска
CP (HL) ; различающихся
DR Z,NXT_B ; байтов
PUSH HL EXX
POP HL
INC HL ; в DE - корректируемое
LD D,(HL) ; значение
SBC HL,BC ; ну вот,
EX DE,HL ; скорректировали !
LD (HL),D
DEC HL ; и запихали его
LD (HL),E ; обратно
LD DE,(ADR2) ; в HL - значение
OR A ; смещения до
SBC HL,DE ; скорректированного
LD DE ,P1__LEN-OFFSET
ADD HL,DE ; значения
LD (IX+O),L
LD (IX+1),H ;
INC IX ; в таблицу его !
INC IX ;
|
ПРОФЕСС1 |
10НАЛЬНЫЙ |
ПОДХОД I |
|
EXX |
пропускаем | ||
|
LDI |
; 1 (один) байт | ||
|
fXT_B |
LDI |
; следующий байт | |
|
JP |
PE,LOOP3 |
( пока есть такой ) | |
|
PUSH |
IX | ||
|
POP |
DE | ||
|
XOR |
A |
; нулевое слово - | |
|
LD |
(DE),A |
; в таблицу | |
|
INC |
DE |
(это маркер конца) | |
|
LD |
(DE),A |
р | |
|
INC |
DE |
; DE - за таблицей | |
|
LD |
HL,PART2 |
; добавили PART2 | |
|
LD |
BC,P2_LEN |
; в конец файла | |
|
LDIR |
* | ||
|
PUSH |
DE |
р | |
|
LD |
DE,(ADR2) |
* | |
|
DEC |
DE |
; добавили PART1 | |
|
LD |
HL,P1 END-1 |
в начало файла | |
|
LD |
ВС,PI LEN | ||
|
LDDR |
DE HL A
HL,DE
в HL - суммарная длина программы
|
ЕХ |
(SP),HL |
; ее - в стек, |
|
; HL * - восстановили | ||
|
ЕХХ |
; в ВС-суммарная длина | |
|
POP |
ВС |
; программы |
|
RET |
все ! |
INC POP OR SBC
Вызов программы: LET A=USR ЗЕ4, суммарная длина программы. Адрес
#007С
_RET PARTI
LD
PUSH DI
EQU
АЛ AF
в А после возврата -начала: ADR2-P1_LEN
; адрес RET в ПЗУ
сохраняем состояние триггера прерываний (*)
CALL EQU
_RET
LABEL-PARTI
LABEL OFFSET
|
DEC |
SP |
; вместе с (**) |
|
DEC |
SP |
; определили адрес LABEL |
|
POP |
ВС |
в ВС его ! |
ПРОФЕССИОНАЛЬНЫЙ
ПОДХОД
|
EQU |
$+1 |
; SC_LEN=aflpec данных |
|
LD |
HL, 0 |
; этой ( <— ) команды |
|
ADD |
HL,BC |
; т.о. HL=адрес таблицы |
|
LD |
E,(HL) | |
|
INC |
HL | |
|
LD |
D,(HL) |
; DE = смещение из |
|
INC |
HL |
; таблицы |
|
LD |
A,D | |
|
OR |
E |
; конец таблицы ? |
|
PUSH |
HL |
; <-- (***) |
|
RET |
Z |
; если да, |
то в результате (***)
переход на PART2, иначе просто сохранили HL
SC LEN.
LOOP1
EX ADD
DE,HL HL,BC
в DE - корректируемое значение
LD
INC
LD
E,(HL) HL
D,(HL)
ну вот, скорректировали !
EX
ADD
EX
DE,HL HL,BC DE,HL
и запихали его обратно
(HL),D HL
(HL),E
LD
DEC
LD
POP OR
HL
LOOP1
закрутили цикл
PI END-PARTI
P1_END PI LEN
EQU
; (*) необходимо запретить прерывания, т.к. если процедура
; их обработки будет вызвана между возвратом из _RET и/или
; DEC-ами SP, то адрес LABEL будет безвозвратно потерян...
(Аплодисменты. Зрители рыдают.)
; Дело в том, что после возврата из подпрограммы _RET адрес
; LABEL лежит по адресу SP-2, а при вызове процедуры обработки
; прерываний адрес возврата будет записан туда же...
PART2 LD HL,-OFFSET ; DE - адрес
ADD HL,DE ; загрузки файла,
LD DE,P1_LEN ; HL - начало
EX DE,HL ; кода настроенной
ADD HL,DE ; программы
аналогичный трюк см. выше
CD LEN
EQU $+1
LD ВС,0
ПРОФЕССИОНАЛЬНЫЙ
ПОДХОД
адрес начала свободного пространства за настроенной программой
LDIR
LD
LD
B,D
C,E
POP RET EI RET
AF PO
P2 END-PART2
P2—END P2 LEN
EQU
К сему мы хотим сказать, что главным критерием при написании этой программы был минимальный размер части, присоединяемой к настраиваемому коду (т.е. размер PART1 и PART2). Именно поэтому эти фрагменты получились не совсем читабельными.
Для облегчения работы с нашей процедурой мы состряпали небольшой BASIC-интерфейс:
10 REM *** RPMU Interface **** 20 GO ТО VAL"100" 30 BORDER NOT PI: PAPER NOT PI: INK VAL"7"
40 CLEAR VAL"29999"
50 LOAD "RPMU.COM" CODE VAL"3E
4"
60 PRINT "RMPU vl.00 installed "'"Use memory from 30300." 70 STOP
99 REM ***********************
100 CLS
110 INPUT "BLOCK1 ORG value : " ;D
120 A^VAL"30002":GO SUB VAL"1E3
и
130 INPUT "BLOCK1 loading addre ss: ";B1A 140 A-A+VAL"2":LET D»B2A:GO SUB VAL"1E3"
150 INPUT "BLOCK2 loading addre ss: ";D
160 A=A+VAL"2":GO SUB VAL"1E3" 170 INPUT "Length of BLOCKS : " ;D
180 AaA+VAL"2":GO SUB VAL"1E3"
190 LET D-USR VAL"3E4"
200 PRINT "Adjusting complited.
M
300 PRINT "Use SAVE ""name"" CO DE ";B1A-VAL"36";",";D
310 STOP
999 REM *********************** 1000 RANDOMIZE D 1010 POKE A,PEEK VAL "xx" : POKE
A+SGN PI,PEEK VAL "ХХ+1" 1020 RETURN
Примечание: xx - адрес системной переменной SEED.
Мы старались не привязываться к конкретному накопителю, поэтому не вставили в BASIC-программу команд. выгрузки полученного и загрузки исходных файлов. Единственная накопителезависимая (извините за выражение) команда - это LOAD в строке 50. Поэтому если Вы используете не ленту, а какое-нибудь другое внешнее устройство (например, дисковод, принтер, плоттер, мышь или настольную лампу) , то замените команду загрузки "RMPU.COM" на нужную. Кстати, запись НАШЕЙ BASIC-программы необходимо производить командой: SAVE "RMPU" LINE 30 (или аналогичной, которую "понимает" Ваш DOS или Микродрайв).
Ну а теперь рассказ о самом интересном: как же пользоваться тем, что мы тут накропали. Итак, чтобы получить релоцируемую программу нужно:
1. Откомпилировать Ваш ассемблерный файл с двумя различными значениями ORG, причем у этих значений должны отличаться и младшие байты. После этого необходимо запомнить (а кому трудно, записать) длину полученного кода и значение ORG одного из блоков.
Потом эти значения потребуются для работы RMPU.
2. Отгрузить полученные модули на внешний носитель.
3. Загрузить RPMU.
4. После того, как появится сообщение: "RMPU vl.00- installed..." загрузить Ваши модули в свободную память.
5. Запустить RPMU командой RUN и чистосердечно ответить на вопросы программы, введя ранее запомненные значения и адреса загрузки .
6. Сообщение: "Adjusting computed. " известит Вас о том, что настройка закончена и готовую программу можно сохранить на внешнем носителе. Для выгрузки на ленту надо использовать указанную команду (если у Вас другой носитель, то скорректируйте ее соответствующим образом).
Итак, Вы получили релоцируемую версию некоторой программы. Возникает резонный вопрос: "А как же ей пользоваться?" Все очень просто. Загружаете ее в память:
LOAD "name" CODE addr
И запускаете: LET A~USR addr
Программа вернется в BASIC и в результате в переменной А будет адрес первой свободной ячейки памяти, а по адресу addr разместится Ваша настроенная программа. Теперь Вы можете запускать ее обынной командой:
RANDOMIZE USR addr
Обратите внимание, что полученная программа содержит только то, что Вы написали. Процедура и таблица настройки удалены так как они больше не нужны.
Теперь нам хотелось бы остановиться на модификации программ, для которых у Вас нет исходного текста (пример: Memory Editor). В этом случае придется воспользоваться директивой "Т" монитора MONS для его получения. К сожалению этот способ подходит только для опытных пользователей, но другого пути на наш взгляд нет.
Наконец, несколько слов еще об одном варианте использования предлагаемой программы. На компьютере IBM PC есть очень интересная возможность организации своих процедур - OBJ файлы. Нечто подобное можно попытаться сделать и на SPECCY. Тут поможет предложенная Вами идея КЕРНАЛЯ. Делать это надо примерно так:
- Вы создаете свои кодовые процедуры так, чтобы они "общались" между собой только через КЕРНАЛЬ.
- Каждая процедура делается перемещаемой при помощи RPMU.
- Программа, которая должна их использовать, сама загружает эти процедуры в память (причем в любой комбинации) и запускает настройщик. Затем адреса размещения процедур заносятся в определенные ячейки КЕРНАЛЯ.
Таким образом Вы полуавтоматически можете создать кодовую часть будущей программы даже из BASICa. Это будет выглядеть примерно так:
10 DATA "NAME1", "NAME2", ...
20 CLEAR 29999 : LET ADR=3E4
30 FOR 1=1 TO NOF : REM NOF -оличество подключаемых файлов. десь должны находиться строки, оторые отвечают за запись адреса ачала очередной процедуры (т.е. DR ) В КЕРНАЛЬ.
80 READ N$
90 LOAD N$ CODE ADR : LET ADR» USR ADR 100 NEXT I
Мы понимаем, что предложенная идея не отличается особой элегантностью, но ведь это только идея. И может быть кто-то, опираясь на нее, создаст что-то действительно стоящее.
* * *