1 Отредактировано Voldemar0 (18-02-2018 17:56)

Тема: LINKER - Cбopщик пepeмeщaeмыx пpoгpaмм

Сюда перенёс всё про раскопки линкера
Немного не по порядку, ну да ладно

2 Отредактировано Voldemar0 (12-02-2018 21:20)

Re: LINKER - Cбopщик пepeмeщaeмыx пpoгpaмм

Возвращаясь к линкеру.
Его многие видели на разных дисках , но мало кто видел к нему доку (она водится только на полном ИКП-7).

Если читать лень: эта прога обединяет несколько скомпилированных частей в один файл, автоматически согласуя метки между ними.

  ПАКЕТ ПРИКЛАДНЫХ ПРОГРАММ "ШКОЛЬНИЦА"

 К О М П О Н О В Щ И К   П Е Р Е М Е Щ А Е М Ы Х   П Р О Г Р А М М 

   РУКОВОДСТВО ПРОГРАММИСТА


 1. ОСНОВНЫЕ СВЕДЕНИЯ

 Компоновщик перемещаемых программ (LINKER) предназначен для компоновки объектных
программ, полученных различными способами на ПЭВМ Агат.

 1.1. Объектные программы
 Объектная (двоичная) программа - это любая последовательность байтов, т.е.
информация, рассматриваемая как содержимое непрерывного участка машинной памяти.
Это может быть как программа в машинных кодах, так и другая двоичная
информация (например, графическая), а также любая их смесь.
 Абсолютная объектная программа считается связанной с определенным
участком памяти: она корректна только при размещении с некоторого начального
адреса. Эта связь возникает, когда в объектной программе содержатся абсолютные
адреса ее внутренних частей (например, в командах абсолютного перехода).
При загрузке такой программы с другого адреса ссылки внутри нее нарушаются.
 Перемещаемая объектная программа не зависит от места размещения: она либо не
содержит абсолютных ссылок внутрь себя (например, графическая информация), либо
содержит дополнительную информацию, позволяющую при размещении с конкретного
адреса выполнить их пересчет (настройку).

 1.2. Создание объектных программ
 Абсолютные программы порождаются Ассемблером ДОК "Школьницы", а также при
записи участка памяти командами BSAVE из DOS 3.3 и [SAVE из Отладчика ДОК.
 Перемещаемые программы порождаются Ассемблером при указании
в тексте исходной программы псевдокоманды REL. Одновременно для них создаются
таблица перемещения и таблица внешних меток. Формат таблиц приведен в Приложении 1.
 На диске абсолютные программы хранятся в К- и В-файлах, перемещаемые - в
П-файлах (DOS 3.3 выдает их в каталоге, как R-файлы). Файлы этих типов тоже
называются абсолютными или перемещаемыми объектными файлами.

 1.3. Компоновка объектных программ
 Компоновкой называется сборка раздельно подготовленных объектных программ
(модулей, подпрограмм, данных) в единый комплекс. Она сводится к выполнению
одного или нескольких из следующих действий:
 - физическое склеивание нескольких программ в одну,
 - настройка перекрестных ссылок между ними для взаимодействия,
 - настройка программы на другое место размещения в памяти.
 LINKER позволяет проводить компоновку в несколько этапов с созданием
промежуточных объектных программ. Окончательный результат компоновки -
абсолютная объектная программа, готовая к запуску.

 1.4. Задание перекрестных ссылок в программах
 Перекрестная ссылка - это ссылка из одной программы на метку, описанную в
другой программе. Такая метка называется глобальной.
 Метка объявляется глобальной с помощью псевдокоманды ENTRY.
 Использование глобальной метки в другом файле описывается псевдокомандой
EXTRN.
 При создании перемещаемого файла все метки, описанные в данной программе
с помощью ENTRY и EXTRN, собираются в таблицу глобальных меток, которая используется
при компоновке. При создании абсолютного файла таблица не создается.


 2. ВХОДНЫЕ И ВЫХОДНЫЕ ДАННЫЕ

 Входными данными Компоновщика являются объектные файлы, подлежащие сборке, и
входная строка, в которой при вызове Компоновщика указываются имена этих
файлов со списками атрибутов.
 Выходными данными Компоновщика являются:
 а) объектные файлы, содержащие:
 - абсолютную программу, готовую к загрузке и исполнению;
 - перемещаемую программу, таблицу перемещения и таблицу глобальных меток,
пригодные для повторной обработки Компоновщиком;
 - отдельную таблицу глобальных меток;
 б) сводка всех глобальных меток и их новых адресов на экране монитора;
 в) текстовый файл, содержащий ту же сводку.


 3. ОБРАЩЕНИЕ К ПРОГРАММЕ

 3.1. Вызов Компоновщика
 Вызов Компоновщика может осуществляться из Отладчика ДОК,
