Inferno #05
30 апреля 2004

For Coderz - Автосборка программы. Оптимизируем процесс сборки.

     Автосборка программы
   Множество предрассудков и неудачных ре-
шений существует в такой, казалось бы, ес- 
тественной и давно освоенной проблеме, как 
сборка  программ. Сам  по себе этап сборки 
случается  даже  не  для  всякой программы 
(например, для таких,с позволения сказать, 
программных продуктов,на каких учатся про- 
граммировать, он и вовсе не нужен), но при 
необходимости  выпустить  череду версий он 
становится  одним  из  вопросов наибольшей 
траты времени. 
   Однократная сборка традиционно осущест-
влялась через выгрузку памяти после компи-
ляции (кусками или сплошняком),затем полу-
ченные блоки паковались вручную в произво-
льных компрессорах, после чего к упакован-
ной программе пришивался какой-либо извес-
тный  бейсик-загрузчик.  Способ  настолько
традиционен, что появились соответствующие
функции  (выгрузка  блоков  и  склейка) во
многих  ассемблерах - для  первого, - и  в
коммандерах - для второго. Однако,даже та-
кая, вроде бы, автоматизация не спасает от
значительных  затрат  времени. На практике
способ сравним и с ручной выгрузкой 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'ом, виснет? Во всех слу- 
чаях  оказывалось, что  я забыл установить 
стек :) 

                                  А. Кодер



Другие статьи номера:

CacheVox - Пакет программ для импорта и последующего проигрывания с дискет цифровой музыки.

For Coderz - RAYCASTING - сделай себе немного DOOM'a. Алгоритм трассировки 3D лабиринта как в игре WOLF.

Inferno - О журнале.

DIY - Приспосабливаем мышь от Amiga к ZX Spectrum.

Sofтинка - обзор экранных упаковщиков для ZX Spectrum.

Inferno - Авторы и контакты редакции.

Gameland - описание игры Stronghold (Бастион).

Sofтинка - Пакет CacheVox v1.0 для импорта и проигрывания с дискет цифровой музыки.

Интервью - интервью с Disabler'ом - кодером, художником и железячником из Ростова-на-Дону.

Others - Глюки записи на дискеты. Причины и методы борьбы.

Gameland - Краткое описание проблем игры Dune: Imperia 2.

Inferno - Ошибки в предыдущих номерах.

For Coderz - Маленькие программерские хитрости.

Spectrum - Форматы упакованных данных на ZX Spectrum.

Gameland - об игре Hexagonal Filler.

Sofтинка - Hrum 3.5i - самый быстрый LZ-распаковщик с битовым потоком.

DIY - Изготовление хвоста для мышки.

Железо - Исследуем микросхему К561ИЕ10A.

Железо - Исследуем микросхему КР1533ИЕ7.

Железо - Исследуем микросхему К561ТЛ1. .

Sofтинка - экранный компрессор Laser Compact 4.0.

Inferno - Письма в редакцию.

Sofтинка - компрессор текстов MS Pack 01.96.

Inferno - Об оболочке.

Sofтинка - преимущества архиватора Rar.

Sofтинка - Упаковщик RGB картинок Powerful Code Decreaser v6.2.

Ликбез - Что такое плюс и минус напряжения.

Ликбез - Как работает защита элементов цепи.

For Coderz - Нюансы Raycasting-а.

Sofтинка - Real Information Packer 0.2x - один из самых мощных компрессоров на ZX.

For Coderz - Автосборка программы. Оптимизируем процесс сборки.

Inferno - Вступление.

Others - Результаты анкетирования.

Others - The Compo. Об анкетировании.

О Спектруме - размышления о будущем спектрума.

Железо - Еще раз о защите микросхемы КР1818ВГ93.


Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Перекресток драконов - Создаём словарь к Адвентюрной игре.
Поэзия - Про Joker`а.
РАзмышления - New-Age-Computer или компьютер XXI века.

В этот день...   19 июля