1

Тема: Меню на РАПИРе

Вот есть меню обычное на бейсике, показывает на экране только защищенные файлы из каталога.
Имеет прокрутку по этим именам и запуск.


http://agatcomp.ru/agat/Software/MicroDos/UdoMenu/UDOMENU.png

А возможно подобное сделать на рапире? И какие могу быть варианты.

2 Отредактировано Voldemar0 (09-02-2022 18:47)

Re: Меню на РАПИРе

Сделать можно всё, но, мягко говоря, сама РАПИРа для этого не предназначена.

Обычно, когда на РАПИРе делали менюшку, она не читала список файлов, но, подобно всяким бейсик-Игротекам, имела готовый список того, что нужно запускать.

Т.е. чтобы такую (автоматически читающую каталог) прогу сделать, надо брать что нибудь вроде стандартной библиотечки ЧИТ_ЗАП и, на её основе, делать чтение каталога, его разбор, вывод на экран в  виде меню. Как-то так.

3

Re: Меню на РАПИРе

А у нас есть примеры менюшки "имела готовый список того, что нужно запускать" ?

4

Re: Меню на РАПИРе

аттач

Post's attachments

Attachment icon zap.rar 3.34 kb, 184 downloads since 2022-02-10 

5

Re: Меню на РАПИРе

Voldemar0 пишет:

Т.е. чтобы такую (автоматически читающую каталог) прогу сделать, надо брать что нибудь вроде стандартной библиотечки ЧИТ_ЗАП и, на её основе, делать чтение каталога, его разбор, вывод на экран в  виде меню. Как-то так.

А разве в Рапире не допускаются ассемблерные вставки?

6

Re: Меню на РАПИРе

нет
там даже нет команды аналогичной bload.
есть только команды чтения и записи в ячейку памяти по абсолютному адресу
ЧИТ-ЗАП как раз и пыталась решать эту проблему
я не помню что она делает, то ли что-то вроде bload, то ли отдельный сектор позволяет прочитать с диска

7

Re: Меню на РАПИРе

Меня вот это сбило:

(************************)
(*                      *)
(* MOДУЛЬ  << MУЗЫKA >> *)
(*                      *)
(*    ( C POЯЛEM )      *)
(*                      *)
(*                      *)
(************************)

ИMEHA:
       HOTKA ДOCTУПHO,
       ПOДГOTOBИTЬ_HOTУ,
       KЧИT,
       ЗAKPOЙCЯ,
       ПИAH ДOCTУПHO;

CTAPT: ПOДГOTOBИTЬ_HOTУ;
ФИHИШ: ЗAKPOЙCЯ;

ПPOЦ HOTKA(KAKAЯ,ДЛИHA);
  AДPЗ(980,KAKAЯ);
  AДPЗ(985,ДЛИHA);
  AДPBЫЗ(976);
KHЦ;

ПPOЦ ЗAKPOЙCЯ;  
  BЫKЛ ЗAЩИTУ:
    HOTA, POЯЛЬ;
  ПУCTO->HOTA;
  ПУCTO->POЯЛЬ
KHЦ;

