1

Тема: ошибка в 6502

Приветствую форумчан ! Я "легендарный" Серков Сергей. Мой ник обычно везде теперь Sertik
Рад знакомству с новыми людьми и общению со старыми любителями АГАТа. Я решил "выйти из тени".
Надеюсь буду писать и участвовать в форуме.

Прочитал недавно случайно у гораздо более легендарного Боба Сандер-Сандерлофа о ошибке в процессоре 6502
Вот оригинал его сообщения от 1980 года

https://www.txbobsc.com/aal/1980/aal8010.html#6502bug

Hardware Error in ALL 6502 Chips!
Bob Sander-Cederlof
INTERFACE, the newsletter of Rockwell International (P. O. Box 3669, RC 55, Anaheim, CA 92803), Issue No. 2, is the source for the following information. It should be noted by all Apple owners working in assembly language, because it could cause an almost unfindable bug!

There is an error in the JUMP INDIRECT instruction of ALL 6500 family CPU chips, no matter where they were made. This means the error is present in ALL APPLES. This fatal error occurs only when the low byte of the indirect pointer location happens to be $FF, as in JMP ($08FF). Normally, the processor should fetch the low-order address byte from location $08FF, increment the program counter to $0900, and then fetch the high-order address byte from $0900. Instead, the high-order byte of the program counter never gets incremented! The high-order address byte gets loaded from $0800 instead of $0900! For this reason, your program should NEVER include an instruction of the type JMP ($xxFF).

Try this example to satisfy yourself that you understand the problem: insert the following data from the monitor.

      *800:09          the actual hi-byte used
      *810:6C FF 08    this is JMP ($08FF) [indirect through the address at $08FF]
      *8FF:50 0A       the lo-byte and the expected hi-byte jump address
      *A50:00          BRK instruction we SHOULD reach
      *950:00          BRK instruction we DO reach!
Execute the instruction at $0810 by typing 810G. If the JMP indirect worked correctly, it would branch to location $0A50 and execute the BRK instruction there. However, since the JMP indirect instruction has this serious flaw, it will actually branch to the BRK instruction at $0950!

Since it is very difficult to predict the final address of all pointers in a large assembly language program, unless they are all grouped in a block at the beginning of the program, I suggest that you take special measures to protect yourself against this hardware problem. (One measure, of course, was suggested in that sentence.) My favorite method is to avoid using the JMP indirect instruction. It takes too long to set it up in most cases anyway. I prefer to push the branch address (less one) onto the stack, and RTS to effect the branch. This allows me to create the effect of an indexed JMP. For example, suppose a command character is being decoded. I process it into a value in the A-register between 0 and N-1 (for N commands), and do the following:

     ASL                  Double to create index
     TAX                  for address table
     LDA JUMP.TABLE+1,X   High order byte
     PHA                  of branch address
     LDA JUMP.TABLE,X     Low order byte
     PHA                  of branch address
     RTS
The jump table looks like this:

     JUMP.TABLE
     .DA COMMANDA-1   The "-1" is
     .DA COMMANDB-1   on each line
     .DA COMMANDC-1   because the RTS
     .DA COMMANDD-1   adds one before
     et cetera        branching.
This trick was described by Steve Wozniak in an article in BYTE magazine back in 1977 or 1978. It is also used by him in the Apple monitor code, and in SWEET-16. In both of these cases, he has arranged all the command processors to be in the same page, so that the high order byte of the address can be loaded into the A-register with a load-A-immediate, and the jump table can be only one-byte-per-command. See your Apple ROMs at locations $FFBE-FFCB (jump table at $FFE3-FFF9) and in SWEET-16 at $F69E, F6A0, F684-F6B8 (jump table at $F6E3-F702).

You can extend this idea of an indexed JMP instruction into a simulated indexed JSR instruction. All you have to do is first push onto the stack the return address (less one), and then the branch address (less one). I use this trick in the Message.Printer program described elsewhere in this issue.

Программируя на Агате я никогда не сталкивался с подобной ошибкой кода процессора, но вероятность попадания в неё очень мала.
Сам я Сандерлофа не проверял. Исправила ли компания APPLE эту ошибку в процессоре не знаю.