для чего необходимо запустить программу LINKER:
 > [RUN LINKER
 После чего на запрос "?" ввести входную строку.
 Другой способ запуска - записать команду вызова и входную строку в
текстовом файле (например, с именем LINK):
 [RUN LINKER
 входная строка
 Тогда для запуска достаточно исполнить команду:
 > [EXEC LINK

 3.2. Формат входной строки
 Входная строка имеет вид:
 файлР [ атрР ] = файл1 [ атр1 ] , ... , файлN [ атрN ]
где файлР - имя файла-результата, файл1-файлN - имена собираемых файлов,
[ атр ] - необязательные списки атрибутов каждого файла.
 Полный список атрибутов файла-результата:
 [ Axxxx, К, П, R, B, M, T, Cметка, Sметка ]
        где xxxx - шестнадцатеричное число.
 Полный список атрибутов собираемых файлов:
 [ M, X ]
 Порядок атрибутов произволен, однобуквенные атрибуты можно не разделять.

 3.3. Атрибуты файла-результата
 Axxxx - задать начальный адрес новой объектной программы, по умолчанию - $800.
 К,П,R,B - задает тип объектного файла и, соответственно, вид объектной программы. 
Атрибуты П и R равнозначны. По умолчанию задан атрибут К.
 M - записать таблицу перемещения и таблицу глобальных меток в отдельный перемещаемый
файл, имя которого образуется приписыванием бувы X к имени файла-результата
(см. примеры в 4.).
 Т - записать листинг (сводку глобальных меток новой объектной программы и их
адресов) в отдельный текстовый файл, имя которого образуется приписыванием
буквы M спереди к имени файла (** в данной версии не реализовано **).
 C,Sметка - задать стартовую метку: первой командой нового файла будет записан
переход на указанную метку (** в данной версии не реализовано **).

 3.4. Атрибуты собираемых файлов
 M - не включать объектную программу из данного файла в файл-результат. Фактически
используются только определения глобальных меток. По умолчанию включается.
 X - не включать глобальные метки данного файла в список глобальных меток файла-
результата. По умолчанию включаются.


 4. ПРИМЕРЫ КОМПОНОВКИ ПРОГРАММ

 Пусть в файлах ПРИМЕР1, ПРИМЕР2, ПРИМЕР3 находятся, соответственно, следующие
программы:
AAA: LDA BBB  BBB: LDA CCC  CCC: LDA AAA
 STA CCC   STA AAA   STA BBB
 RTS   RTS   RTS

 Задача состоит в том, чтобы объединить их в одну объектную программу.
 Вставим в каждую программу какой-нибудь ORG, например, с адреса $800,
а также псевдокоманду REL.
 Для обозначения перекрестных ссылок на метки ААА, ВВВ, CCC между файлами
используем псевдокоманды EXTRN и ENTRY.
 В окончательном виде файлы будут иметь вид:
 ORG $800  ORG $800  ORG $800
 REL   REL   REL
 EXTRN BBB  EXTRN AAA  EXTRN AAA
 EXTRN CCC  EXTRN CCC  EXTRN BBB
 ENTRY AAA  ENTRY BBB  ENTRY CCC
AAA: LDA BBB  BBB: LDA CCC  CCC: LDA AAA
 STA CCC   STA AAA   STA BBB
 RTS   RTS   RTS

 При ассемблировании будут созданы 3 перемещаемых файла (пусть их имена -
ПР1, ПР2, ПР3).
 Для сборки их необходимо набрать:
     > [RUN LINKER
 и на дополнительный запрос ввести следующую входную строку:
     ?> КОДФАЙЛ [A2000] = ПР1,ПР2,ПР3     
 По окончании сборки будет выдан такой листинг сборки:
 КОДФАЙЛ:2000-2014 - диапазон адресов, в котором лежит новая программа
 AAA  2000  - новые адреса всех глобальных меток, которые
 BBB  2007    вошли в эту программу
 CCC  200E

 Рассмотрим еще несколько вариантов сборки той же программы:
 1. ПЕРФАЙЛ1 [A2000,П] = ПР1 [X], ПР2 [X], ПР3 [X]
 Глобальных меток в новом файле не будет. В отличие от программы КОДФАЙЛ эта
программа перемещаема (атрибут П) и не готова к немедленному запуску. Но зато ее
можно легко настроить на произвольный адрес:
 КОД_ИЗ_ПЕР1 [A2015] = ПЕРФАЙЛ1
 КОД_ИЗ_ПЕР2 [A202А] = ПЕРФАЙЛ1
 а также переписать в формат B-файла:
 BIN_ИЗ_ПЕР [A203F,B] = ПЕРФАЙЛ1

 2. ПЕРФАЙЛ2 [П] = ПР1, ПР2, ПР3
 В этом перемещаемом файле будут глобальные метки AAA,BBB,CCC.
 
 3. КОДФАЙЛ2 [A2000,М] = ПР1, ПР2, ПР3_
 Здесь будет создан К-файл КОДФАЙЛ2, содержащий то же, что файл КОДФАЙЛ,
но дополнительно будет создан перемещаемый файл ХКОДФАЙЛ2, в котором нет
никакого кода, но есть таблица перемещения и таблица глобальных меток к
файлу КОДФАЙЛ2. Теперь можно, во-первых, исполнить данную объектную программу
(запустить КОДФАЙЛ2), а во-вторых, использовать ее для дальнейшей сборки:
 СБОРНАЯ [A3000,П] = НОВАЯ, ХКОДФАЙЛ2
 Здесь, в предположении, что НОВАЯ программа использует метки ААА,ВВВ,CCC,
будет создан файл со ссылками "в никуда", т.к. сами эти программы отсутствуют.
Это может оказаться удобным при ссылке на резидентную программу (например, ОС).

 5. Как видно из примеров, только перемещаемые файлы могут при сборке
действительно перемещаться. Компоновщик допускает объединение и абсолютных
файлов (возможно, вперемешку с перемещаемыми), но при этом должно строго
выполняться правило состыковки: конец предыдущего файла совпадает с началом
следующего.
 Так, абсолютные файлы КОДФАЙЛ, КОД_ИЗ_ПЕР1, КОД_ИЗ_ПЕР2, BIN_ИЗ_ПЕР из
примеров выше можно скомпоновать именно в таком порядке, и ни в каком ином:
 СУПЕРКОД = КОДФАЙЛ, КОД_ИЗ_ПЕР1, КОД_ИЗ_ПЕР2, BIN_ИЗ_ПЕР
 Даже простое перемещение их недопустимо: обращение
 СУПЕРКОД [A3000] = КОДФАЙЛ, КОД_ИЗ_ПЕР1, КОД_ИЗ_ПЕР2, BIN_ИЗ_ПЕР
 даст ошибку.

             Приложение 1
 ФОРМАТ ПЕРЕМЕЩАЕМЫХ ФАЙЛОВ

 Байты файла Содержимое

 0-1  Начальный адрес A
 2-3  Длина кодовой части K
 4-5  Суммарная длина таблиц T
 6- 6+K-1 Кодовая часть программы
 6+K -... Таблица перемещения
 ... - ... Таблица глобальных меток

      Приложение 2
 СООБЩЕНИЯ КОМПОНОВЩИКА

 #n в сообщениях ниже означает номер объектного файла во входной строке,
в котором произошла данная ошибка.

 ОШИБКА В ПАРАМЕТРАХ - ошибка в задании входной строки.

 МАЛО ПАМЯТИ: СВЯЖИТЕ ПО ЧАСТЯМ - не хватает оперативной памяти для хранения
таблиц Компоновщика. Рекомендуется собрать файлы в два приема с использованием
промежуточного файла.

 КОНЕЧНЫЙ АДРЕС > FFFF: #n - получившаяся программа выходит за границу памяти.
Рекомендуется собрать ее, указав меньший начальный адрес. 

 <метка> ПОВТОРЯЕТСЯ В #n - указанная метка определена дважды в разных файлах;
выдается номер файла каждого повторного определения.

 НЕ СТЫКУЕТСЯ #n - при компоновке абсолютных программ: начальный адрес файла #n
не совпадает с концом файла #n-1.

 МНОГО ИМЕН: #n - больше, чем 256 глобальных меток, при сборке недопустимо. 

Для

3 Отредактировано Voldemar0 (13-02-2018 06:44)

Re: LINKER - Cбopщик пepeмeщaeмыx пpoгpaмм

Че-то приподнял всю эту тему - насчёт разработки крупных прог и программных комплексов.
Вторая задача даже посложнее: к примеру, есть библиотека, хочется иметь её скомпилированной и уже в таком виде подключать к другим программам.

На агате это делалось раньше просто на уровне исходников, причем библиотека либо вкладывалась в цепочку исходников и допиливалась по месту либо ... либо не допиливалась.
Это клёво, конечно, но компиляция идёт долго. Более худший вариант: компилировалась отдельно, а входные точки и переменные в основную прогу передавались в виде фиксированного списка констант (что совсем уж плохо).

В одной из своих старых прог я делал по другому: в начале библиотеки вставлял таблицу входных точек, создавая её командами DW. Это было простое позднее связывание, a'la динамическая линковка. Ни у кого на агате таких решений не видел. Но это требует привязки библиотеки к конкретному адресу. Зато программу и библиотеку можно существенно модифицировать по отдельности друг от друга.

Теоретически (ну прям вот очень теоретически), на агате существовал всем известный (и - одновременно - никому неизвестный) LINKER. Именно он может собирать группу уже компилированных частей программы в один файл, загружающийся в заданную точку, причем согласовав все метки, смещения и всё, что нужно.

Известный - потому что был почти на всех системных дисках с ДОК ("Ассемблер"), неизвестный - потому что ни в одной коллекции я не видел следов его использования. Сам я его тоже не мог использовать, потому что не знал - как? ИКП у меня не было, а инструкция к LINKER была только в ИКП-7-840.

Я даже пытался его декомпилировать и разобраться в логике работы. Но результатов (уже не помню - почему) не было. Наверное, свои велосипеды оказались проще.
При разборе коллекции Лёвина (автор СУБД Паруса/Карата/Эврика и Шахмат/Шашек) обнаружилась как инструкция к Linker так и его исходники.

Некоторое время назад я приступил к его неспешному ковырянию.
Выяснилось, что сам по себе линкер почти работает, но только в старых версиях Школьницы. Судя по всему, те, кто записывал его на диски с ИКП, не пытались проверить работоспособность.

Тут вот какая штука: Цикоза (руководитель группы авторов Школьницы, LINKER родом оттуда же), стремился сохранять совместимость между разными версиями ДОК, в том числе по программному интерфейсу. Но делалось это немного коряво: при компиляции ДОК добивались того, чтобы ДОКУМЕНТИРОВАННЫЕ входные точки и таблицы аргументов драйверов файловой системы и дисководов совпадали. А вот недокументированные - как повезёт. Linker зачем-то использует входную точку процедуры PUTTSL (мне пока не ясно - зачем она ему?), которая в ИКП-шном ДОК уехала на пару кб в сторону.

Но даже подправив этот косяк (а заодно и сравнив работу в старой версии Школьницы) я не смог добиться корректной сборки загружаемого файла. И над этим я ещё буду работать.
А вот компиляция программ в так называемый Перемещаемый формат (который LINKER ожидает на входе) выполняется ДОК успешно. И это весьма радует.

С другой стороны: ассемблер Громова, к сожалению (проверил версии 1.0 и 5.0), при указании параметра R, хотя и собирает Перемещаемый файл, но таблица меток там записывается ... не, не так. Вместо таблицы меток записывается мусор :((. И это весьма огорчает. Тем более исходников громовского ассемблера у меня нет.

Коротко о том, что представляет собой Перемещаемый файл:
это скомпилированный код программы (или библиотки), а также таблицы, в которых указано:

1) По каким смещениям находятся абсолютные адреса (например, аргумент команды JMP). Эти адреса линкер пересчитает при сборке окончательного проекта, подставив их фактические значения.

2) Какие адреса были неопределены и их следует подставить из другой библиотеки.

3) Какие адреса были известны и их следует использовать для настройки другой библиотеки.

Сам механизм импорта/экспорта адресов широчайше используется во всех современных ОС. Причем в двух видах: как для связывания кода на этапе компиляции, так и для связывания на этапе загрузки. Всем известные DLL- файлы -  ни что иное, как сильно развившийся аналог R-файлов агата (хотя, конечно, сама эта идея старше как Агата так и всех современных ОС).

На данный момент мне удалось разобрать формат R-файлов, осталось выловить ошибки в LINKER'e.

4 Отредактировано Voldemar0 (14-02-2018 19:04)

Re: LINKER - Cбopщик пepeмeщaeмыx пpoгpaмм

Неожиданно подоспел очередной акт мерлезонского балета.

Разбирая код LINKER'а обнаружил забавное место:

 403  LDA CODE,%N ;нoв.cмeщeниe 
 404  ADC OFFSETL,X
 405  STA OFFSETL+1,X
 406  LDA CODELEN+1
 407  ADC OFFSETH,X             
 408  STA OFFSETH+1,X           

Так текст выглядит в редакторе ДОК семёрки без расширенного знакогенератора.
Строчка 403 немного странная, не правда ли?
На самом деле ',%' - это мусор (причем символы с D7=0). Почему компилятор не давал на него синтаксической ошибки - не знаю. Из последующих строк видно, что должно быть: LDA CODELEN. К некоторой неудаче в тексте есть определения как CODELEN так и CODE, так что ошибки "метка неопределена" также не возникло.

Судя по всему, эта ошибка зело древняя. Генерируемый код находится по смещению $214 (от начала файла):

2A13-  A5 20 ..   "%?"    LDA  $20           <== тут должно быть LDA $DC
2A15-  7D 60 0C   "щю?"   ADC  $0C60, X
2A18-  9D 61 0C   "?а?"   STA  $0C61, X
2A1B-  A5 DD ..   "%]"    LDA  $DD
2A1D-  7D 78 0C   "щь?"   ADC  $0C78, X
2A20-  9D 79 0C   "?ы?"   STA  $0C79, X

Я не поленился и убедился, что неверный код есть даже в таких, вполне авторитетных, источниках, как "заводские" ИКП1 с маркировками Фг и указанной на этикетках контрольной суммой.

Будучи исправлен и пересобран, LINKER вполне заработал. К сожалению, сразу обнаружился один его существенный недостаток: у него не предусмотрена диагностика неопределённых меток (т.е. тех, которые объявлены в тексте как "внешние", но для которых не было найдено внешнее значение) при генерации финального (неперемещаемого - К или B) файлов. Ну просто нет такой ошибки.

Продолжим разбор...

(offtop: сегодня впервые в жизни держал в руках плату, изготовленную по собственному проекту. Удивительные ощущения. Производство платы и монтаж - всё промышленное, в Новосибирске заказывали. Около 20 тысяч р обошлись три экземпляра. Предсерийные экземпляры. Несколько USB-устройств + хаб. Вроде работает. К Агату отношения не имеет ... хотя, в жизни, всё ко всему имеет отношение, пусть и косвенное)

5 Отредактировано Voldemar0 (15-02-2018 20:54)

Re: LINKER - Cбopщик пepeмeщaeмыx пpoгpaмм

Продолжаем жрать вкусный кактус :)

Сегодня я снова обратил внимание на странные, хоть и нечастые косяки LINKER'а. Вроде бы случайные. Но сегодня он вовсе неслучайно зависал. Всего за пару часов удалось выяснить, что линкер ни при чем, при чем совсем другие явления, куда более высокого порядка.

Авторы ИКП-шной сборки ДОК опять что-то забыли. Но теперь не пару команд, а целый цикл инициализации.

На агате было полно всяких игротек и игрозапускалок. Они не очень сложные: прочитал какой нибудь файл в память и передал на него управление. Пусть даже посекторно. Чуть сложнее разработать то, что уже претендует на звание ДОС: это всё таки библиотека разных файловых процедур: чтение, запись, удаление, переименование. Да ещё чтобы командный процессор был. А вы в юности понимали как устроены синтаксические анализаторы комстроки ? Ну и высший пилотаж: это ещё и полноценный набор функций произвольного доступа к части файла.

Так вот единственная популярная ДОС, которая всё это кое как делает - это ДОС3.3. Она - самая сложная (пожалуй, сложнее только спрайт-ОС, ну и ONIX, хотя твёрдо не уверен). Про BTK сказать сложно, Школьница, хотя и может оперировать с фрагментами файлов, но фрагмент - только 1 байт, не больше. Всё остальное, что как-то читает файлы, как правило, может только прочитать весь файл за раз.

Чтобы оперировать с фрагментами файлов никак невозможно обойтись без отдельных операций открытия, чтения/записи и закрытия файла. Иначе дело станет совсем грустным (чтение/запись сами по себе требуют времени, а если ещё и каждый раз заного искать файл на диске - совсем до скорости программируемого калькулятора можно дойти.)

Но для того, чтобы операцию открытия файла как -то связать с последующей операцией чтения, нужен какой-то блок ОЗУ, в котором будет хранится информация об открытом файле.
Такие куски ещё называли когда-то давно FCB (file control block - например, в ранних версиях MS-DOS), потом стали называть по другому. Ну да неважно. Важно, что при старте ДОС эта память должна быть очищена. Ну то есть ДОС должна знать, что сейчас нет открытых файлов.

Вот.

У Школьницы регион FCB находится примерно в адресах $403..$4FF.

У канонично-семёрочного ДОК фрагмент инициализации, занимающийся очисткой FCB, начинается с адреса $2753, где уютно сидит примерно такой код:

275E -   A9 07 ..   ")."    LDA   #07
2760 -   8D 00 04   "..."   STA   0400
2763 -   A9 90 ..   ")."    LDA   #90
2765 -   8D 01 04   "..."   STA   0401
2768 -   A9 7F ..   ")ъ"    LDA   #7F
276A -   8D 02 04   "..."   STA   0402
276D -   A9 00 ..   ")."    LDA   #00
276F -   A2 07 ..   ""."    LDX   #07    
2771 -   9D B0 03   ".0."   STA   03B0, X
2774 -   CA .. ..   "J"     DEX   
2775 -   10 FA ..   ".З"    BPL   2771
2777 -   A2 03 ..   ""."    LDX   #03        
2779 -   9D 00 04   "..."   STA   0400, X    
277C -   E8 .. ..   "Х"     INX   
277D -   D0 FA ..   "PЗ"    BNE   
277F -   EE B3 03   "Н3."   INC   03B3
2782 -   A9 43 ..   ")c"    LDA   #43
2784 -   8D E7 1B   ".Г."   STA   1BE7
2787 -   A9 27 ..   ")."    LDA   #27
2789 -   8D E8 1B   ".Х."   STA   1BE8
278C -   A9 21 ..   ")."    LDA   #21
278E -   8D E4 1B   ".Д."   STA   1BE4
2791 -   AE 00 0B   "..."   LDX   0B00
2794 -   8E 00 B0   "..0"   STX   B000
2797 -   20 37 0B   "..."   JSR   0B37
279A -   B9 17 0F   "9.."   LDA   0F17, Y
279D -   20 4B B4   ".k4"   JSR   B44B
27A0 -   A5 4B ..   "%k"    LDA   4B
27A2 -   8D B6 B4   ".64"   STA   B4B6
27A5 -   4C 00 1E   "l.."   JMP   1E00

Тут мы видим сброс всяких структур, в том числе $403..$4FF. Дальше идёт установка текущего дисковода и кой что ещё.

А что у нас в ИКП-7 ?

274F -   A5 4D ..   "%m"    LDA   4D
2751 -   8D 33 20   ".Ё."   STA   2033
2754 -   8D 25 20   "..."   STA   2025
2757 -   20 64 1B   ".д."   JSR   1B64
275A -   A5 4B ..   "%k"    LDA   4B
275C -   8D FF AF   ".Ъ/"   STA   AFFF
275F -   A9 07 ..   ")."    LDA   #07
2761 -   8D 00 04   "..."   STA   0400
2764 -   A9 90 ..   ")."    LDA   #90
2766 -   8D 01 04   "..."   STA   0401   
2769 -   A9 7F ..   ")ъ"    LDA   #7F
276B -   8D 02 04   "..."   STA   0402
276E -   A9 00 ..   ")."    LDA   #00
2770 -   8D EE 04   ".Н."   STA   04EE
2773 -   AC B3 03   ",3."   LDY   03B3
2776 -   A2 07 ..   ""."    LDX   #07
2778 -   9D B0 03   ".0."   STA   03B0, X
277B -   CA .. ..   "J"     DEX   
277C -   10 FA ..   ".З"    BPL   2778
277E -   8C B3 03   ".3."   STY   03B3
2781 -   A9 3F ..   ")."    LDA   #3F
2783 -   8D E7 1B   ".Г."   STA   1BE7
2786 -   A9 27 ..   ")."    LDA   #27
2788 -   8D E8 1B   ".Х."   STA   1BE8
278B -   A9 21 ..   ")."    LDA   #21
278D -   8D E4 1B   ".Д."   STA   1BE4
2790 -   4C 00 1E   "l.."   JMP   1E00

Похоже ?
Да. Но регион 403..4FF почистить забыли :(
А, не, не забыли:

276E -   A9 00 ..   ")."    LDA   #00
2770 -   8D EE 04   ".Н."   STA   04EE

Эта парочка команд вообще-то чистит как раз таки первый FCB. Значит не забыли!
Но не подумали о том, что их там больше одного. На самом деле их MAXFILES - содержимое ячейки $400.

Вообще-то тут ещё нет установки B001 - там в ИКП номер дисковода, но это есть в другом месте:

3B69 -   A5 55 ..   "%u"    LDA   55
3B6B -   99 A3 B3   ".#3"   STA   B3A3, Y
3B6E -   C8 .. ..   "H"     INY   
3B6F -   8C 01 B0   "..0"   STY   B001       <==
3B72 -   84 B4 ..   ".4"    STY   B4
3B74 -   8C B3 03   ".3."   STY   03B3
3B77 -   C0 04 ..   "@."    CPY   #04
3B79 -   90 1C ..   ".."    BCC   3B97
3B7B -   4C 37 3B   "l.."   JMP   3B37

это, конечно, не весь код инициализации. Важно другое - ну тут тоже нет очистки $403..$4FF. И вообще его нигде нет. Заполните этот регион любым мусором и после загрузки ДОК увидите его на месте. Я попал на это в ИКП-7 и позднее убедился в том же для ИКП-9.

В общем-то это всё влияет только на группу операций fopen/fclose (в системе имён Школьницы это OPEN/SHUTFILE), компилятор ассемблера, например, их не использует, равно как и текстовый редактор. Они работают сразу с LOAD/SAVE - чтение/запись файла целиком.

Ну ладно, не инициализировали регион, он забит мусором - значит ДОС на запрос открытия файла скажет "нет свободных буферов" (NOFRBUFS). Есть у неё такой код ошибки. Но это было бы слишком просто. И скучно.

Чтобы не было скучно, авторы LINKER'а подключают к ДОС свой обработчик ошибок. Но они забыли, что при возникновении ошибок не надо суетится и закрывать последний открытый файл. Потому что он может быть и не открыт, если ошибка произошла во время исполнения fopen. И вот в этот момент ДОС и пытается закрыть файл, который никто не открывал. Она бы не пыталась этого делать, если бы $403..$4FF был чистым.

Но там мусор, и снежный ком нарастает: в дескрипторе файла есть поле с номером дисковода, там мусор, драйвер дисковода не проверяет номер дисковода (какая незадача!),  а так как драйвер двухстандартный (140+840кб), то неизвестный номер имеет неизвестный обработчик ..... всё заканчивается "программа выполнила недопустимую операцию......." BRK, короче. Хотя, конечно, как повезёт. Зависит от первоначального мусора.

6 Отредактировано Voldemar0 (16-02-2018 20:43)

Re: LINKER - Cбopщик пepeмeщaeмыx пpoгpaмм

avivanov76 пишет:

А тут ftp://ftp.apple.asimov.net/pub/apple_II … Manual.pdf на 228 странице описание для ProDOS, с форматом словаря.

Два часа залипал :) СПАСИБО !
Накопал кой что новое для себя, притом весьма замороченное.
("low 8 bit of 16 bit value for an 8-bit field containing upper 8 bits, zero if $40 bit clear in RLD byte one. Or, if the $10 bit is set, than this is the ESD symbol number". И кое что ещё)


Да, формат совпадает, если я что-то не упустил. Только на 229-230 страницах описан байт не то sl не то s1: у него здесь упомянуто два бита (D3 и D4), в то время как ДОК использует почти все биты:

    d7 - =d1 ?
    d6 - эта переменная не используется (ENTRY считается использованием)
    d5 - определена в коде (не EQU или DSECT)
    d4 - импорт (вместо адреса в младшем байте будет номер в таблице импорта)
    d3 - экспорт
    d2 - ?
    d1 - 0 - ok, 1 - метка не определена и не имеет EXTRN
    d0 - 0 - 1 byte, 1 - 2 byte

7

Re: LINKER - Cбopщик пepeмeщaeмыx пpoгpaмм

Voldemar0 пишет:

Продолжаем жрать вкусный кактус :)

Да... Это просто песня.
Сразу вопросы возникают, как у разработчиков хранились исходники, если из побитых файлов собирались официальные ИКП, и как они тестировали софт, если даже в ДОС такие баги.
Я правильно понимаю, что в какой-то момент разработка системного софта была передана из Новосибирска в Москву?
Может, после передачи от одной команды разработчиков к другой все эти баги полезли?

И интересно про формат R. Вроде на Apple их было 2: один под DOS 3.3, другой под ProDOS. Вот тут кусочек описания для DOS 3.3, но без словаря символов http://apple2.org.za/gswv/a2zine/faqs/Csa2DOSMM.html

Byte   Meaning 
----   ------- 
$00-01 Original program load address 
$02-03 File length (program image + relocation dictionary) 
$04-05 Length of program image alone (not including relocation 
       dictionary) 
$06-xx Program image 
$xx-yy Relocation dictionary 

А тут ftp://ftp.apple.asimov.net/pub/apple_II … Manual.pdf на 228 странице описание для ProDOS, с форматом словаря.

Что нибудь общее с Агатовским форматом есть?

8 Отредактировано Voldemar0 (16-02-2018 11:47)

Re: LINKER - Cбopщик пepeмeщaeмыx пpoгpaмм

avivanov76 пишет:

Я правильно понимаю, что в какой-то момент разработка системного софта была передана из Новосибирска в Москву?

В Новосибе была команда Виктора Цикозы, он работал над Школьницей, сперва для эпла, но быстро перетащили её на появляющийся Агат. Вылизано было всё до блеска - я правда не знал об ошибках в ДОК, просто не сталкивался. Хотя, в отличие от многих агат-программистов, писал многое именно под ДОК либо под ДОК + Бейсик-60 (один исходник, в начале котого делал условную компиляцию блока системных адресов).

В Москве же пилили агат и тот софт, который шел с ним в комплекте (в общем-то всё, кроме Школьницы).
Дальше мои предположения.
Когда появился 840кб флоп и было желание разные разработки объединить на один диск, группа Цикозы сделала релиз для 840кб, но исходники в Москву не отдала (если бы отдала - мы бы где нибудь их уже раскопали). Скорее всего, им уже был неинтересен этот проект, они во всю пилили тогда Спрайт-ОС. Москва особо не пользовалась всевозможными расширенными фичами ДОК,  в т.ч. П-файлами. Возможно потому, что основной их проект был ИКП-бейсик, который строился как дизасм эплсофта, а при дизасме было важно сохранить всевозможные входные точки. Так что как раз вся суть ассемблера, как автоматического калькулятора плавающих адресов, была не очень нужна. Отладчик ДОК для отладки Бейсика также был малоинтересен - он не слишком мощнее сисмона, если отпилить от него ДОС, компилятор и редактор. А если не отпилить, то будет драка за использование общих рессурсов (памяти, в первую очередь).
Ну это Москва=ЛЭМЗ (наверное, Кривцов, в первую очередь). А остальные московские разработчики вообще вряд ли имели какой-то выход на Цикозу.
Ну можно было письмо написать.... не знаю, чем бы это закончилось.

По сути, разработчиков высокого класса, которые бы могли задействовать линкер, было не так уж много.
Мне кажется, единственный человек, который мог бы взяться (не только по уровню знаний, но и по подходам и сфере интересов) за исследование/использования линкера - Александр Голов. Ему бы это было полезно при разработке BTK. Почему он этого не сделал - не знаю. Но можно спросить.

А тестирование софта - это ... как бы сказать ... не было тогда такой буквы. Ведь чтобы реально тестировать, нужен мощный штат тестировщиков, сответствующая должность, деньги, время, и проект тестирования. Возможно, разработка каких-то автоматических тестов. Но отношение руководства было примерно такое: "Компьютер ? зачем ? А, это чтобы управлять чем нибудь и считать. О! Причем считать по написанной программе, которую пользователь может написать сам. Ага. Понятно. А зачем нам тестировать программы, если пользователь пишет их сам?"
Т.е. как бы ПО не существовало как отдельного продукта. Вот бейсик, он кажется работает, вот программа же 2+2 может сложить. О чём говорить, если официально разработанное ПО не имело номеров версий ? Кто ставил номера версий на свои работы ? Голов (только на BTK, на ALV Super DOS, ALV Format не было), Цикоза (только на Школьницу в целом и отдельно на РАПИРу), Бадер (и то можно найти сборки MouseGraf с одинаковыми номерами версий, но различных по внутренностям), Лёвин (только на свои СУБД, на шахматах вроде не было). Всё. Я больше не помню. А, вроде ещё Самарцев (Сирин и Маркис).


avivanov76 пишет:

Что нибудь общее с Агатовским форматом есть?

Очень похоже. Надо почитать ссылки, возможно, всё 1:1.

9 Отредактировано Voldemar0 (18-02-2018 20:11)

Re: LINKER - Cбopщик пepeмeщaeмыx пpoгpaмм

Маленько крыша едет, но раз уж начал и почти процентов 80 сделал, то стоит всё таки закончить.

Короче: грустно.

LINKER - замороченная прога, но бывает замороченность красивая и/или нужная, а бывает наоборот. Линкер - как раз "наоборот".

Что он делает ? Он читает группу файлов (чуть ли не до 30 штук можно), выдёргивает из них таблицы импорта и имён и затем начинает их тасовать.

Легко ли это? В общем-то не сложно. Если бы слабая определённость задачи: неизвестны ни размеры файлов, ни размеры таблиц (а они, как минимум, зависят как от числа переменных, так и от длин их имён).

Чтобы добится почти 100% универсальности, авторы линкера пошли наикрутейшим путём:

1) Все файлы читаются побайтно. Самое странное, что даже там, где нужно просто пропустить кусок, вместо того чтобы использовать POSITION (fseek), авторы пропускают байты вызовами RDBYTE (fgetc).

