CODING
Интересный текст от Ивана Рощина для коде-
ров. Пожелания, предложения, возражения,
отправляйте на мой адрес: 2:5020/689.53
20 ноября 98 Alex Letaev
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(c) Иван Рощин, Москва, 13.11.1998
Fido : 2:5020/689.53
E-mail: asder_ffc@softhome.net
+---------------------------------+
| Недокументированная особенность |
| процессора Z80 |
+---------------------------------+
Разрешается свободное распространение этой
статьи при условии не внесения каких-либо
изменений и сохранения моего копирайта.
1. С чего все началось
----------------------
Писал я как-то очередную версию программы
BestView (v2.4), и использовал в ней вот
такой фрагмент:
....
EI
CALL SUBR1
HALT
....
SUBR1 LD A,R
PUSH AF
DI
....
POP AF
DI
RET РО
EI
RET
В этом фрагменте происходит вызов процеду-
ры SUBR1, которая на время своей работы
запрещает прерывания, а при выходе восста-
навливает прежний режим их работы.
Проверка того, разрешены или запрещены
прерывания при вызове процедуры, и восста-
новление режима прерываний происходит сле-
дующим образом:
-команда LD A,R заносит во флаг Р/V сос-
тояние триггера прерываний IFF2;
-регистровая пара AF запоминается в стеке
(PUSH AF);
-запрещаются прерывания (DI);
-выполняются, собственно, те функции, для
кот. и предназначалась процедура SUBR1;
-содержимое AF восстанавливается (POP AF);
-прерывания запрещаются (DI);
-если флаг Р/V сброшен, происходит выход
из процедуры с запрещенными прерываниями
(RET РО);
-иначе происходит выход с разрешенными
прерываниями (EI: RET).
Я стал замечать, что при работе этого
фрагмента BestView зависает - не всегда, и
даже не слишком часто, а в очень редких
случаях. Но все равно это было не очень-то
приятно. Программа, вроде бы, не содержала
никаких ошибок, по крайней мере с первого
взгляда ничего подозрительного я не заме-
тил. Оставалось лишь прибегнуть к более
сильным средствам...
2. Ситуация начинает проясняться
--------------------------------
После очередного зависания я вставил чис-
тый диск и уверенно нажал кнопку MAGIC.
Затем загрузил отладчик "STS 6.2 +@" (не
зря я его переделывал - теперь с его по-
мощью после загрузки @-файла можно восста-
новить содержимое регистров процессора на
момент сброса программы на диск). Нажатие
пары клавиш - и вот я вижу, в каком месте
программы произошло зависание.
....
EI
CALL SUBR1
HALT <------------ вот здесь!
....
Типичный случай - прерывания запрещены, и
процессор прекратил выполнение программы
на команде HALT. Но почему прерывания ока-
зались запрещены - неясно. Ведь перед вы-
зовом процедуры SUBR1 они были разрешены
командой EI, а после окончания работы
SUBR1 они тоже должны быть разрешены -
процедура SUBR1 не должна оказывать влия-
ния на режим их работы.
Трассирую SUBR1. Все идет как положено - и
при входе, и при выходе прерывания остают-
ся разрешенными. Повторяю трассировку:
раз, другой,.. десятый. Все идет нормаль-
но.
А может быть, дело в том, что в SUBR1
что-то происходит со стеком? И из-за этого
иногда неправильно восстанавливается со-
держимое AF? Надо бы проверить...
3. Зависания: дубль второй
--------------------------
Ну вот, переделал программу. Теперь уж
точно буду знать, в чем дело:
SUBR1 LD A,R
PUSH AF
DI
PUSH HL
PUSH AF
POP HL
LD (WR_НН1),HL
POP HL
....
POP AF
PUSH HL
PUSH AF
POP HL
LD (WR_НН2),HL
POP HL
DI
RET РО
EI
RET
WR_НН1 DW 0
WR_НН2 DW 0
Содержимое AF теперь запоминается не толь-
ко в стеке, но и в переменной WR_НН1 (для
контроля), а при выходе из процедуры -
снятое со стека значение запоминается в
WR_НН2. Если процедура работает правильно,
WR_НН1 и WR_НН2 должны совпадать, а флаг
Р/V быть установленным.
Запускаю... Вот уже минуту BestView рабо-
тает нормально... Просматриваю с ее по-
мощью тот самый файл, при просмотре кото-
рого она зависла в прошлый раз... Ну вот,
опять! Да и неудивительно, ведь причину я
не устранил. Ладно, будем разбираться.
Снова нажимаю MAGIC, загружаю "STS 6.2 +@"
и сразу же проверяю значения WR_НН1 и
WR_НН2. И там, и там записано #5908. Зна-
чения совпадают - следовательно, при рабо-
те со стеком ошибок не было. Но если в ре-
гистре флагов содержится #08 - значит,
флаг Р/V сброшен, и при вызове процедуры
SUBR1 прерывания были запрещены.
Но это же совершенно невозможно! Ведь в
программе стоит EI: CALL SUBR1! Наверное,
просто Спектрум перегрелся, и потому такие
глюки. Ничего более умного я в этот день
так и не придумал.
4. Ложный след
--------------
На следующий день я нашел возможное объяс-
нение таинственному запрещению прерываний.
Допустим, после команды EI, но до выполне-
ния команды LD A,R произошло прерывание.
Как известно, процедура его обработки дол-
жна заканчиваться командами EI:RET (потому
что в начале обработки происходит автома-
тическое запрещение прерываний). Если же
обработчик прерываний завершается просто
командой RET, то прерывания останутся зап-
рещенными.
Конечно, вероятность того, что прерывание
произойдет именно между командами EI и LD
A,R очень мала, но ведь и зависания проис-
ходят очень редко. Так что это лишний раз
подтверждало мою гипотезу.
Тем не менее, оставалось неясным, с чего
бы это обработчик прерываний завершался
командой RET, а не EI:RET. Я решил прове-
рить, действительно ли в этом все дело, и
для этого добавил после EI команду HALT
(см. ниже). Если обработчик прерываний
действительно завершается некорректно, то
после добавленного HALT-а прерывания всег-
да будут запрещены, и, соответственно,
BestView всегда будет зависать.
....
EI
HALT <------ добавленная команда
CALL SUBR1
HALT
....
SUBR1 LD A,R
PUSH AF
DI
....
POP AF
DI
RET РО
EI
RET
Компилирую, запускаю... Совершенно неожи-
данный результат - зависания полностью
прекратились! Хотел все так и оставить, но
все же решил разобраться, почему так полу-
чается.
5. Прием "упрощение программы"
------------------------------
Когда найти ошибку обычными средствами не
удается, я удаляю из программы все что
можно, но чтобы ошибка при этом остава-
лась. В итоге, когда от программы остается
с десяток строк, ошибка заметна сразу. Так
я поступил и в этот раз:
ORG #6000
EI
M1 CALL SUBR1
JR M1
SUBR1 LD A,R
DI
JP РО,M2
EI
RET
M2 LD A,4
OUT (254),A
RET
Вот такая программа, всего 19 байт. Разре-
шаются прерывания, и в бесконечном цикле
вызывается процедура SUBR1. Эта процедура
устанавливает зеленый border, если при
входе в нее прерывания были запрещены, и
не меняет цвет border, если прерывания
разрешены. Таким образом, если произойдет
самопроизвольное запрещение прерываний,
это сразу же будет заметно.
Запускаю - да, border меняет свой цвет на
зеленый. Причина столь странного поведения
программы остается неизвестной. Может
быть, в этом виновата процедура обработки
прерываний 1-го рода? Добавляю к программе
несколько команд, устанавливающих режим IM
2 с обработчиком, состоящим всего из двух
команд: EI:RET.
ORG #6000
LD HL,#8000
LD (HL),#81
LD DE,#8001
LD ВС,#100
LDIR
LD A,#80
LD I,A
IM 2
EI
M1 CALL SUBR1
JR M1
SUBR1 LD A,R
DI
JP РО,M2
EI
RET
M2 LD A,4
OUT (254),A
RET
ORG #8181
EI
RET
Запускаю - тот же результат! Хотя при
трассировке и этой, и предыдущей программы
в отладчике border остается черным. Внима-
тельное изучение программы приводит к
предположению: может быть, команда LD A,R
иногда устанавливает бит Р/V так, как буд-
то прерывания запрещены, в то время как на
самом деле они разрешены?
6. Неужели ошибка в процессоре?
-------------------------------
Еще раз изменяю программу. Теперь прерыва-
ния вообще не будут запрещаться (убрана
команда DI). Если при выполнении команды
LD A,R бит Р/V станет равным 0, на некото-
рое время border станет зеленым (для этого
предусмотрена задержка):
ORG #6000
LD HL,#8000
LD (HL),#81
LD DE,#8001
LD ВС,#100
LDIR
LD A,#80
LD I,A
IM 2
EI
M1 CALL SUBR1
JR M1
SUBR1 LD A,R
RET РЕ
LD A,4
OUT (254),A
LD HL,0
LD DE,0
LD ВС,#600
LDIR ;WAIT
XOR A
OUT (254),A
RET
ORG #8181
EI
RET
Запускаю... И что я вижу? Верхняя часть
border-а мигает зеленым цветом:
Это говорит о том, что, во-первых, команда
LD A,R действительно иногда неверно уста-
навливает бит Р/V, и во-вторых - что это
происходит в момент прихода прерывания, а
не когда угодно (действительно, тогда бы
border мигал зеленым в совершенно произ-
вольных местах).
То, что верхняя часть border-а мигает, а
не постоянно окрашена в зеленый цвет, тоже
получает свое объяснение. По-видимому, ко-
манда LD A,R неправильно работает лишь
тогда, когда импульс прерывания приходит
во время ее выполнения, а так бывает дале-
ко не всегда - прерывание может произойти
и во время выполнения другой команды.
7. Окончательное подтверждение
------------------------------
Проверим этот факт. Пусть обработчик пре-
рывания определяет, в каком месте была
прервана программа. Если она была прервана
именно после команды LD A,R, пусть бордюр
на некоторое время станет желтым:
ORG #6000
LD HL,#8000
LD (HL),#81
LD DE,#8001
LD ВС,#100
LDIR
LD A,#80
LD I,A
IM 2
EI
M1 CALL SUBR1
JR M1
SUBR1 LD A,R
ВР1 RET РЕ
LD A,4
OUT (254),A
LD HL,0
LD DE,0
LD ВС,#600
LDIR ;WAIT
XOR A
OUT (254),A
RET
ORG #8181
EXX
EX AF,AF'
POP HL
PUSH HL
LD DE,ВР1
AND A
SBC HL,DE
JR NZ,NE_ВР1
LD A,6
OUT (254),A
LD HL,0
LD DE,0
LD ВС,#600
LDIR ;WAIT
NE_ВР1 EXX
EX AF,AF'
EI
RET
Если неправильная работа команды LD A,R не
связана с тем, что во время ее выполнения
приходит импульс прерывания, то мы увидим,
как верхняя часть border-а будет мигать то
зеленым цветом, то желтым. Но если связь
между этими двумя событиями существует, то
мы должны увидеть, как верхняя часть
border-а мигает желтым цветом, а нижняя
часть - зеленым, причем они должны мигать
совершенно синхронно.
Запускаю - и вижу именно то, что и предпо-
лагал. Действительно, такая связь сущест-
вует:
Но как могут быть связаны прерывания и ра-
бота команды LD A,R? Эта команда помещает
во флаг Р/V содержимое триггера прерываний
IFF2. При разрешенных прерываниях этот
триггер равен 1, а когда приходит импульс
прерывания, он автоматически сбрасывается
в 0, чтобы исключить повторную обработку
прерывания. Но обработка запроса на преры-
вание начинается во время выполнения пос-
леднего такта выполняемой команды (т.е.
команды LD A,R). И, видимо, уже сброшенный
триггер IFF2 копируется во флаг Р/V (дей-
ствительно, с точки зрения процессора,
прерывания в этот момент уже запрещены).
Все сказанное относится и к команде LD
A,I. Приведенная информация была проверена
на оригинальном процессоре Z80 фирмы ZILOG
и на отечественном аналоге КР1858ВМ1.
8. К чему это приводит и что делать?
------------------------------------
Применение команд LD A,R и LD A,I для оп-
ределения состояния триггера прерываний,
вообще говоря, используется во многих про-
граммах (и даже в ПЗУ TR-DOS). Вот вам и
объяснение некоторого числа странных зави-
саний. Кажется, будто вероятность прихода
импульса прерывания именно во время выпол-
нения команды LD A,R невелика. Но, во-пер-
вых, вероятность увеличивается за счет то-
го, что эта команда может выполняться в
программе не один раз (а для зависания
достаточно единственного неверного выпол-
нения), и, во-вторых - если в программе до
этого встречалась команда HALT, т.е. син-
хронизация с прерываниями, может случиться
так, что команда LD A,R будет каждый раз
выполняться в то время, когда наиболее ве-
роятно очередное прерывание (так и было в
BestView).
Итак, этот способ ненадежен. Но как же
быть? Оказывается, можно со 100% точностью
определять состояние триггера прерываний
по следующему простому правилу:
-выполнить команду LD A,R;
-если флаг Р/V = 1 - значит, прерывания в
самом деле разрешены;
-если флаг Р/V = 0 - либо прерывания в са-
мом деле запрещены, либо они разрешены,
но команда LD A,R неверно установила
флаг. Чтобы устранить неопределенность,
еще раз выполняем команду LD A,R. Если и
теперь флаг Р/V = 0 - значит, прерывания
запрещены (в самом деле, не может быть
так, чтобы и во время выполнения второй
команды LD A,R произошло прерывание -
между двумя прерываниями проходит 1/50
секунды, а между двумя командами LD A,R -
гораздо меньше времени). Если же флаг Р/V
= 1 - значит, прерывания разрешены.
Вот соответствующий фрагмент программы:
SUBR1 LD A,R
JP РЕ,M1
LD A,R
M1 PUSH AF
DI
....
POP AF
DI
RET РО
EI
RET
9. Как это можно использовать?
------------------------------
С помощью команды LD A,R удобно выполнять
тестирование процессора, чтобы распознать
выполнение программы под эмулятором. Эму-
лятор выполняет команды Z80 последователь-
но, одну за другой, и команда LD A,R всег-
да будет правильно устанавливать флаг Р/V.
А в реальном Z80 это не так.
Вот простейшая процедура для тестирования
процессора, которая возвращает в аккумуля-
торе 1, если она запущена на реальном Z80,
и 0 в противном случае. Она пытается 65536
раз прочитать регистр R при разрешенных
прерываниях, и если при этом хотя бы один
раз флаг Р/V установится в 0 - делается
вывод, что процедура работает на реальном
Z80.
TESTZ80 EI
LD ВС,0
M1 LD A,R
JP РО,QUIT
INC ВС
LD A,В
OR С
JR NZ,M1
RET
QUIT LD A,1
RET
Если распознался эмулятор, можно либо
прекращать выполнение программы (своеоб-
разная защита), либо отключать некоторые
участки программы, которые могут неверно
работать под эмулятором (например, вместо
прямой работы с ВГ93 использовать точку
входа #3D13 и т.п.).
10. Исправление STS 6.2
-----------------------
В известном отладчике STS определение сос-
тояния триггера прерываний также происхо-
дит с помощью команды LD A,R. Из-за этого
может неправильно выполняться трассировка
программы. При трассировке STS запускает
каждую команду (кроме команд передачи уп-
равления) с помощью резидента, а после
окончания ее выполнения запоминает содер-
жимое регистров процессора и состояние
триггера прерываний. Вот тут и возможны
ошибки.
Допустим, прерывания разрешены и трасси-
руется такая простейшая программа:
#8000 NOP
#8001 JR #8000
Прекратив трассировку через некоторое вре-
мя (при отключенной опции Indicate одной
минуты вполне достаточно), мы увидим, что
прерывания оказались запрещены. Если бы
трассировалась реальная программа, такое
запрещение прерываний могло оказать влия-
ние на весь ход ее дальнейшего выполнения
и даже привести к зависанию (если при
трассировке попалась бы команда HALT).
Совершенно очевидно, что в STS надо внести
исправления. Вот как это сделать для вер-
сии 6.2:
Сначала нужно запустить STS и загрузить
файл "stsб.2 <С>", в котором и будут
производиться исправления.
Затем нужно отыскать свободные 14 байт -
их назначение будет объяснено ниже. Можно
использовать буфер функции пользователя (с
адреса #FEЗ7). Но в той версии STS, кото-
рую я использую, этот буфер занят под про-
цедуру дизассемблирования с метками ассем-
блера ZX ASM, поэтому я решил сократить
некоторые текстовые сообщения:
'Block' -> 'Bl.' (экономится 2 байта)
'Save' -> 'S.' (----/----- 2 --/--)
'Load' -> 'L.' (----/----- 2 --/--)
' DEFB' -> ' ' (----/----- 4 --/--)
'FileName' -> 'Name' (----/----- 4 --/--)
Для этого с адреса #ЕВ24 нужно ввести сле-
дующую последовательность байт:
#ЕВ24: AE 46 72 6F ED 54 EF 46
#ЕВ2С: 69 6С E5 53 65 63 74 6F
#EB34: F2 53 AE 4С AE 53 74 6F
#EB3C: 70 20 69 E6 42 61 6E ЕВ
#EB44: 51 75 69 F4 54 72 61 63
#EB4C: E5 53 74 61 72 F4 44 69
#EB54: 73 61 73 ED A0 46 69 6С
#EB5C: E5 42 41 53 49 С3 20 44
#EB64: 4F D3
По адресу #E702 заменяем значение #0A на
#0E, чтобы правильно печаталось имя файла
(т.к. вместо строки FileName осталось
просто Name).
Итак, теперь с адреса #EB66 свободно 14
байт. Смотрим, где в STS происходит опре-
деление состояния триггера прерываний:
#DFFE: LD (#5BA1),SP
LD SP,#5BA1
PUSH ВС
PUSH AF
LD A,R
DI
LD ВС,#7FFD
LD A,#1F
OUT (С),A
LD В,#BF
LD A,#00
OUT (С),A
JP #E028
Заменим команды LD A,R: DI на NOP, а ко-
манду JP #E028 - на JP #EB66. С адреса
#EB66 поместим такой фрагмент:
#EB66: LD A,R
JP РО,#EB6E +
NOP |
+- JR #ЕВ70 |
| LD A,R <---+
+> DI
JP #E028
Обратите внимание - этот фрагмент в любом
случае при своей работе увеличивает ре-
гистр R на одинаковую величину (на 7). Де-
ло в том, что далее будет выполнена еще
одна команда LD A,R, на этот раз нужная
уже для определения значения регистра R, и
будет произведена коррекция полученного
значения, т.к. значение регистра R увели-
чивается с каждой выполненной командой, а
надо узнать его значение на момент оконча-
ния выполнения трассируемой команды. Вот
как это выглядит:
#DCA2: LD A,#5A
LD HL,#FEFЧ
SLA (HL)
RLA
ADD A,(HL)
RRCA
LD (HL),A
RET
Константу #5A по адресу #DCA3 следует за-
менить на #53, т.е. уменьшить на 7 - ведь
в программу был добавлен дополнительный
фрагмент, увеличивающий регистр R на 7, и
надо скомпенсировать это изменение.
После этого остается только записать изме-
ненный файл на диск.
Other articles: