Shaos wrote:Смотрю в свои старые тетрадки - весной 1994 я выдумал способ писать перемещаемые программы для 8080 под названием SYS-TRANS - суть в том, что переходы и вызовы подпрограмм по конкретным адресам (а также обращение к памяти за данными) происходят не напрямую, а через специальные подпрограммы, которые модифицируют адрес в соответствии с тем, в каком месте памяти загружена такая программа. Вот слегка переработанный вариант такого подхода, который можно вставить в новую ShaOS:
Предположим у нас есть подпрограмма SYS по адресу #CCFC, которая при вызове залезает по адресу возврата и берёт один или более байтов идущих следом за вызовом, чтобы сделать сдедующее:
CD FC CC 00 - корректировка регистровой пары BC путём прибавления к ней базового адреса загрузки SYS-программы;
CD FC CC 10 - корректировка регистровой пары DE путём прибавления к ней базового адреса загрузки SYS-программы;
CD FC CC 20 - корректировка регистровой пары HL путём прибавления к ней базового адреса загрузки SYS-программы;
CD FC CC 30 - по адресу HL находитя имя SYS-программы (с аргументами), которую надо загрузить (если ещё не загружена) и выполнить;
CD FC CC xx yy zz - во всех остальных случаях базовый адрес прибавляется к zzyy и оно вместе с xx копируется в специальную область памяти, где находятся 6 байтов:
xx yy' zz' C3 aa bb - где bbaa это адрес следующего за zz байта, т.е. таким образом можно корректировать адреса в трёхбайтовых командах 8080 - таких как условные переходы или условная передача управления.
К сожалению такой способ не будет работать с вызовом подпрограмм - по видимому надо сделать отдельный спец.вызов для CALL:
CD FC CC CD aa bb - вызов подпрограммы с возвратом на следующий адрес после bb (надо ли такой же подход распространять на условные вызовы подпрограмм?).
В этом случае всё также надо скорректировать адрес aa bb, но вместо вызова инструкции CALL надо засунуть в стек адрес байта после bb и сделать JMP на модифицированный адрес bbaa.
Если аналогично поддержать условые вызовы подпрограмм (C4,D4,E4,F4,CC,DC,EC,FC) то там кроме первого условного джампа (соответствующего условию вызова подпрограммы) в специальную область памяти надо будет вписывать и второй безусловный JMP на адрес возврата, если условие не выполнится. Можно относительно быстро проверить что требуется сделать с кодом идущим следом за CD FC CC по его младшему нибблу:
x0 - простая корректировка регистровой пары BC,DE или HL, а также загрузка SYS-программ по имени, как описано выше (никаких инструкций с аргументами с этим кодом нет, поэтому ничего больше применяться не будет);
x1 - общий случай для инструкций 01 (LXI B, ...), 11 (LXI D, ...), 21 (LXI H, ...) и 31 (LXI SP, ...);
x2 - общий случай для инструкций 22 (SHLD ...), 32 (STA ...), C2 (JNZ ...), D2 (JNC ...), E2 (JPO ...) и F2 (JP ...);
x3 - общий случай только для инструкции C3 (JMP ...);
x4 - специальный случай для инструкций C4 (CNZ ...), D4 (CNC ...), E4 (CPO ...) и F4 (CP ...), которые при транслировании превращаются в C2 (JNZ ...), D2 (JNC ...), E2 (JPO ...) и F2 (JP ...) соответственно (т.е. минус 2);
x5...x9 - инструкций с 2-байтовым аргументом среди таких кодов нет;
xA - общий случай для инструкций 2A (LHLD ...), 3A (LDA ...), CA (JZ ...), DA (JC ...), EA (JPE ...) и FA (JM ...);
xB - инструкций с 2-байтовым аргументом среди таких кодов нет;
xC - специальный случай для инструкций CC (CZ ...), DC (CC ...), EC (CPE ...) и FC (CM ...), которые при транслировании превращаются в CA (JZ ...), DA (JC ...), EA (JPE ...) и FA (JM ...) соответственно (т.е. минус 2);
xD - специальный случай только для инструкции CD (CALL ...), которая при транслировании превращается в C3 (JMP) или минус 10;
xE...xF - инструкций с 2-байтовым аргументом среди таких кодов нет.
Соответственно можно одним массивом из 16 байт определить все наши действия (там будет либо величина смещения кода инструкции, либо #FF для идентификации ошибочного выбора):
Code: Select all
SYS_ACTION DB #00,#00,#00,#00,#02,#FF,#FF,#FF,#FF,#FF,#00,#FF,#02,#0A,#FF,#FF
Если вдруг по ошибке программист применит неприемлиемый код, например FE (CPI n) типа CD FC CC FE n ... то при обработке такого вызова можно завершить исполнение с печатью ошибки в терминале. А вот если кто-то воспользуется неправильной командой из покрываемой колонки (например OUT n с кодом D3), то последствия могут быть непредсказуемыми, т.к. транслятор будет ожидать 2-байтовый аргумент и произведёт с ним соответствующие манипуляции (прибавит базовый адрес SYS-программы).
Такой подход может упростить ассемблирование т.к. теперь можно просто писать программу с ORG 0 и обычными мнемониками типа JMP, CALL и т.д.:
Code: Select all
SYS EQU #CCFC
ORG 0
....
CALL @SYS
CALL SUB1 \ вышестоящий вызов прочитает эти 3 байта и передаст управление на PUSH L1+base;JMP SUB1+base
L1:
...
SUB1:
...
CALL @SYS
CZ SUB2 \ вышестоящий вызов прочитает эти 3 байта и передаст управление на PUSH L2+base;JZ SUB1+base; JMP L2+base
L2:
RET
SUB2:
...
RET
Для спец.кодов #00, #10, #20 и #30 можно завести макросы:
Code: Select all
SYS_TRANS_BC EQU #00
SYS_TRANS_DE EQU #10
SYS_TRANS_HL EQU #20
SYS_TRANS_RUN EQU #30
...
LXI_H, VAR
CALL @SYS
DB @SYS_TRANS_HL
\ тут в HL будет реальный адрес VAR с учётом смещения
...
VAR DB 0
Вроде ничего не перепутал и все варианты учёл - есть у кого какие мысли по этому поводу?
Таким образом можно реализовать подгружаемые команды ОС в частности CD.SYS и DIR.SYS - будучи единожды запущены они останутся в памяти (в перемещённом виде) для более быстрого дальнейшего запуска.
Для вывода списка загруженных SYS-программ, а также для их выборочного удаления из памяти с компактированием, можно сделать отдельную COM или EXE программу, чтобы не нагружать этим функционалом ядро ОС.