2) И потом начинается тусссссня: из каждого файла выдёргиваются таблицы, скучиваются в два массива, один из которых растёт вверх, а вот второй - вниз. И я как-то не заметил проверки на столкновение двух массивов :) Ну, на самом деле буфер довольно большой, так что вряд ли будут проблемы.

Как нетрудно понять, все эти операции требуют 16-битных указателей. Много указателей. Массивы указателей. Поэтому код забит всевозможной арифметикой. Умножение встречается редко, но разнообразные сложения/вычитания - ужас.

Я думаю, это тот случай, когда проще уже было бы написать всё на бейсике: массивы 16-битных там есть, логика программы была бы гораздо более очевидной.

А если сделать какую-то логику фрагментарного чтения (блок-за-обращение) - это бы уже легко компенсировало потери на работе интерпретатора. К сожалению, прочитать произвольный файл (а LINKER читает B, К, R/П-типы) по частям можно только накручивая над стандартными ДОСами свои надстройки, но оно бы того стоило.

Более того, если предположить, что файлы не такие уж здоровые (всё таки это уже не текст), то читая файл-за-обращение, можно было бы и логику не особо усложнять. Свободного ОЗУ в агате не мало, такой подход проиграл бы только в момент сборки какого-то огромного исполняемого файла, занимающего кб 40 (160 блоков).

