════════════════════════════════════════════════════════════════ ЗЛОБА ДНЯ-II ════════════════════════════════════════════════════════════════ (c) Mac Buster^XTM (c) UnBEL!EVER^XTM Около года назад Иван Рощин вместе с релизом очередной версии BEST VIEW опубликовал небольшую исследовательскую заметку о найденном им глюке ( недокументированной особенности ) в работе команд процессора Z80 в аспекте возникновения прерываний. Сенсацией стало то, что благодаря описанному Иваном тесту можно было 100% определить - выполняется ваша программа под эмулятором или на живой машине. В силу редкости возникновения этого "недокументированного" явления ни один эмулятор тех времен не работал именно так, как вёл себя реальный Z80. Прошло время... Было выяснено, что и раньше, программисты или знали, или догадывались о неправильном установлении флага IFF2 в случае прихода прерывания в момент выполнения команд LD A,R или LD A,I. Судить об этом можно совершенно однозначно по коду программ, написанных ещё задолго до 98-го года. Большинство эмуляторов в настоящее время уже поддерживают эту особенность Z80, но остаётся лишь один вопрос: "Почему за 20 лет существования процеcсора этот феномен не стал достоянием широких масс общественности наряду с половинками индексных регистров, двойной префиксации и массой других, so-called недокументированных особенностей???" Ответ на этот вопрос трагичен... Мы живём в состоянии настоящего информационного голода!!!! Именно из-за отсутствия доступа к первоисточникам происходит это постоянное открывание Америки. Вот представьте себе, что вы стали обладателем Hi-End видеомагнитофона, но к нему нет инструкции. И вот год за годом вы нажимаете на нём кнопки управления и постепенно осознаёте их истинные функции. Точно так же глупо выглядит игрок, пытающийся понять, что скрывается за клавишами, издающими звуки в режиме PAUSE в знаменитой ELITE. И ведь ему даже в голову не может прийти, что в прилагаемом к ELITE фирменном руководстве всё это подробно описано... Короче, господа! Если вы хотите тратить своё время на исследование давно известных вещей - пожалуйста! Но может быть следует напрячься и достать всё же наконец инструкцию по эксплуатации???? Именно так поступил Mac Buster^XTM, когда не пожалев 5$, заказал в фирме Zilog руководство по процессору Z80. Ну и естественно результат превзошёл все ожидания! Ниже приводится перевод нескольких ключевых страниц руководства, имеющих прямое отношение к "bug'у Ивана Рощина". ----------[начало]---------------------------------------------- Zilog МП Z80(R) Руководство пользователя ---------------------------------------------------------------- LD A,I Операция : A<-I Оператор: LD Операнды: A,I +-+-+-+-+-+-+-+-+ |1|1|1|0|1|1|0|1| ED +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ |0|1|0|1|0|1|1|1| 57 +-+-+-+-+-+-+-+-+ Описание : Содержимое регистра вектора прерывания I загружается в аккумулятор. M Cycles T States MHz E.T. 2 9(4,5) 2.25 Действие на флаги: S: Установлен, если 7-ой бит в регистре I равен 1; иначе сброшен Z: Установлен, если регистр I = 0; иначе сброшен H: Сбрасывается P/V: Содержит копию триггера прерываний IFF2 N: Сбрасывается C: Не изменяется Примечание: Если прерывание происходит в момент выполнения этой команды, флаг переполнения/чётности ( P/V ) будет содержать 0. ------------------------------------------------------[A5-23]--- Zilog МП Z80(R) Руководство пользователя ---------------------------------------------------------------- LD A,R Операция : A<-R Оператор: LD Операнды: A,R +-+-+-+-+-+-+-+-+ |1|1|1|0|1|1|0|1| ED +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ |0|1|0|1|1|1|1|1| 5F +-+-+-+-+-+-+-+-+ Описание : Содержимое регистра обновления памяти R загружается в аккумулятор. M Cycles T States MHz E.T. 2 9(4,5) 2.25 Действие на флаги: S: Установлен, если 7-ой бит в регистре I равен 1; иначе сброшен Z: Установлен, если регистр I = 0; иначе сброшен H: Сбрасывается P/V: Содержит копию триггера прерываний IFF2 N: Сбрасывается C: Не изменяется Примечание: Если прерывание происходит в момент выполнения этой команды, флаг переполнения/чётности ( P/V ) будет содержать 0. ------------------------------------------------------[A5-24]--- Zilog Семейство Z80, Вопросы и Ответы: МП Z80 ---------------------------------------------------------------- В: Я не уверен в том, что получаю истинное значение состояния прерываний, когда использую инструкции LD A,I и LD A,R для чтения IFF2. Почему это происходит? Как с этим бороться? О: В CMOS версии Z80 CPU мы исправили эту проблему. Но в Z80 CPU NMOS версии, при определённых обстоятельствах, триггер прерываний IFF2 процессора Z80 не всегда отображает реальное состояние прерываний. Две инструкции - LD A,R и LD A,I копируют состояние триггера прерываний IFF2 во флаг чётности/переполнения ( P/V ), и изменяют содержимое аккумулятора Z80 ( см. таблицу 7.0.1 в "Z80 CPU technical manual"). Таким образом, можно определить, были ли разрешены или запрещены прерывания во время выполнения одной из этих команд. Это необходимо для получения достоверной информации о состоянии машины. Тем не менее, если прерывание произойдёт во время выполнения одной из этих инструкций (подразумевается, что прерывания должны быть разрешены), то флаг P/V очистится (сбросится в 0). Это приведёт к неверной трактовке - якобы прерывания были запрещены на момент выполнения инструкции. Этот парадокс может быть объяснён последовательностью действий при выполнении внутренних функций процессора. Дело в том, что в данной ситуации вторичный триггер прерываний (IFF2) очищается ещё до того, как его содержимое пересылается во флаг P/V. Состояние триггера прерываний не копируется во флаг чётности до появления сигнала подтверждения приёма прерывания, происходящего во время исполнения инструкции. Поскольку механизм подтверждения приёма прерывания автоматически очищает триггер прерываний, то и флаг чётности также очищается, несмотря на то, что прерывания были разрешены в момент начала исполнения инструкции. Наиболее простой способ определить такую ситуацию основан на том факте, что при возникновении прерывания на вершину стека заносится как минимум один элемент - старое значение PC. Таким образом, записав нулевое значение по адресу, предшествующему текущей вершине стека, непосредственно перед исполнением команды LD A,I ( или LD A,R ) и проверив это значение сразу же после её исполнения, мы сможем установить истинное значение триггера прерываний. Если это значение изменилось во время выполнения LD A,I ( или LD A,R ), значит прерывание произошло в процессе исполнения этой команды и прерывания определённо разрешены. Вот последовательность действий, воспользовавшись которой, можно написать процедуру для определения состояния прерываний: - обнуляем слово, предшествующее текущей вершине стека - выполняем команду LD A,I ( или LD A,R ) - сразу же проверяем состояние флага P/V. Если он установлен, то у нас нет никаких причин сомневаться в том, что прерывания и в самом деле разрешены - если же P/V сброшен, то нам имеет смысл проверить состояние слова перед вершиной стека и если оно не равно нулю, то это значит, что прерывания всё же разрешены и в момент исполнения LD A,I ( или LD A,R ) происходило прерывание. Если то слово перед вершиной стека не изменялось - можно с уверенностью сказать, что прерывания и в самом деле запрещены Обе нижеприведённых процедуры возвращают флаг C сброшенным в том случае, если прерывания разрешены, либо установленным, если они запрещены. Процедуры изменяют содержимое регистра A, но не стоит рассчитывать на то, что на выходе он будет содержать значение регистров I или R. Значение прочих флагов неопределено. Первая процедура может располагаться где угодно в памяти кроме "нулевой страницы" - от #0000 до #00FF. Это небольшое ограничение обусловлено лишь тем, что процедура проверяет только старший байт слова из следующей позиции стека. Этот байт не будет нулевым после прихода прерываний только в случае, если процедура сама не находится в "нулевой странице". Вторая же процедура проверяет сразу оба байта следующей позиции стека и, таким образом, избавлено от подобного ограничения. Внимание! Эти процедуры предполагают, что обработчик прерываний разрешает прерывания при завершении своей работы. Это делается в большинстве случаев. Однако, возможно получение неверного результата, если процедура обработки прерывания, которая не разрешает прерывания при прекращении своей работы, начнёт выполняться непосредственно после исполнения инструкции LD A,I (или LD A,R). Листинг 1: Эта процедура не может работать из адресов #0-#00FF GETIFF: XOR A ;C flag, acc.:=0 PUSH AF ;stack bottom :=00xxh POP AF ;Restore SP LD A,I ;P flag :=IFF2 RET PE ;Exit if enabled DEC SP ;May be disabled ( P flag :=0 ). DEC SP ;Has stack bottom been POP AF ;overwritten ? AND A ;If not xx00h, INTs were RET NZ ;actually enabled. SCF ;Otherwise, they really are RET ;disabled. END Листинг 2: Эта процедура может работать из любых адресов GETIFF: PUSH HL ;Save HL contents XOR A ;C flag, acc.:=0 LD H,A ;HL :=0000h LD L,A ; PUSH HL ;Stack bottom :=0000h POP HL ;Restore SP LD A,I ;P flag :=IFF2 JP PE,POPHL ;Exit if enabled DEC SP ;May be disabled ( P flag := 0). DEC SP ;Let's see if stack bottom POP HL ;is still 0000h. LD A,H ;Are any bits set in H OR L ;or L ? POP HL ;Restore old contents. RET NZ ;HL <> 0 : isn't enabled SCF ;Otherwise they really are RET ;disabled. POPHL: ; POP HL ;Exit when P flag is RET ;set by LD A,I ( P flag := 1 ). END ----------[конец]----------------------------------------------- Так что, дорогие читатели, всё уже давно открыто и задокументировано... Нам остаётся лишь ещё раз посоветовать вам читать фирменные руководства и вообще, брать информацию из первоисточников. Тем более, что эти самые первоисточники вполне доступны с приходом InterNet'а.