ПPOЦ ПOДГOTOBИTЬ_HOTУ;
ИMEHA:A,AДP; 
  BЫKЛ ЗAЩИTУ:
    HOTA, POЯЛЬ;
  HOTKA->HOTA;
  ПИAH->POЯЛЬ;  
  BKЛ ЗAЩИTУ:
    HOTA, POЯЛЬ;
  KЧИT("MУЗЫKA.KOД",0);
  976->AДP;
  ДЛЯ A ИЗ
    <32,100,27,  (* JSR DOPOZU  *)
     169,00,     (* LDA #XX     *)
     141,01,190, (* STA $BE01   *)
     169,00,     (* LDA #XX     *)
     141,17,190, (* STA $BE11   *)
     32,00,190,  (* JSR $BE00   *)
     76,96,27>   (* JMP NORMOZU *)
  ::
    AДPЗ(AДP,A);
    AДP+1->AДP
  BCE
KHЦ;

(* ПPOГPAMMA ИЗ ФAЙЛA "ЧИT_ЗAП" *)

ПPOЦ KЧИT(ИMЯ,A);
ИMEHA:AH,AL,AP,NB,PROG,И;
  3*256+13*16-1->AP;
  3*256-1->NB;
  A//256->AH;A-AH*256->AL;
  ДЛЯ И OT 1 ДO 30::
    ECЛИ И>#ИMЯ TO
      AДPЗ(NB+И,160)
    ИHAЧE
      AДPЗ(NB+И,KOД(ИMЯ[И]))
    BCE
  BCE;
  <32,100,27,141,65,AДPЧ(6994),169,32,133,83,
   169,AH,133,153,169,AL,133,152,32,79,187,
   32,92,27,76,96,27>->PROG;
  ДЛЯ И OT 1 ДO #PROG::
    AДPЗ(AP+И,PROG[И])
  BCE;            
  AДPBЫЗ(AP+1)
KHЦ;

 ПPOЦ ПИAH;
  ИMEHA: Д;

  2->Д;
  

    ПOKA 1=1 ::    
    
      BЫБOP HAЖATO() ИЗ  
        ""  : !
        "" : BЫXOД !
        "Я" : HOTA(1 ,Д) !
        "Ч" : HOTA(2 ,Д) !
        "C" : HOTA(3 ,Д) !
        "M" : HOTA(4 ,Д) !
        "И" : HOTA(5 ,Д) !
        "T" : HOTA(6 ,Д) !
        "Ь" : HOTA(7 ,Д) !
        "Б" : HOTA(8 ,Д) !
        "Ю" : HOTA(9 ,Д) !
        "," : HOTA(10,Д) !
        "/" : HOTA(11,Д) !
        "Ъ" : HOTA(12,Д) !
        "Ф" : HOTA(13,Д) !
        "Ы" : HOTA(14,Д) !
        "B" : HOTA(15,Д) !
        "A" : HOTA(16,Д) !
        "П" : HOTA(17,Д) !
        "P" : HOTA(18,Д) !
        "O" : HOTA(19,Д) !
        "Л" : HOTA(20,Д) !
        "Д" : HOTA(21,Д) !
        "Ж" : HOTA(22,Д) !
        "Э" : HOTA(23,Д) !
        "." : HOTA(24,Д) !
        "Й" : HOTA(25,Д) !
        "Ц" : HOTA(26,Д) !
        "У" : HOTA(27,Д) !
        "K" : HOTA(28,Д) !
        "E" : HOTA(29,Д) !
        "H" : HOTA(30,Д) !
        "Г" : HOTA(31,Д) !
        "Ш" : HOTA(32,Д) !
        "Щ" : HOTA(33,Д) !
        "З" : HOTA(34,Д) !
        "X" : HOTA(35,Д) !
        ":" : HOTA(36,Д) !
        ";" : HOTA(37,Д) !
        "1" : HOTA(38,Д) !
        "2" : HOTA(39,Д) !
        "3" : HOTA(40,Д) !
        "4" : HOTA(41,Д) !
        "5" : HOTA(42,Д) !
        "6" : HOTA(43,Д) !
        "7" : HOTA(44,Д) !
        "8" : HOTA(45,Д) !
        "9" : HOTA(46,Д) !
        "0" : HOTA(47,Д) !
        "-" : HOTA(48,Д) !
        "" : 1->Д       !
        "" : 2->Д       !
        "" : 3->Д       !
        "" : 4->Д       !
        "" : 5->Д       !
        "" : 6->Д       !
        "" : 7->Д       !
        "" : 8->Д       !
        "" : 9->Д
      BCE
    BCE
KHЦ;

== EOF ==

8

Re: Меню на РАПИРе

Это даже не ассемблер, а машинные коды.

9 Отредактировано garnizon (13-02-2022 23:28)

Re: Меню на РАПИРе

Не вижу смысла делать поправки, того что и так понятно. Я же спрашивал про ассемблерные вставки, проводя аналогию как на бейсике. Здесь код, в комментариях команды, так и написал что это сбило.

А можно как специалиста по рапире попросить прислать пару версий меню?

10 Отредактировано garnizon (20-02-2022 23:46)

Re: Меню на РАПИРе

Дополнительный кошмар в том, что мало помню как писали на рапире и например даже простой запуск другого файла получается кривым.

В аттаче два диска, на них все одинаково кроме файла "ЗАПУСК".

На диске Ordinary.DSK файл ЗАПУСК совершенно стандартный, такой идет с любым дистрибутивом рапиры.
После загрузки набираем ЗАПУСК ЭКСКУРС; чем запускаем обучающий пакет, где кроме прочего, есть пункт "3. музыка", если его выбрать - можно убедитя что все работает.


На диске AUTOLOAD.DSK файл ЗАПУСК выглядит вот так:

ПPOЦ ЗAПУCK;
(* AGATCOMP *)
ЗAПУCK ЭKCKУPC;
KHЦ;

Пакет запускается автоматически, и вроде работает, но если выбрать пункт "3. музыка" - начинается фигня.
То ли, там проверяется версия рапиры, которая не указана в данном ЗАПУСК, то ли еще что....

Post's attachments

Attachment icon AUTOLOAD.DSK 840 kb, 172 downloads since 2022-02-20 

Attachment icon Ordinary.DSK 840 kb, 158 downloads since 2022-02-20 

ошменрап.png, 2.71 kb, 448 x 391
ошменрап.png 2.71 kb, 159 downloads since 2022-02-20 

11

Re: Меню на РАПИРе

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

Насколько я понял, запуск пункта меню "Музыка" делается иначе, чем остальных пунктов. Он делается через импорт модуля "МУЗЫКА". Видимо, в этом месте все и ломается, потому что у модуля есть процедура инициализации, которая должна загрузить бинарник с подпрограммой генерации звука и создать в памяти "вызывалку", которая включает правильную страницу ДопОЗУ, вызывает подпрограмму генерации звука и переключает ДопОЗУ обратно. Так вот, до этого дело не доходит, все падает раньше.

Почему так происходит - непонятно. Возможно, Рапире нужно перейти в интерактивный режим чтобы завершить инициализацию. Или закрыть файл автозапуска. А может, еще что-то сделать. Надо где-то найти исходники Рапиры и курить их.

12 Отредактировано Voldemar0 (24-02-2022 11:06)

Re: Меню на РАПИРе

А у меня AUTOLOAD при вызове музыки делает так:

ЭE951                           
                                
 BAM ЛУЧШE ПEPEBЫЗBATЬ CИCTEMУ  

Хм ?
Однако, надо будет создавать раздел "баги в Школьнице/РАПИРе".....

13 Отредактировано Prol (24-02-2022 13:17)

Re: Меню на РАПИРе

Думаю, что это не баги рапиры, а особенности музыки или ее запускалки. Когда игрушка затирает бейсик, это же не баги бейсика.

14 Отредактировано avivanov76 (24-02-2022 21:56)

Re: Меню на РАПИРе

Prol пишет:

Думаю, что это не баги рапиры, а особенности музыки или ее запускалки. Когда игрушка затирает бейсик, это же не баги бейсика.

Нет, тут другая ситуация. Если команду на запуск вводить вручную, то все работает. А вот когда эта же команда на запуск прописана в автозагрузке, то все падает.

Похоже, тут все-таки бага в Рапире. В тот момент, когда выполняется файл "ЗАПУСК", какие-то внутренние переменные Рапиры не проинициализированы и это приводит к ошибке.

Я сделал дампы первых 32 Кб памяти после вызова "МУЗЫКИ" при ручном и автоматическом запуске. Потом взял описание Цикозы и посмотрел, какие рабочие ячейки отличаются. Отличий много, но, к сожалению, у Цикозы очень мало написано про назначение этих переменных, да и описание, кажется, для Рапиры 1.2. Но можно наверняка сказать, что незакрытых файлов нет и бага относится не к ДОСу, а к интерпретатору.

Еще сравнил дампы до вызова "МУЗЫКИ" при ручном и автоматическом запуске. Отличий тоже много, я там только две вещи понял наверняка: отличается обработчик ошибки (ERRBRAN) и отличается состояние стека.

Короче, если после автозагрузки выйти в интерактивный режим, то он, похоже, что-то доинициализирует в Рапире. Чтобы понять, что конкретно происходит, надо дизассемблить Рапиру и разбираться в подробностях того, как она работает, а это 32 Кб кода.

15

Re: Меню на РАПИРе

Так как мне тогда поступить? Самое разумное кажется, выкладывать диски с стоковым файлом ЗАПУСК, и в текстовике рядом с диском указывать что писать для загрузки пакета. Ведь почему-то в таким режиме все работает.

16

Re: Меню на РАПИРе

Ну, наверно пока так.
Со временем, наверно, раскопаем в чем там дело, но это времени потребует.

17 Отредактировано Voldemar0 (27-02-2022 16:10)

Re: Меню на РАПИРе

Состояние ЭмПЗУ - $A0:

E940 -   A5 67 ..   "%г"    LDA   67 
E942 -   C9 40 ..   "I`"    CMP   #40
E944 -   90 09 ..   ".."    BCC   E94F       <== два перехода на BRK
E946 -   C9 C0 ..   "I@"    CMP   #C0
E948 -   B0 05 ..   "0."    BCS   E94F       <== два перехода на BRK
E94A -   A9 04 ..   ")."    LDA   #04
E94C -   85 BE ..   ".>"    STA   BE
E94E -   A0 00 ..   " ."    LDY   #00
E950 -   84 BF ..   ".?"    STY   BF
E952 -   A9 03 ..   ")."    LDA   #03
E954 -   85 71 ..   ".я"    STA   71

A940-  A5 67 C9 40 90 09 C9 C0  B0 05 A9 04 85 BE A0 00

--

Похоже на то, что $66-67 - какой-то вектор свободной памяти или текущего значения переменной или локального стека процедуры... что-то из динамического озу, в общем.

Процедура проверяет его выход или не выход за пределы какого-то диапазона.

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

Вариант 1: этого никогда не должно случиться, и проверка - что-то вроде assert().
Т.е. специальный сбой, чтобы показать что что-то пошло не так, хотя разработчики были уверены, что это невозможно.

Вариант 2: каким-то чудесным образом это именно ошибка в коде.
Возможно, имеет смысл сравнить этот участок с другими версиями РАПИРы.

Собственно, трасса сбоя:

E0FA-  A5 C0 ..    "%@"     LDA   C0
E0FC-  D0 6D ..    "Pм"     BNE   E16B
E0FE-  20 D4 E1    ".TА"    JSR   E1D4
E1D4-  A5 CA ..    "%J"     LDA   CA
E1D6-  F0 FB ..    "ПШ"     BEQ   E1D3
E1D8-  A5 01 ..    "%."     LDA   01
E1DA-  C9 04 ..    "I."     CMP   #04
E1DC-  D0 03 ..    "P."     BNE   E1E1
E1DE-  20 0A E8    "..Х"    JSR   E80A
E80A-  EA .. ..    "Й"      NOP       < --- интересно, зачем ?
E80B-  EA .. ..    "Й"      NOP   
E80C-  EA .. ..    "Й"      NOP   
E80D-  EA .. ..    "Й"      NOP   
E80E-  EA .. ..    "Й"      NOP   
E80F-  A9 00 ..    ")."     LDA   #00
E811-  85 76 ..    ".ж"     STA   76
E813-  A0 00 ..    " ."     LDY   #00
E815-  B1 60 ..    "1ю"     LDA   (60), Y
E817-  F0 68 ..    "Пх"     BEQ   E881
E819-  29 0F ..    ".."     AND   #0F
E81B-  C9 06 ..    "I."     CMP   #06
E81D-  30 2C ..    ".."     BMI   E84B
E84B-  20 19 F2    "..Р"    JSR   F219
F219-  A5 60 ..    "%ю"     LDA   60
F21B-  85 66 ..    ".ф"     STA   66
F21D-  A5 61 ..    "%а"     LDA   61
F21F-  85 67 ..    ".г"     STA   67
F221-  60 .. ..    "ю"      RTS   
E84E-  D0 50 ..    "Pp"     BNE   E8A0
E8A0-  20 40 E9    ".`И"    JSR   E940
E940-  A5 67 ..    "%г"     LDA   67
E942-  C9 40 ..    "I`"     CMP   #40
E944-  90 09 ..    ".."     BCC   E94F
E94F-  00 .. ..    "."      BRK                   <==

По этой трассе видно, что ошибочное значение 66-67 копируется из 60-61.
Кто бы знал, что это за вектора...

Этот сбой не происходит при ручном запуске ЭКСКУРС (Ordinary.DSK).

И ещё: в 140-кб РАПИРе те же особенности: наличие группы NOP и те же кривые BCS / BCC.

18

Re: Меню на РАПИРе

Хоть выяснить на данный момент удалось, прямо скажем, немного, но результатами поделюсь.


***
Сразу было понятно, что реверсить Рапиру легко не будет, но на деле все оказалось еще сложнее.

Первая сложность - это большой объем кода. ПсевдоПЗУ занято Рапирой полностью, все 32 Кб. Но кроме этого, есть еще ДОС и Редактор, которые живут во второй странице ДопОЗУ, а также блоки IOSUB, COMOZU и INITMEM, которые живут в основной памяти. Все это сильно между собой переплетено, так что реально объем кода, который надо дизассемблировать, примерно 44 Кб.

Очень ценно, что есть описание от Цикозы. Без него вообще пришлось бы блуждать в потемках. Но, как я и подозревал, оно от какой-то более ранней версии Рапиры. Часть символов не бьется с имеющимся кодом: где то вместо подпрограмм лежат данные, где-то адреса "съехали" и адреса, указанные в таблицах у Цикозы попадают в середину инструкций. Ну и проблема в том, что реально в коде меток раз в пять больше, чем в тексте Цикозы. Отдельно надо отметить "говорящие имена" переменных вроде DL, N1 или AS. Пользы от таких имен не больше, чем от абсолютных адресов :)

Отдельная засада - это переключение банков памяти. Основная часть Рапиры живет в ПсевдоПЗУ, которое доступно по адресам $D000 - $FFFF. Но это всего 12 Кб, а кода 32 Кб. Конечно, есть большие блоки кода, вроде синтаксического анализатора и транслятора, которые не используются одновременно, друг к другу не обращаются, и потому могут относительно спокойно жить на своих страницах ПсевдоПЗУ. Но есть масса служебных функций, которыми они пользуются, и эти функции раскиданы по всему ПсевдоПЗУ. Угадайте, как они вызываются?

Есть группа подпрограмм в основном ОЗУ с именами LC0D0, LC0D1, LC1D0, LC1D1. Они включают нужную страницу ПсевдоПЗУ, а также нужный 4 Кб блок по адресам $D000 - $DFFF. На каждой странице ПсевдоПЗУ по адресам $FF00 - $FFF9 находятся подпрограммы вот такого вида:

JPRINTERR
        JSR    LC1D0    ; уходим отсюда
        JSR    EXP2
        JMP    LC1D0

Когда Рапире нужно вызвать процедуру PRINTERR, находящуюся на другой странице ПсевдоПЗУ, она вызывает подпрограмму JPRINTERR. Эта подпрограмма вызывает подпрограмму LC1D0, которая включает нужную страницу и возвращает управление. Вот только страница ПсевдоПЗУ то уже другая, поэтому возврат происходит в другое место:

JEXP2
        JSR    LC0D0
        JSR    PRINTERR    ; возвращаемся сюда
        JMP    LC0D0

То есть, дальше будет выполнен не вызов JSR EXP2, а как раз JSR PRINTERR. После чего будет вызвана подпрограмма LC0D0, которая вернет исходную страницу и управление вернется тому куску кода, который вызывал JPRINTERR.

Тот же кусок кода из первого фрагмента, который вызывает JSR EXP2, это ответная часть для аналогичной процедуры из другой страницы ПсевдоПЗУ (JEXP2).

Проблема тут не только в том, что код странно выглядит и надо соображать, куда реально передается управление, но и в том, что заранее неизвестно, какой из 4-х блоков будет подключен на адреса $D000 - $DFFF. Поэтому у дизассемблера сносит крышу, если скормить ему сразу 12 Кб кода - обязательно оказывается, что часть меток идет на другой блок, а в текущем блоке на их месте или данные или середина инструкции.

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

Отдельный вопрос, каких сил стоило разработчикам Рапиры скомпилировать все это изначально. Ясно, что было минимум 6 исходных файлов: 4 блока по 4 Кб, размещающихся по адресам $D000 - $DFFF, и два блока - по адресам $E000 - $FFFF. Может, нарезка была и более мелкая, но меньше, чем 6 файлов быть не могло. И все эти файлы должны были быть аккуратно провязаны по входным точкам. Любое изменение кода в одном файле, которое меняет адрес входной точки, требует, чтобы новый адрес этой входной точки был внесен во все остальные файлы, которые на него ссылаются. И, судя по всему, внесен руками, потому что штатный ассемблер тут мало чем может помочь. Он ни такой объем исходников не переварит, ни в метках из разных страниц не разберется, ни несколько фрагментов кода с одинаковым начальным адресом не соберет.


***
Ну, это так, присказка была.

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

Рапира в своей основе - это интерпретатор, как Бейсик или эппловый UCSD Pascal. Она берет исходный текст программы и переводит его в язык виртуальной машины (ВМ). Проще говоря, в токены. Можно считать, что у нас есть такой супер-пупер процессор, который может одной инструкцией складывать числа с плавающей точкой, присваивать значения строкам, открывать файлы и т.д. Каждая инструкция ВМ - один байт.

6502 - это конечно не супер-пупер процессор, выполнять инструкции ВМ напрямую он не может, поэтому он их эмулирует. Для этого в Рапире есть такая штука как INTERCOM. Не путать с переговорным устройством! На деле, это сокращение слов "Интерпретатор команд" :)

INTERCOM берет очередной байт (он же - инструкция ВМ), умножает его на два и использует как индекс к таблице TAIP. В этой таблице лежит указатель на первое описание инструкции. Почему пишу первое, потому что у одной инструкции может быть несколько описаний.

Каждое описание содержит 4 байта. Первые два байта описывают число аргументов инструкции (не больше трех) и их типы. Следующие два байта - это адрес подпрограммы, которая будет фактически выполнять инструкцию ВМ. Интересно, что в Рапире есть перегрузка инструкций, то есть, предусмотрено несколько вариантов кода для разных типов аргументов. INTERCOM сравнивает типы аргументов с описанием инструкции и если они не совпадают, он берет следующее описание инструкции и пробует его. Если больше описаний нет, а аргументы нельзя привести к одному из допустимых типов, то возникает ошибка "НЕДОПУСТИМЫЕ ОПЕРАНДЫ".

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

Если на стек надо положить что-то большое, например, кортеж, строку или целое число (напомню, если кто не знает, что в Рапире используется "длинная" арифметика, поэтому целое число может занимать до 128 байт), то на стек кладется только ссылка. Сам объект при этом лежит в "куче".

"Куча" - это, собственно, рабочая память Рапиры с адреса $4000 по $BFFF. Всё, включая переменные, исходный текст программы, оттранслированный код для ВМ, лежит в "куче".

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