Через это всё я предполагаю, что линкер не стал практически использоваться из-за крайней тормознутости. Я часто гоняю тестовый пример, состоящий из 4-х файлов, примерно по полблока (128 байт) каждый. Линкеру требуется где-то секунд 15 на их обработку (если не турбировать :). Что будет, если загнать в него несколько Кб кода и таблиц - не знаю. Но попробую.

Чтобы было понятно, в чём заморока с таблицами (откуда столько указателей и суеты) расскажу о формате П-файла.

Он состоит из 4-х частей: сперва идёт 6 байт заголовка, затем исполняемый код и две таблицы: RLD и STB.

1) Таблица реаллокации (RLD). Это список смещений в коде, в которых линкеру что-то нужно изменить.

2) Таблица имён (STB). Любые переменные, через которые код данного файла связан с другими П-файлами.

Как используются таблицы объясню на примерах:

 ORG $2800
 REL

ST
 JMP ST

ST - метка. Она не импортируется (так как объявлена в этом файле). Но в команде JMP она должна быть пересчитана, так как точка загрузки файла заранее неизвестна. В П-файл на её место будет записано число $2800, а адрес ($2801) этого места попадёт в таблицу RLD.
Если линкер разместит код из этого файла по адресу $1000, из таблицы RLD он узнает, что значение по адресу $2801 нужно пересчитать как СТАРОЕ_ЗНАЧЕНИЕ - СТАРЫЙ_ORG +  НОВЫЙ_ORG. В таблице STB об этом ничего не будет сказано, так как имя ST не экспортируется.

