1

Тема: Ошибки в Бейсике-60

После багов в операторах TAB и POS решил поискать баги и в других операторах Бейсика, связанных с управлением курсором.
И нашел :) Поигравшись с аргументами оператора HTAB мне удалось завесить Бейсик.

Сразу скажу, что удалось мне это только на комбинации из ранней версии ALV DOS (которая свиристит при запуске) и Бейсика-60. Впоследствии выяснилось, что в этой версии ALV DOS просто сломан обработчик BRK. Так что дальше речь пойдет только про штатный DOS и Бейсик-60 (и немножко Бейсик-67).

Итак, загружаем Бейсик-60 и вводим следующие команды:

HTAB 200
HTAB 255

Интерпретатор вылетает в обработчик BRK. Баг есть и в режиме выполнения программы:

10  HTAB 200: HTAB 255

Пишем RUN, интерпретатор также вылетает в обработчик BRK.
В чем дело? Почему оператор HTAB дает такой эффект?

Разбираться я начал, как обычно, с исходников Бейсика Apple.
Оператор HTAB находится по адресу $F7E7. В Apple он работает так: сначала вычитается единица из аргумента. Если результат больше длины экранной строки (40), то выполняется цикл: из аргумента вычитается длина экранной строки, вызывается функция печати символа перевода строки и проверка повторяется. Когда значение делается меньше 40, оно записывается в ячейку $24 (горизонтальная позиция курсора). Грубо говоря, HTAB 201 вызовет 6 переводов на новую строку и выглядеть это будет так, будто нажали стрелку вправо 200 раз.

В Бейсик-60 функция переехала на адрес $F3CD. В тексте два исправления - длина экранной строки равна 32, а перед записью в ячейку $24 значение удваивается. (Поэтому в режиме Т64 курсор движется двойными шагами.)

F3CD    20 B3 E6    JSR    $E6B3    ; в X аргумент HTAB
F3D0    CA          DEX
F3D1    8A          TAX
F3D2    C9 20       CMP    #$20     ; начало цикла
F3D4    90 0A       BCC    $F3E0
F3D6    E9 20       SBC    #$20
F3D8    48          PHA
F3D9    20 F8 DA    JSR    $DAF8
F3DC    68          PLA
F3DD    4C A7 E7    JMP    $E7A7    ; ???
F3E0    0A          ASL
F3E1    85 24       STA    $24
F3E3    60          RTS

Но самое интересное - это адрес $F3DD. В Apple тут просто стоит переход на начало цикла. В Бейсик-60 это должен был быть адрес $F3D2. Но на деле переход происходит на адрес $E7A7. То есть в середину процедуры FADD, которая выполняет сложение двух чисел с плавающей точкой!

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

Что получилось? Если аргумент HTAB превышает 32, то управление улетает в процедуру FADD.
Для выполнения операций с плавающей точкой в нулевой странице выделено несколько аккумуляторов. Каждый аккумулятор - это временная переменная, содержащая 40-битное число с плавающей точкой. При вычислениях может понадобиться несколько таких переменных, поэтому у процедуры FADD первый аргумент лежит по фиксированному адресу, а второй аргумент задается регистром X. А когда из HTAB выполняется переход, то в регистре X у нас находится аргумент функции HTAB... Сюрприз!

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

Если кто не знает, кратко опишу устройство числа с плавающей точкой в Бейсике. Там есть две части - экспонента и мантисса. Экспонента - это первый байт числа, мантисса - это следующие 4 байта.
Представим, что у нас есть большое двоичное число с фиксированной точкой. Первые 127 бит - это целая часть, потом точка, а потом еще 159 бит дробной части. А теперь мы берем бумажку и вырезаем в ней окошечко, через которое видно только 32 бита. Это и будет мантисса. А экспонента - это просто число разрядов, на которое нужно сдвинуть это окошечко относительно точки, чтобы попасть на начало числа.

В результате у двух разных чисел мантиссы могут быть на разном расстоянии от точки. И чтобы сложить эти числа, нужно сначала сделать так, чтобы "окошечки" находились на одинаковом расстоянии от точки. Это и есть выравнивание мантисс.

Делается оно сдвигом мантиссы меньшего числа вправо. Величина сдвига равна разности экспонент и помещается в регистр Y. И вот, когда HTAB делает переход внутрь FADD, он заодно передает и величину сдвига. Очень немаленькую. Поэтому мантисса числа уезжает вправо так сильно, что полностью обнуляется.

В результате получаем такой прикольный эффект: когда аргумент HTAB превышает 32, то происходит обнуление 4 байт в нулевой странице, начиная с адреса, равного аргументу HTAB. Круто?

Спрашивается, а почему же интерпретатор падает в обработчик BRK?

Тут тоже интересная история. Вспомним, что у Apple объем памяти мог быть от 4 до 48 Кбайт. Бейсик должен был работать с любой конфигурацией. В результате его код написан так, чтобы использовать как можно меньше памяти. Но это привело к тормозам при выполнении программы.

Microsoft постаралась поднять производительность за счет оптимизации. Какие процедуры нужно оптимизировать в первую очередь? Те, которые вызываются чаще всего. Какая процедура используется чаще всего во время выполнения программы на Бейсике? Процедура получения следующего токена CHRGET. Вот ее и перенесли, в рамках оптимизации, в нулевую страницу. Адрес ее начала $B1.

Оптимизация, кстати, довольно заметная: если бы код был в ПЗУ, то пришлось бы использовать косвенную адресацию, а значит освобождать и перезагружать индексный регистр. В общем, сэкономили не меньше десятка тактов. При выполнении программы средней длины с циклами ускорение может составлять секунды.

Ну а оператор HTAB 200 успешно затер часть кода этой процедуры и поэтому при выполнении следующего за ним оператора интерпретатор упал.

***

Что интересно, бага сохранилась и в Бейсик-67. Полностью от нее избавились только в ИКП-7, причем самым суровым образом: цикл выпилили, а при превышении аргументом значения 32 (или 64 в режиме Т64) сделали переход на обработчик ошибок.

***

Напоследок вот несколько интересных эффектов, которые можно вызвать с помощью этого бага.

HTAB 33 - обнуляет переменные, в которых находится размер текстового окна. В результате размер окна делается 1x1 символ. Очень прикольно потом что-то вводить с клавиатуры :)

HTAB 54 - обнуляет стандартные векторы ввода-вывода ($36, $38). В результате попытка вывода на экран вызывает падение в обработчик BRK, который тоже падает, потому что ему тоже нужен вывод на экран. Со стороны это выглядит как зависание, лечится через сброс.

HTAB 104 : NEW - обнуляет адреса расположения текста программы на Бейсике. После этого оператор NEW пытается расположить программу по нулевому адресу, что полностью убивает Бейсик. После сброса экран будет отображать мусор.

2 Отредактировано Voldemar0 (14-02-2024 06:57)

Re: Ошибки в Бейсике-60

> Напоследок вот несколько интересных эффектов, которые можно вызвать с помощью этого бага.

Еггогология :))

Тебе с таким объёмом знаний уже пора делать справочник по архитектуре бейсиковских интерпретаторов Агата.

1) Общее описание архитектуры: чтобы можно было понять, в какой части структуры сидят разные процедуры. И, соответственно, как и на что может влиять каждая.

2) Справочник адресов, на примере какой нибудь распространённой версии интерпретатора (a'la "Под яблоней DOS").

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