***
Так, исходных данных достаточно, теперь можно переходить к изучению сбоя.

Ясно, что мы имеем дело с обращением к неинициализированной памяти, потому что сбой в эмуляторе и на реальной машине проявляются по разному. Эмулятор падает с сообщением "НЕДОПУСТИМЫЕ ОПЕРАНДЫ ">" В "МУЗЫКА" В СТРОКЕ 2 "МУЗЫКА"", а на реальной машине происходит системный сбой.

Вот трасса сбоя с именами:

        JSR    LC0D0
        JSR    EXECINSTR
        LDA    ERROR        ; проверяем, была ли ошибка выполнения инструкции
        BNE    SEMBRAN
        JSR    FREEARGS    ; ошибки не было, освобождаем аргументы


FREEARGS
        LDA    LIBARG        ; байт, содержащий число аргументов и тип первого аргумента
        BEQ    CODE_E1D3    ; если ноль, то аргументов нет
        LDA    TGS1        ; тег первого аргумента
        CMP    #$04        ; если в нем 4, освобождаем аргумент
        BNE    CODE_E1E1
        JSR    DELETE        ; да, в теге 4

Ну вроде все должно быть понятно - выполнили инструкцию, надо освободить первый аргумент.

DELETE
        NOP            ; здесь, скорее всего, были отладочные команды
        NOP            ; наверняка разработчикам не хотелось менять адрес вызова
        NOP            ; чтобы не пересобирать отлаженный код
        NOP
        NOP
        LDA    #$00
        STA    NESTING_DEPTH