А вот если в текст будет добавлено "ENTRY ST", то в таблице STB появится запись о том, что существует такая переменная ST и её адрес $2800, но при этом будет стоять пометка, что адрес является перемещаемым и перед использованием в другом файле его сперва нужно пересчитать.

Если теперь команду JMP ST убрать из текста, это приведёт к удалению записи из таблицы RLD, в STB запись останется.

 ORG $2800
 REL

 EXTRN ST
 JMP ST

Здесь ST будет задано значение $0000. А таблицы будут заполнены так: в STB будет зарегистрировано имя ST с неизвестным адресом, а в RLD появится запись об адресе $2801, но будет стоять пометка, что адрес является импортируемым и ссылка на запись в таблице STB.

 ORG $2800
 REL

 EXTRN ST
 ENTRY ST

В этом примере получится пустой код, запись будет только в таблице STB о неизвестном имени ST, которое отмеченно - одновременно - как импортируемое и экспортируемое.

Это пока были обычные двухбайтовые указатели. Но можно сделать и однобайтовые:

 ORG $2800
 REL

 EXTRN ST
 DFB >ST

Получим запись в STB о неизвестном значении ST, а в RLD указатель $0000 со ссылкой на запись в STB, но с флажком, указывающим, что требуется только один младший байт.
Если DFB > заменить на DDB (сохранение значения в порядке старший-младший), оно будет сохранено также, но с отметкой о двух байтах, хранимых в прямом порядке.

 ORG $2800
 REL

ST:
 DFB <ST

Этот пример, хотя и выглядит почти как предыдущий, на самом деле сложнее. Дело в том, что
в RLD указывается, что по смещению $0000 нужно пересчитать значение и сохранить его старший байт. Но чтобы пересчитать правильно, нужно знать и младший байт этого значения (ST). И в этой ситуации он сохраняется в RLD.

Свои особенности начинаются, если переменная объявлена через EQU либо как метка внутри DSECT..DEND. В этом случае она может стать как статичной (независящей от реаллокаций кода), так и динамической (и я пока не понял, в каких случаях это происходит).

К сожалению, следующий пример, хотя и компилируется без ошибок, работает не так, как бы хотелось:

 ORG $2800
 REL

 EXTRN ST
AX EQU ST
 JMP ST

Запись в STB о переменной ST появится, в RLD тоже будет ссылка на аргумент команды JMP, но AX будет иметь значение 0 и в таблицы не попадёт.
Тоже происходит, если, например, ST будет использовано внутри DSECT..DEND как аргумент ORG. Компилятор воспримет это как ORG 0 и все переменные внутри DSECT получат фиксированные адреса начиная от 0.

Это плохо, хотя, в "больших" ассемблерах для решения такой проблемы есть понятие именованного сегмента: например, если вы объявляете в любых частях программы сегмент с именем DATA и объявляете переменные разных частей в нём, то на выходе, задав фактический адрес этого сегмента (например, $E0), вы разместите все переменные подряд от адреса $E0 и выше (по порядку их нахождения в частях программы).

Как видно, между всего -то двумя таблицами могут устанавливаться разные связи и нельзя выделить какую-то одну таблицу как более важную или ведущую. Как RLD так и STB могут иметь связанные записи, но могут иметь и самостоятельные. Более того, разные записи RLD могут ссылаться на одну и ту же запись в STB.

Отдельной сложности добавляет то, что так как STB содержит имена переменных, размеры её записей не фиксированы и линкер вынужден пробегать все ячейки памяти таблицы не имея возможности расчитать положение очередной записи.

Всё это приводит к заметной сложности кода линкера.

10 Отредактировано Voldemar0 (22-02-2018 22:51)

Re: LINKER - Cбopщик пepeмeщaeмыx пpoгpaмм

Закончил разбор кода линкера, решил немного рассказать о его архитектуре и своих впечатлениях.

Прога вызывает неоднозначное впечатление.

С одной стороны - она, несомненно, писалась человеком вполне опытным: это видно не только по использованию всяких приёмов вроде SKIP-COMMAND (добавляем в код байт $2C и следующая двухбайтовая команда становится аргументом почти ничего не делающей команды BIT и, таким образом, как бы пропускается) или многовходовых (в том числе частично-рекурсивных!) подпрограмм:

PROC:
 ...
 JSR PROC2
 LDA #1
 DFB $2C
PROC2:
 LDA #2
 ...
 RTS

Тут есть и почти корректное оперирование с ДОС Школьницы, кой где оптимизация памяти (некоторые рабочие ячейки используются под разными именами в нескольких блоках). Чувствуется, что прога написана не "в лоб", а как бы с неплохой локальной оптимизацией кода. Попадаются и довольно любопытные приёмы программирования, типа такого:

int enable_make_STB_file; // 0 - не надо, 1 - надо

  fopen(filename);
  { Запись кода }
  { Запись RLD }
1:
  { Запись STB }

  if (--enable_make_STB_file) { fclose(); return; }
  fclose();
  filename = 'X' + filename;
  fopen(filename);
  goto 1;

Процедура должна создать полный выходной файл, но если enable_make_STB_file == 1, то также создаётся отдельный файл меток, содержащий только STB и имеющий такое же имя с дополнительным символом 'X' в начале.

Механизм извращённый, конечно; додуматься до него начинающему было бы не просто.


С другой стороны: глобально линкер не красив. Представьте себе почти 1200 строк кода, содержащего 5 (и автор их явно выделил комментариями!) совершенно различных частей, написанных в виде одной большой процедуры. Эта дикая простыня временами прерывается вкраплениями мелких процедур-подпрограмм, которые основной текст "обтекает" JMP-ами. Тут, конечно, в использовании стека не накосячишь, но я посчитал количество команд RTS. Угадайте, сколько их ? Пять штук, Карл ! Ну да, процедур побольше, благодаря вложенности некоторых, близких по смыслу, но .... Это всё было бы отличной работой какого нибудь компилятора С, но не программиста, пишущего, хоть чуть-чуть расчитанный на дальнейшую поддержку или развитие, код.

Это первое. Второе: имена меток. В "больших" ассемблерах существует понятие локальной метки, оно есть даже в ассемблере Громова. Чтобы не придумывать какие-то замысловатые имена вы просто пишете что-то вроде "1:" - это объявление метки и затем ссылаетесь на неё как "1b" или "1a", что понимается компилятором как "метка 1 выше по тексту" или "метка 1 ниже по тексту". Так вы избавляетесь от необходимости изобретать уникальные имена для огромного количества меток, которые отстоят от команд переходов и ветвлений в пределах 5-15 строк (видимости на экране). Заодно в ссылке на метку сразу видно где её искать - выше по тексту или ниже. Подобрать имена для более "глобальных" меток гораздо проще, тем паче, что их гораздо меньше, нежели локальных.

Но так как ДОК не поддерживает локальные метки, можно придумать какую нибудь систему именований, например:

OPEN_NEXT_FILE:
 ...
ONF.LOOP:
 ...
 BCC ONF.FIN
 ...
 BNE ONF.LOOP

ONF.FIN:
 RTS

Т.е. входная точка процедуры имеет максимально звучащее имя, а дальше все метки этой процедуры имеют имена в виде аббривеатуры, точки и уточнящего слова или просто числа.

Вместо этого в линкере метки идут вида: CC, APR, NOIX, RC... Поди, угадай, что L3 - метка инкремента счётчика цикла, после которого будет переход к следующему обрабатываемому элементу таблицы. А к чему относится метка ADDR ? Ага, в программе, которая предназначена для обработки адресов...

Итого:

;  LINK : 08.03.88 (c) Группа В. Цикозы
        : 13.02.18 (c) Ravodin & ...
 В этом тексте исправлено/изменено следующее:
 - испорченная строка "LDA CODE??N ;нoв.cмeщeниe";
 - изменён адрес процедуры PUTTSL;
 - добавил константу выравнивания таблицы меток при выводе на экран (MAXVARLEN=20, было фиксированное значение 9);
 - добавлено много комментариев.

 Нужно:
 - сменить метки на более вменяемые;
 - вспомнить про процедуру позиционирования по файлу;
 - в обработчике ошибок запретить закрытие файлов, которые мы не открывали !
 - поискать '?' и подумать, не следует ли там встроить дополнительные проверки и улучшения;
 - подумать об оптимизации парсера сроки параметров и о разделении этого монстра на части;
 - возможно, имеет смысл выделить какие-то общие операции, применяемые в каждом из этапов и забить их в процедуры;
 - собрать все подпрограммы в кучу ? или наоборот ?

Нужно 2:
 - дописать надстройку, которая позволит эффективно работать с файловой системой (поблочно, с произвольной адресацией);
 - сделать ветку "STATIC-линкера": который бы читал все или часть файлов "за раз" в память и работал напрямую с большим буфером в ОЗУ.

Я специально вносил изменения только такие, которые не сместят команды. Это сделано для того, чтобы можно было потом двоично сравнить исходный бинарник и обновлённый.

То, что перечислено после слова "Нужно", я предоставляю желающим.
Хотя, если в дальнейшей эксплуатации линкера вылезут ещё какие-то ошибки, буду исправлять их сам.

Теперь про пять блоков:

   C - парсинг командной строки
   I - первый проход: зaгpузкa RLD и STB, oпpeделение нoвыx бaзoвыx aдpecoв
  II - второй проход: нacтpoйкa пepeкpecтныx ccылoк, пocтpoeниe GLOBALS - пpooбpaз нoвoй STB
 III - третий проход: запись выходного файла, также запись файла меток (если нужен) и вывод сортированной таблицы GLOBALS
   L - вывод (с сортировкой по адресам) глобальной таблицы меток

