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