CODE_E813
        LDY    #$00
        LDA    (ADR1), Y    ; адрес первого аргумента лежит в ADR1
        BEQ    CODE_E881
        AND    #$0F        ; младшие 4 бита - тип аргумента
        CMP    #$06
        BMI    CODE_E84B    ; меньше 6, значит, текст или число
...

CODE_E84B
        JSR    ADR14        ; копирует ADR1 в ADR4
        BNE    CODE_E8A0
...

CODE_E8A0
        JSR    SUP2        ; освобождение блока памяти по адресу ADR4
...

SUP2
        LDA    ADR4 + 1
        CMP    #$40
        BCC    ADRASSERT    ; адрес меньше $4000
        CMP    #$C0
        BCS    ADRASSERT    ; адрес больше или равен $C000
        LDA    #$04
        STA    ALLOC_BLOCK_SIZE
        .byte    $A0    ; LDY    #$00

ADRASSERT
        BRK
...

Тут уже не так понятно, но тоже не сложно. На входе адрес аргумента в ADR1. Так как это простой тип (текст или число), то его адрес просто передается подпрограмме SUP2. Она ждет аргумент в ADR4.
Отдельно отмечу любовь разработчиков к звучным именам. SUP2 - это не про еду. Это "Система Управления Памятью, вызов 2". Или free по-нашенски, по C-шному :) A SUP1 - это malloc :)

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

В эмуляторе падение происходит на другом этапе - перед выполнением инструкции ВМ, на стадии проверки аргументов. Но у обоих падений есть общее: на программном стеке вместо аргументов находится какой-то мусор.


***
Проблема в том, что настоящая ошибка, в результате которой этот мусор на стеке оказался, произошла намного раньше. Сообщение Рапиры об ошибке - это сообщение о дальних последствиях, оно сильно отстает по времени от того момента, когда происходит настоящая ошибка.

Пока мне удалось заметить вот что:
1) при сбое адрес потока инструкций ВМ намного больше, чем при нормальном выполнении. То есть, либо трансляция исходного текста идет в какие-то странные адреса, либо со странного адреса начинается выполнение
2) повреждается таблица управления памятью

Рапира для управления памятью использует "алгоритм близнецов". Фишка этого алгоритма - выделение памяти блоками, размер которых всегда равен степени двойки. 4, 8, 16 байт и т.д. Хочет программа 9 Кб, получает 16 Кб. Не слишком экономный алгоритм, но зато быстрый.

Для управления выделением памяти используется таблица ASP (что-то там "системы памяти"; что значит A - я не понял). Она содержит пары указателей на первый и последний занятые блоки памяти определенного размера. Так вот, при сбое первые элементы этой таблицы, которые отвечают за выделение блоков размером 4 и 8 байт содержат в указателях $FFFF, чего в норме быть никак не может (пустой указатель должен быть равен 0).