Какие будут мнения ?

2 Отредактировано Voldemar0 (26-02-2025 06:45)

Re: ошибка в 6502

Мне доводилось видеть списки ошибок 6502, давно (до 2010, точнее уже не вспомнить). И про эту я тоже читал. У меня в эмуле этот баг повторён. Хотя, вообще, эта команда редко использовалась - она не сильно удобная. Если бы было что-то вроде JMP (1234,X)  - это бы была тема! Но такой нет :(

Сайт 6502.org возник давно уже и там таких документов немало. Например, про недокументированные команды есть.

Но тут вот вопрос: что считать ошибками, а что - нет.
В сообщении выше есть пометка:

"The "-1" is on each line because the RTS adds one before"

Это поведение известно всем, а сам трюк с комбинацией PHA/PHA/RTS используется в коде разных авторов частенько.
Но ошибка это или нет ?
Ведь комбинация JSR/RTS работает правильно, как и задумано.
Но мы знаем, что JSR оставляет на стеке адрес на единицу меньше, чем адрес следующей команды, а RTS, в свою очередь, эту единицу добавляет.
Я не видел оригинальной доки на систему команд 6502. Если там точно написано, что JSR сохраняет на стеке адрес следующий команды и выполняет переход, а про "-1" не упомянуто - то да, можно считать ошибкой. Если же формулировка попроще: "сохраняет адрес возврата", без деталей - то вроде как бы ошибки нет.

Что ещё вспомнить:
При X > 0 команда LDA ($FF,X) что будет читать ? нулевую страницу или зайдёт на первую ? Скорее всего останется в нулевой.
А команда LDA ($FF), Y откуда возьмём второй байт аргумента и что будет, если аргумент в сумме с Y  будет тоже будет пересекать границу страниц ?

Программисты старались не допускать в коде таких пограничных условий.

Ещё, тоже к ошибкам можно причислить, а можно и не делать этого:
поведение флага B. Оно определёно для команды BRK и прерывания IRQ. В одном случае он устанавливается, в другом - сбрасывается.
Но в реальности есть занятная штука: этот флаг часто устанавливается вообще едва ли не всеми командами. Его только PLP может сбросить, если на стеке слово состояние сохранено со сброшенным B.
LDA # 0 / PHA / PLP
В реальных обработчиках IRQ/BRK, хотя бы в сисмоне, первая же команда на входе в обработчик - PHP. Закинули слово состояния на стек и дальше флаг B из него никуда уже не денется.

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

Если у кого есть реальное железо - попробуйте.

3

Re: ошибка в 6502

Voldemar0 пишет:

Ведь комбинация JSR/RTS работает правильно, как и задумано.
Но мы знаем, что JSR оставляет на стеке адрес на единицу меньше, чем адрес следующей команды, а RTS, в свою очередь, эту единицу добавляет.
Я не видел оригинальной доки на систему команд 6502. Если там точно написано, что JSR сохраняет на стеке адрес следующий команды и выполняет переход, а про "-1" не упомянуто - то да, можно считать ошибкой.

Есть растактовки выполнения команд.
JSR выполняет сохранение счетчика команд (PCL, PCH) ДО того, как читает старший байт адреса подпрограммы. Соответственно, на стек попадает адрес на единицу меньше, чем адрес следующей за JSR команды.
Скорее всего, это связано с тем, что внутри 6502 нет временного регистра, в котором можно было бы запомнить все 16 бит адреса подпрограммы, перед тем, как начинать сохранение счетчика команд.

Spoiler

http://forum.agatcomp.ru//misc.php?action=pun_attachment&item=1333&download=1

А инструкция RTS после того, как прочитает со стека адрес возврата в (PCL, PCH), начинает стандартный цикл выполнения команд. Выполнение инструкций в 6502 частично перекрывается (цикл выборки новой инструкции начинает выполняться в последнем такте предыдущей). Видимо, первое действие - это как раз инкремент счетчика команд, и он выполняется в последнем такте RTS. В результате, к следующей инструкции счетчик команд уже увеличен на единицу.

Spoiler

http://forum.agatcomp.ru//misc.php?action=pun_attachment&item=1334&download=1

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

Post's attachments

Attachment icon 6502_JSR.jpg 44.46 kb, 47 downloads since 2025-02-26 

Attachment icon 6502_RTS.jpg 35.54 kb, 46 downloads since 2025-02-26 

4

Re: ошибка в 6502

А кто встречал недокументированные, НО ПОЛЕЗНЫЕ и РАБОТАЮЩИЕ команды 6502 ?

5 Отредактировано avivanov76 (28-02-2025 00:46)

Re: ошибка в 6502

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

Например, команда LAX - это гибрид LDA и LDX. Она одновременно открывает на запись регистры A и X, в результате они оба грузятся одним и тем же значением с шины данных.
А команда SAX - это гибрид STA и STX. Выходы регистров A и X одновременно подключаются к внутренней шине процессора и получается операция "монтажное И". Результат пишется в память. Вот только насколько хорошо сработает "монтажное И" зависит от качества транзисторов, температуры процессора и прочих фаз Луны.
Так что очень немногими командами можно реально пользоваться.

Из примеров можно вспомнить тест Лисина: там команда KIL - единственный способ сделать так, чтобы процессор не обращался в память, пока идет проверка регенерации ОЗУ.
Видел, как недокументированные команды используются для обфускации. Типа, непонятно как загружаются регистры, кладутся в стек и процессор потом по RTS улетает неведомо куда.
Слышал, что игроделы используют такие команды для быстрого вывода программных спрайтов.
Вот, пожалуй, и все.

6

Re: ошибка в 6502

Так в 6502 это какие реально команды то ? Например для обфускации кодa ? Или Вы это про другие процессоры ?

7

Re: ошибка в 6502

Нет, это все про 6502.
Пример обфускации я видел на Правце и это было 30+ лет назад, точно уже не помню. Вроде это была команда LAX.

Вообще, вот тут https://codebase64.org/lib/exe/fetch.ph … 202412.pdf есть даже примеры кода с такими инструкциями. Это правда для 6510, но большая часть инструкций совпадает с 6502.

8 Отредактировано Voldemar0 (28-02-2025 07:49)

Re: ошибка в 6502

Мой эмулятор останавливается на недокументированных командах и спрашивает, что с ними делать?
Он может их исполнить, но только если это разрешить.

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

Было два исключения:

1) У нас есть перепиленный на Агат Aztec-C - это компилятор языка C с дописанной к нему IDE. К сожалению, авторы этого чуда не описали с какими операционками смогут работать программы, сгенерированные этим транслятором. А мы-то сейчас знаем, что версий как Бейсика-60, так и ИКП-Бейсика много и отличия между ними иногда довольно заметные. Изучение коллекций показывает, что, похоже, те, к кому попадал этот Aztec-C с интересом с ним игрались, но ни одной законченной программы на нём написано не было.

Так вот в загрузке Aztec-C есть недокументированная команда ANL. Причём она используется только как обычный AND. И поэтому сложно сказать: была ли это ошибка или мелкое запланированное усложнение. Но эта особенность была на всех, встреченных мной, копиях Aztec-C.


2) Иногда встречается код загрузчика, забитый примерно 10-20 недокументированными командами, сложенными в одном месте. Этот загрузчик использовался в нескольких игротеках и, кажется, в версии FantaVision, адаптированной для Агата и 840к- дисков.

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

undoc cmd1
undoc cmd2
BNE L1
undoc cmd3
norm cmd4
BEQ L2
undoc cmd5
BPL L3
undoc cmd6
BMI L4

Если вы не знаете, сколько байт занимает каждая из недокументированных команд (а у нас 80-90е и инета ещё нет и сайта 6502.org тоже), то вы не можете даже правильно дизассемблировать такой код. И дело не в том, что конкретно делают эти команды, а в том какая часть этих байт кода является тем самым BPL L3, который ведёт на продолжение нормальной загрузки. Потому что все остальные переходы на L1, L2, L4 ведут в тупики. Переходов на L1, L2, L4 может даже вовсе не быть, важно лишь, чтобы вы не смогли опознать уход на L3.

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

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