ZX Club
#09
31 октября 1998 |
|
Soft Group - Интересный текст от Ивана Рощина для кодеров.
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, и надо скомпенсировать это изменение. После этого остается только записать изме- ненный файл на диск.
Другие статьи номера:
Похожие статьи:
В этот день... 4 декабря