Здесь есть слово "проход" - это из авторских комментариев и, IMHO, подобрано оно не вполне верно.
Обычно, под "проходом" понимается работа над одним и тем же массивом данных, здесь же:

- первый проход - это чтение файлов и "выкусывание" таблиц из файлов в память.
- второй проход - это анализ и обработка получившихся таблиц.
- третий проход - повторное чтение файлов и "выкусывание" из них кода. Код обрабатывается в соответствии с таблицами и записывается в выходной файл (автор называет его "объектным", но, насколько я помню терминологию, объектными как раз таки являются П-файлы - входные для линкера). А то, что синтезирует линкер - это уже "загружаемые" файлы.

PS И всё же - это код новосибирцев и группы Цикозы (а может и его самого?). Только они всё таки комментировали свои творения. Причем не только в начале файлов. Комментарии часто сокращены всевозможными способами, так что пока сам не поймешь кусок кода, комментарий мало что объясняет. Но уж когда поймешь - сразу становится понятно, что написано в комментарии :)) Значит правильно разобрался.

(Всё на сегодня, пойду пялится в футураму...)

11

Re: LINKER - Cбopщик пepeмeщaeмыx пpoгpaмм

Исправил найденные проблемы, линкер завёлся, в общем-то метки (входные точки) он вполне нормально понимает и обрабатывает.

Вылез мелкий косяк только:

 ORG $20
 REL

START:
 LDA START

Здесь ассемблер сделает LDA с ZP-адресацией и ему без разницы, что, возможно, я затем захочу собрать этот фрагмент на адресе $4000. Ну ладно. Не бага, в общем-то. Скорее, изъян архитектуры транслятор-линкер.

Но дальше полезло другое: любую метку можно объявить через EQU или DSECT. С точки зрения ассемблера, это уже как бы константы, а не метки. Разница в том, что "PI10000 EQU 31415" не должна допускать никакой настройки линкером - как константа, она не зависит от смены адресов. И тут линкер поплыл по полной: он вообще игнорирует флаг EQU, который ему ставит ассемблер. И пытается пересчитывать константы.

Ну это я исправил.

А вот дальше - ещё хуже. Во внутренних структурах линкера, там где собирается глобальная таблица переменных, есть такая дурацкая схема: если старший байт адреса = 0, то вроде как переменная не найдена. И её значение остаётся нулем или чем-то ещё. Ну она может быть не найдена, проблем нет, но ведь может встретится переменная, у которой реально старший байт адреса = 0. Чем думали разработчики - не знаю, но факт: VT EQU $28 не импортируется в прогу, хотя успешно присутствует в таблицах экспорта библиотеки.

Будем ковырять дальше.

(что-то сдаётся мне, что этот линкер немного не был доведён до ума, от того и забыт всеми)

12 Отредактировано Voldemar0 (24-02-2018 14:12)

Re: LINKER - Cбopщик пepeмeщaeмыx пpoгpaмм

Вчера сходили с женой на концерт Би-2, сегодня покатал немного на веле (где-то км 20-25 всего), но давно не ездил за город, давно пора было. Вес растёт без нагрузок :)
И сегодня добил линкер. В аттаче образ, там пример его использования:

T  040  Koмпoнoвщик:pук.пpoгpaммиcтa
T  065  LINKER.TEKCT.2018A          
T  063  LINKER.TEKCT.2018B          
К  008  LINKER2018                  
T  064  LINKER.TEKCT.1988           
К  009  LINKER1988                  
T  011  IOSUB                       
П  002  IOSUB.KOД1                  
T  004  HW                          
П  001  HW.KOД1                     
T  001  ST                          
К  001  TI                          
П  001  XTI                       

 

IOSUB - это как бы простенькая перемещаемая библиотека. Там функции (JMPы на функции) IOSUB ДОК и пара процедурок - вывод ASCIZ-строки средствами IOSub.

HW - это пример проги, использующей эту библиотеку.

IOSUB и HW собираются обычным ассемблером. Тип выходного файла не имеет значения, так как ассемблер в любом случае соберёт их как П. Если вы компилировали что нибудь ассемблером ДОК, вы не найдёте почти никаких отличий в исходнике, кроме того, что обязательна ORG с любым адресом, больше $FF и следующая за ней команда REL.

После компиляции библиотеки и программы нужно "выйти в отладчик".
Файл ST содержит вызов линкера, параметры вызова, а также нейтрализует косяки ДОК. Запускаем ST:

[EXEC ST

Увидите несколько команд, затем приглашение ком-строки - всё, финальная программа слинкована.

Линкером создаются два файла: TI и XTI. Первая - это исполняемый в ДОК файл:

[RUN TI

Прога запустится, пройдёт примерно такой диалог:

>[RUN TI                        
Тест ввода строки:              
.VC202JZV                       
Эхо:                            
VC202JZV                        
Адрес начала этой               
программы: 4000                 
                                
>                               

Также создаётся файл XTI - это П-файл без кода, содержащий только таблицу STB - итоговый список экпортируемых меток. Я сделал его генерацию просто для примера. В дальнейшем добавлю в dos33c2 просмотрщик таких файлов. Будет выглядеть примерно так:

Load ptr = 4000
Import list (пока только безымянные, их нужно модифицировать согласно сдвига):
Names list (эти имена могут где-то пригодится и могут иметь значения):
[29]    406B [--] {--} exp CODE  IOS_ENTERSTRING
[29]    406E [--] {--} exp CODE  IOS_KEYIN
[29]    4071 [--] {--} exp CODE  IOS_KEYIN_KBD
[29]    4074 [--] {--} exp CODE  IOS_DIGOUT
[29]    4077 [--] {--} exp CODE  IOS_BYTEOUT
[29]    407A [--] {--} exp CODE  IOS_CR
[29]    407C [--] {--} exp CODE  IOS_COUT
[29]    4082 [--] {--} exp CODE  IOS_PRINT
[29]    40C7 [--] {--} exp CODE  IOS_PRINT_PTR
[29]    407F [--] {--} exp CODE  IOS_COUT_SCR
[08]    __E8 [--] {--} exp EQU/D IOS_PTR
[09]    0200 [--] {--} exp EQU/D IOS_STRBUF
[08]    __33 [--] {--} exp EQU/D IOS_PROMPT
[08]    __24 [--] {--} exp EQU/D IOS_HT
[08]    __25 [--] {--} exp EQU/D IOS_VT

Обратите внимание, что в исходнике HW нет ни одной константы, кроме выводимых строк и адреса в нулевой странице (IOS_PTR), который может использовать библиотека.

Пробуйте, расскажите, что получилось.

Post's attachments

Attachment icon linker097.zip 145.05 kb, 275 downloads since 2018-02-24