Автосборка программы
Множество предрассудков и неудачных ре-
шений существует в такой, казалось бы, ес-
тественной и давно освоенной проблеме, как
сборка программ. Сам по себе этап сборки
случается даже не для всякой программы
(например, для таких,с позволения сказать,
программных продуктов,на каких учатся про-
граммировать, он и вовсе не нужен), но при
необходимости выпустить череду версий он
становится одним из вопросов наибольшей
траты времени.
Однократная сборка традиционно осущест-
влялась через выгрузку памяти после компи-
ляции (кусками или сплошняком),затем полу-
ченные блоки паковались вручную в произво-
льных компрессорах, после чего к упакован-
ной программе пришивался какой-либо извес-
тный бейсик-загрузчик. Способ настолько
традиционен, что появились соответствующие
функции (выгрузка блоков и склейка) во
многих ассемблерах - для первого, - и в
коммандерах - для второго. Однако,даже та-
кая, вроде бы, автоматизация не спасает от
значительных затрат времени. На практике
способ сравним и с ручной выгрузкой STS'ом
и ручной же коррекцией каталога в нём -
по крайней мере, различные программы реже
требуется загружать.
──────────────────────────────────────────
Подробное разъяснение (про традиционную
сборку).
Собираемый проект изначально предпола-
гается находящимся в виде, пригодном для
отладочного запуска.Делаем в нём следующие
добавления:адреса начала и длины всех бло-
ков выводим директивой ассемблера DISPLAY;
если возможно,то лучше собрать все блоки в
один блок,а программа во время запуска до-
лжна перекидывать их LDIR'ом (LDDR'ом) на
нужные места;адрес запуска тоже должен вы-
водиться директивой DISPLAY. То есть так,
чтобы после ASSEMBLE все нужные адреса
светились на экране, откуда их можно спи-
сать на бумажку.
После ASSEMBLE все указанные блоки нуж-
но сохранить через STS (кнопкой S) или че-
рез ДОС ( SAVE "..." CODE ...,... ). Далее
компьютер следует сбросить,чтобы загрузить
компрессор.Этим компрессором нужно запако-
вать блоки (все - с распаковщиком, если мы
говорим именно о традиционном способе).
Остаётся сформировать бейсик-загрузчик,
которого есть два основных варианта - мо-
ноблочный и пофайловый.Пофайловый делается
полностью в бейсике: CLEAR:LOAD,LOAD,LOAD,
потом USR+USR+USR... кое-где,возможно, OUT
32765,... Только следует учесть, что между
OUT 32765 и относящимся к этой странице
LOAD'ом (или USR'ом) не должно присутство-
вать никаких других операторов - иначе 128
бейсик может переключить страничку обрат-
но.
Моноблочный загрузчик содержит только
(опционально) CLEAR, USR и (снова опциона-
льно) REM, с большим запасом пробелов. В
этих пробелах STS'ом вписывается код заг-
рузчика типа LD DE,(23796)...CALL #3D13.
Набирать такой загрузчик довольно утомите-
льно. Неприятно также то, что после сохра-
нения этого загрузчика на диск приходится
копировать после него все относящиеся к
нему упакованные файлы. Потом программой
Global Commander (или какой-нибудь другой,
в которой вы, возможно, и копировали их)
файлы склеиваются в моноблок. К большой
неудаче, Perfect Commander 1.52 из состава
Gluk Reset Service v5.4R склеивает файлы
неправильно. В Gluk v5.5RR это исправлено.
Положительная сторона моноблочного заг-
рузчика и в том, что он получается короче.
И ограничений на включение страниц уже
нет, особенно если перестраховаться коман-
дой LD (IY+1),#CC (во всяком случае, уста-
новлено, что эта команда не даёт IM 1 пор-
тить #5bxx). Кстати,именно в нём,таком за-
грузчике, можно предусмотреть общий распа-
ковщик для всех кодовых блоков. Удобен,на-
пример, классический DEHRUST для Hrust 1.3
- этот депакер весит ровно один сектор.
Моноблочную программу с общим депакером
можно сократить и ещё сильнее, если все
сжатые блоки склеить байт к байту. Это де-
лается уже в ассемблере:
LD HL,pack1
LD DE,depackto1
CALL DEHR
LD HL,pack2
LD DE,depackto2
...
DEHR INCBIN "DEHRUST
pack1 INCBIN ...
pack2 INCBIN ...
...
Не забывайте! Память при запуске прог-
раммы не обязательно чистая; включена не
обязательно страница #10; и т.д.!
Конец подробного разъяснения.
──────────────────────────────────────────
Что такое эта упомянутая сборка STS'ом?
Изначально это была выгрузка кодовых бло-
ков без упаковки (причём адреса подгляды-
вались в метках ассемблера) после предва-
рительно скопированного бейсик-блока - на-
пример, поверх старой сборки той же самой
программы.Бейсик-блок нужно было бы загру-
зить как сектор, изменить в нём то,что из-
менилось (например, адрес пуска программы)
и сохранить поверх,снова как сектор. Такой
релиз неудобен к распространению, но соби-
рается очень оперативно - например,для те-
стирования.
──────────────────────────────────────────
Подробное разъяснение (про сборку STS'-
ом).
После ассемблирования и записи адресов
на бумажку выходим в STS; загружаем через
меню бейсик-загрузчик старого релиза прое-
кта (внимание! следите, чтобы старый и но-
вый релизы совпадали по суммарному размеру
блоков!). Если стартовый адрес программы
или что-либо ещё изменилось,выполняем сле-
дующую последовательность:загрузить сектор
бейсик-загрузчика снова; через ssL с подс-
тановкой номера сектора на 1 (размер заг-
рузчика) меньше текущего;исправить необхо-
димые байты в нём; сохранить через ssS по-
верх, опять уменьшив номер сектора на 1,
как было (т.к. он снова увеличился после
операции загрузки).
В любом случае,"текущий сектор" в STS'е
сейчас будет указывать на сектор после
бейсика.
Далее через ssS из памяти поочерёдно
выгружаются все кодовые блоки. Номера сек-
тора-дорожки корректировать уже не надо.
Релиз готов.
Вариация:
Клеим в памяти все блоки, и бейсик-
сектор в том числе, после чего сохраняем
программу как CODE. Далее грузим каталог
через ssL, исправляем расширение на B, а
Start и Length на длину бейсик-загрузчика,
после чего выгружаем исправленный каталог
( STS в роли диск-доктора).
Вариация с упаковкой:
Формируем в ALASM загрузчик с INCBIN'ом
упакованных файлов,навроде того,что указан
в первом "подробном разъяснении". В тот же
загрузчик вписываем весь бейсик-блок, пе-
реписав его байт-в-байт, включая даже #80,
#AA,X,X в конце; компилировать этот бейсик
будем не по его обычному адресу #5D3B, а
за сектор перед остальными блоками.Снабдив
загрузчик DISPLAY'ами и сохранив, получаем
удобный, но не идеальный,способ многократ-
ного склеивания через STS (в той самой ва-
риации),в упакованном виде.
Конец подробного разъяснения.
──────────────────────────────────────────
Такие понятные способы часто использо-
вал и я.Однако это плавно мне надоело. До-
лгое время я потратил на упрощение задачи
по частям.
1. Установить при запуске программы из
ассемблера (без сборки) те же условия, что
и у запущенной собранной программы.
2. Сохранять блоки без ручного вмешате-
льства.
3. Запускать упаковщик без ручного вме-
шательства.
4. Настраивать бейсик-блок автоматичес-
ки.
5. Склеивать блоки программы без выхода
в коммандер.
6. Автоматически запускать собранную
программу.
Автоматизацией я начал заниматься толь-
ко в 1999 году.
Первой решилась задача 4 о бейсик-бло-
ке.Поскольку ALASM позволял его компилиро-
вать вместе с основной программой, все ну-
жные числа могли быть настроены при компи-
ляции через подстановку соответствующих
меток. Полученный загрузчик выгружался как
сектор. Полный набор переменных ОС,которые
нужно было настроить для сохранения бейси-
ка обычными средствами, я разыскал позже.
Это оказались переменные 23649, 23651,
23653, 23641 и 23627.
Да, бейсик как сектор можно выгружать
без этих премудростей. Однако премудрости
нужны при сохранении через окно ДОС или
соответствующую подпрограмму этой ДОС (фу-
нкция #C точки #3D13 ). И ещё,к сожалению,
эти действия необходимы для восстановления
рабочего состояния системы на момент запу-
ска программы, по задаче номер один. И это
не всё. Ещё надо сделать CLEAR. Для этого
служит подпрограмма 7863 с параметром в
BC. Между прочим, она двигает стек, что
важно учитывать и не надо забывать. А то
можно отлаживать и искать ошибку всю жизнь
:)
Все вопросы запуска бейсик-файлов реши-
ла маааленькая подпрограмма, отысканная
BASIL'ом и опубликованная в ZX-Guide #,ка-
жется, 3 или в том AlCoNews, где писалось
о командной строке. Такой автозапуск можно
вставить и для загрузки Hrust (Rip), и для
запуска собранной своей программы. Однако
второе тогда без первого :)
Автосборщик сам по себе организуется в
исходнике так.Адрес для RUN (последний ORG
программы) вместо адреса запуска самой
программы устанавливается, например, на
#5B00, где помещается сборщик. Сборщик ак-
тивизируется при запуске программы, допус-
тим, с Caps Shift'ом. (Можно использовать
условную компиляцию,но это,я думаю,не луч-
ше.) Без Caps Shift программа просто отла-
дочно запускается. Впрочем, если удобно, в
ней можно сделать специально для отладоч-
ного запуска какие-либо изменения, навроде
выхода в ALASM вместо выхода в ДОС,по вку-
су. В ветке же автосборки программу,конеч-
но,менять не следует - сохраняться она до-
лжна именно в задуманном виде.
Беда же в том, что в Hrust'е (который
Rip) кнопки нажимать, тем не менее, надо.
Первым серьёзным упаковщиком, официально
опубликованным в исходниках, был,насколько
мы помним, Hrust 2.x. Именно этот алгоритм
я и начинал применять вместо громоздких
компрессоров с интерфейсом. Преимущества
прирученного компрессора в автосборщике
очевидны.
Прирученный компрессор можно хранить и
в виде кодового блока,не компилируя каждый
раз. Ведь проблема была только в наличии
интерфейса, который совершенно не нужен,
чтобы упаковать блок с адреса N длиной L в
адрес M, где L, M и N известны и заключены
в метки.
Автоматически сохранять блоки (упако-
ванные) и склеивать проект при сравнении
со всем пройденным оказывается уже совсем
просто.Благо даже функции ДОС предусмотре-
ны для всего требуемого, кроме склейки мо-
ноблоком.Моноблок,увы,приходится создавать
в каталоге искусственно.Или изменением чи-
сла файлов, или изменением длины бейсика и
свободного места диска/1-го свободного се-
ктора - при сохранении блоков не файлами.
На этом решении я, так сказать, и оста-
новился.
Вот, например, как выглядит автосборщик
ACEdit:
ORG #7000
mk IFN usePG4 ;если часть редактора в
LD HL,lDIRME+LDIRLN-1 ;странице,
LD DE,DDD-1 ;то переносим эту
LD B,'LDIRLN+1 ;часть ДО редактора
LDDR ;а при запуске её перебросят
ENDIF
LD B,#6F ;BC=#6Fxx
CALL 7863 ;CLEAR бейсика
IFN ?make ;если не определена метка make
CALL 8026 ;опрос CS (CS=сборка)
JR NC,maker ;CS нажат
JP SUDA ;просто отладочный запуск
ENDIF
maker ;собственно, здесь и собирают
;при сборке используется более мощный
;упаковщик, чем уже есть в редакторе:
LD HL,HR24 ;здесь был упаковщик
LD DE,#4000 ;здесь он должен быть
LD B,6 ;его размер
LDIR ;перекидываем его на место
LD HL,END-1
LD DE,-1
LD BC,END-DDD+LDIRLN
PUSH BC
LDDR ;перекидываем код в конец ОЗУ
POP HL
CALL #4000 ;вызываем упаковщик
LD HL,(KUDA+6) ;длина упак.файла
PUSH HL
DEC HL ;ищем длину в секторах
INC H ;(см. этюды в ZG#3)
LD A,H
IFN prn ;если принтер поддержан,
INC A ;лоадер грузит на 1 s больше
ENDIF
LD (secsz),A ;подставляем в лоадер
ADD A,#C1
LD (drvphysH),A ;а здесь лоадер
;найдёт драйвер принтера
LD C,#12 ;del - удаляем
CALL #3D13 ;старый одноименный ACE
LD HL,1
LD (#5CD1),HL ;автостарт с LINE 1
LD C,#C ;savB
CALL #3D13 ;сохраняем бейсик
LD HL,#7100
LD D,L,E,L
LD BC,#905
CALL #3D13 ;грузим системный трек
POP BC
LD HL,KUDA+8 ;адрес упак.блока
DEC BC ;пересчитываем длину в size
INC B ;(sectors)
LD DE,(#79E1) ;first free tr/sec
LD C,6
PUSH BC
CALL #3D13 ;сохраняем упак.блок
POP BC
LD HL,(23796)
LD (#79E1),HL ;first free tr/sec
LD HL,(#79E5)
PUSH BC
XOR A
LD C,B,B,A
SBC HL,BC
LD (#79E5),HL ;корректируем free
LD C,#A ;find
CALL #3D13 ;ищем № файла на диске
LD L,C ;вот номер файла нашего ACE
INC L
LD H,7
DB "))))" ;умножили на 16
INC H ;прибавили 1: #7100+(C+1)*16
DEC HL,HL,HL ;HL=#7100+C*16+13
POP BC
INC B,B ;+2 сектора под бейсик
LD (HL),B ;sector size бейсика
;запись исправленного каталога
LD HL,#7100
LD D,L,E,L
LD BC,#906
CALL #3D13
;вроде как инициализация интерпретатора
;48 бейсика, если был включен 128 бейсик
LD HL,4867
PUSH HL
LD (23613),SP ;ERR_SP
LD HL,(23631)
LD BC,15
ADD HL,BC
LD DE,5566
EX DE,HL
LD C,4
LDIR
;эти п/п вроде как грузят бейсик
LD HL,RNBAS2
PUSH HL
LD HL,#2970
PUSH HL
LD L,#20
PUSH HL
LD L,#4A
PUSH HL
JP 15663
RNBAS2
LD HL,#6E01 ;там было ",acn## p"
LD DE,(23641) ;E_LINE
LD B,L
LDIR ;формируем командную строку
JP EEE ;запуск ACE с ком. строкой
ORG #6200 ;преинициализация сборки
LD HL,WASBAS
LD DE,#5D3B
LD BC,512
LDIR ;перебрасываем basic на место
LD (IY+83),B ;устанавливаем чёрный
LD (IY+14),7 ;экран (для красоты)
CALL ROTFONT ;конвертируем шрифты
JP mk ;переход на сам автосборщик
Обратите внимание, что в окончательном
своём варианте автосборщик включает только
часть процедуры запуска бейсиков (ZX-Guide
#3). Полная процедура не была нужна,поско-
льку память уже выделена (7863=CLEAR), а
адрес запуска кодовой части (EEE) извес-
тен. Сам бейсик:
ORG #6000 ;for mkace
WASBAS DISP #5D3B
;1 RANDOMIZE USR EEE
DW 256,BASIC-4,#C0F9,#E30,0,EEE
...
DS IMER-$ ;IMER=#5D5D
;обработчик прерываний - тоже в бейсике
RST 56
...
;депакер Hrust2.x без переброски блока
;т.к. код запакован без заголовка (8 байт)
Hpush PUSH DE
LD DE,6
ADD HL,DE
PUSH HL
EXX
POP HL,DE
...
;отсюда запускается загрузчик:
EEE LD (IY+1),#CC ;не запортит #5B88..
LD SP,#7000
...
LD HL,#C000 ;ACE весит<64 секторов
PUSH HL
secsz=$+2 ;кол-во секторов+сектор драйвера
LD BC,#3805
LD DE,(23796)
CALL #3D13 ;загрузка ACE+драйвера
XOR A
;HALT ;будет место - вставим :)
OUT (-2),A
drvphysH=$+2 ;адрес драйвера в памяти
LD HL,#F801
LD DE,#5C01
LD B,L,C,L
LDDR ;переброс драйвера принтера
LD H,87 ;адрес в экране (чистом!)
LD B,4
LDDR ;обнуление атрибутов
POP HL ;#C000 (упак-ный блок ACE)
WasAdr=$+1 ;зависит от usePG4
LD DE,DDD-LDIRLN
CALL Hpush ;распаковка ACE
SUDA
IFN usePG4 ;если не 48k версия
CALL OUT4 ;страница с куском ACE
ENDIF
LD HL,ONERR ;устанавливаем адрес
LD (23747),HL ;обраб-ки ошибок DOS
CALL DEPK64 ;разворачиваем шрифт
JP ini ;пуск
...
ENDLOAD
ENT
;вот переменные, указывающие длину бейсика
ORG 23649
DW ENDLOAD+3
DW ENDLOAD+15
DW ENDLOAD+15
ORG 23641
DW ENDLOAD+1
ORG 23627
DW ENDLOAD
;тут должно лежать имя бейсика
ORG #5CDD
;v1,v2 - номер версии
DB "ACE0.",v1,v2," B
Когда я впервые показал этот автосбор-
щик в народ, мне пришлось приложить прог-
рамму "mkace", которая сама загружает ас-
семблер и нажимает кнопки за пользователя.
Отсюда и контроль наличия метки make.
Обратите внимание, что бейсик-блок фор-
мируется не по рабочему адресу #5D3B. Это
предусматривалось также для mkace, так как
её подпрограмма перехвата клавиш тоже рас-
полагается в области бейсика.
Шрифт переворачивается при сборке и
разворачивается при запуске редактора -
исключительно потому, что он в вывернутом
виде пакуется лучше.
Но Hrust2.x, являясь относительно хоро-
шим упаковщиком (быстрый, например, и ко-
роткий), пакует между тем довольно слабо.
На основе только-только тогда написанного
движка RAR'а и части распаковщика RIP
Романа Петрова я реализовал свой упаковщик
специально для автосборки, под названием
mRIP. Его депакер впритык занимает область
бейсика,что избавляет от необходимости ду-
мать головой,как этот бейсик должен выгля-
деть. Однако грузит и распаковывает этот
бейсик-загрузчик всего один блок.Для игру-
шек mRIP использовать,таким образом,трудно
- разве что подгружать дополнительные бло-
ки из intro (саму intro загрузит лоадер
mRIP). Зато для системных программ,помеща-
ющихся в нижнюю память, mRIP годится. Име-
ется в виду, Pro Tracker им запаковать ещё
можно, а BGE уже нельзя. Впрочем, через
"intro" - тоже можно. А ACE просто должен
быстро запускаться!
Аналогично mRIP'у я сделал и универса-
льный автосборщик m2hrust. Он короче и бы-
стрее, но проигрывает в размере собранной
программы 1-3 сектора.
Иногда бывает непонятно,почему програм-
ма,собранная mRIP'ом, виснет? Во всех слу-
чаях оказывалось, что я забыл установить
стек :)
А. Кодер