Очень похоже, что появление кривых адресов связано с повреждением системы управления памятью. Короче, ломается что-то в Рапире нешуточно. Беда только в том, что место возникновения настоящей ошибки может быть примерно где угодно. Сколько времени уйдет на его поиск - фиг его знает.

Но будем надеяться, что продолжение последует :)

19

Re: Меню на РАПИРе

Вот это квест! Оторваться не мог от текста, спасибо!

20

Re: Меню на РАПИРе

Всё таки это assert ! :))
Я угадал :))

--

Интересно это всё :)

Напомню, что если нужно найти кто что ломает по какому-то адресу: я могу отследить модификацию какого нибудь конкретного адреса.

--

Про метод близнецов:
в 90-е мне достался файлик такой:

;  SUP : 02.12.85
;
; УПPABЛEHИE BЫДEЛEHИEM И OCBOБOЖДEHИEM БЛOKOB
; B OБЩEM ПУЛE ДИHAMИЧECKOЙ ПAMЯTИ
;  (METOД БЛИЗHУEЦOB)
;

и тогда ломал голову над тем, для  чего это было нужно и кто такой этот мистер БЛИЗHУEЦOB ?
Или тут просто ошибка и всё таки это от "близнец" ?

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

--

sup1 == malloc - тоже любопытное наблюдение.
Я считал, что группа Цикозы - это были весьма опытные программисты, но ведь malloc - вызов очень старый, он и 80-е годы существовал. Получается, что авторы РАПИРы не знали о нём, а значит, скорее всего, может быть и работали с ассемблерами (пусть и на разных системах), но явно не прикасались к теме C- программирования. Иначе бы, скорее всего, брали оттуда имена.

21

Re: Меню на РАПИРе

Voldemar0 пишет:

Напомню, что если нужно найти кто что ломает по какому-то адресу: я могу отследить модификацию какого нибудь конкретного адреса.

Это бы сэкономило мне несколько недель раскопок :)
Только мне надо отследить не просто запись в адрес (их там будет дофига), а запись конкретного байта.
Мне нужно отловить момент записи байта $FF в адрес $329.

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

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

Да, и к трассе нужен будет дамп первых 48 Кб. Просто бинарник.

Voldemar0 пишет:

Я считал, что группа Цикозы - это были весьма опытные программисты, но ведь malloc - вызов очень старый, он и 80-е годы существовал. Получается, что авторы РАПИРы не знали о нём, а значит, скорее всего, может быть и работали с ассемблерами (пусть и на разных системах), но явно не прикасались к теме C- программирования.

Я находил в архиве Ершова документ, согласно которому первая версия Рапиры появилась в 1978-1979 годах. И похоже, что она была на БЭСМ-6. Так что в группе Цикозы наверняка были люди, пришедшие с мейнфреймов. А там C приживался неохотно, потому что было других языков.

22

Re: Меню на РАПИРе

На всяк случай, для однозначности, и чтобы память освежить: с каким образом мы работаем?

23

Re: Меню на РАПИРе

Voldemar0 пишет:

На всяк случай, для однозначности, и чтобы память освежить: с каким образом мы работаем?

Сообщение №10 в этой теме, архитектура Агат-7, файл AUTOLOAD.DSK.

Да, я забыл еще такой момент, что бомба попадает точно в эпицентр только в эмуляторе :) На эмуляторе всегда одинаковое начальное состояние памяти. А вот воспроизведется ли запись байта $FF на реальной машине - это большой вопрос.
Можно ли поставить условие, чтобы ловился байт > $C0?

24

Re: Меню на РАПИРе

>  байт > $C0

такого не обнаружено
даже когда всё упало там было A3

были две подозрительные записи значения 01, состояние с ними в аттаче
Обычно же писались Bx и Ax.

Сильно удлиннить трассу сложно, это надо как-то эмулятор перабатывать, поскольку трасса выводится на экран, сколько там вошло столько и могу скопировать, скролинга нет.

Но если сильно понадобится, надо будет что нибудь придумать...

Post's attachments

Attachment icon inf0.rar 31.96 kb, 160 downloads since 2022-05-22 

25

Re: Меню на РАПИРе

Voldemar0 пишет:

такого не обнаружено
даже когда всё упало там было A3

Нда, похоже все устроено еще более сложно :) Я думал, что повреждается таблица управления памятью и из-за этого все падает. А получается, что процессы идут параллельно. Что-то ломается, и заодно портится таблица управления памятью, причем не всегда.

Но в момент записи значения 01 таблица выглядит поврежденной примерно так же, как и в эмуляторе.

Трасса, конечно, короткая, но ничего, есть указатель стека и дамп и можно "размотать" цепочку вызовов.

Ладно, спасибо за помощь, буду думать.