Born Dead
#05
06 января 1999 |
|
Coding - Глюки в эмуляторах. Особенности эмуляции процессора Z80.
════════════════════════════════════════════════════════════════ ╠╬╣║╞╪╡█╡╪╞║╣╬╠ CODING-2. EMULATORS MUST DIE! ╠╬╣║╞╪╡██╡╪╞║╣╬╠ ════════════════════════════════════════════════════════════════ (c) 1998 ALK/Stars of Keladan Вот дожили... На эмуляторы пересели! И что? Проги глючить стали реже? - Нет. Это только у них там - "Программы для MS-DOS (R) стали работать быстрее и надёжнее". Вот запустишь какой-нить boot с автоопределителем дискеты и кранты... Я уж молчу про демы ;) они в эмуляторах превращаются в какой-то отстой! Поэтому провозглашаю вместо эпиграфа: "Хороший эмуль - распознанный эмуль!" Ибо зная, что ты в эмуляторе, можно отключить часть процедур и т.д., можно вообще пойти на принцип - типа: "Не желаю работать на этом отстое - пошёл на ..." Как программа определит, что она работает под эмулятором? Очень просто... Скажу вам по секрету, любой эмулятор глючит. Нужно и всего-то - распознать, где глюк, а где правда... На сегодняшний день мне известны восемь глюков, поддаю- щихся распознаванию. Перехожу к их описанию: <----───────────────────────────────────────────────────────---> 1. "Глюк границы памяти" Теория : Неверное выполнение команд LD NN,(#FFFF) Эмулятор : UKV Debugger (Углеков) Комментарии: Сомнительная ценность, так как мне известен лишь один эмулятор с таким глюком, но и в нём последствия выполнения этих команд куда более плачевны (см. ниже). Можно использовать в качестве защиты... Подозреваю, что в одном из демо-релизов Чёрного Ворона так и было... LD HL,#FFFF LD E,(HL) INC HL LD D,(HL) LD HL,(#FFFF);<= Вот тут-то UKV и обломится, ;правда, если установлен QEMM386 или EMM, ;то не только UKV, вообще PC повиснет! ;Вот так Спектрум _легко_ может сделать ;must die "калькулятору" ;) OR A SBC HL,DE JP NZ,EMUL;Здесь и далее под переходом на метку EMUL ;понимается положительный результат ;определения эмулятора .... Посткомментарий: Вот что пишет по этому поводу сам автор (Углеков): "Команды типа LD HL,(FFFF) выполняются правильно не всегда (точнее сказать, всегда, но не на всех машинах, но от этого не легче). Почти наверняка будет работать правильно в такой конфигурации: 386 процессор + драйвер EMM386 либо отсутствие драйвера EMM. Драйвер QEMM не позволит выполнить команды типа LD HL,(FFFF) наверняка. С 286 процессором ситуация хуже - у меня эти команды всегда вешали эмулятор (правда, проверялось только на одной машине)." Вот такие дела... <----───────────────────────────────────────────────────────---> 2. "Эмуляция регистра регенерации R" Теория : Неверная эмуляция регистра R Эмулятор : Z80 (версия LUNTER), UKV Комментарии: В Z80 и UKV есть опции отключения эмуляции R, что заметно повышает "мощность" виртуального Спектрума, посему, если таковые отключены, то этот факт будет установлен. На всякий случай также проверяется правильность эмуляции старшего бита R - он не должен изменяться. Мало-ли какие эмуляторы могут быть? DI LD A,#7F LD R,A LD A,R DEC A JP NZ,EMUL ;Вторая проверка нужна исключительно для старшего ;бита R - сначала проверялось нулевое значение, ;теперь - единичное. LD A,#81 LD R,A LD A,R CP #81 JP NZ,EMUL .... <----───────────────────────────────────────────────────────---> 3. "Самоуничтожение команды LDIR" Теория : Грубая эмуляция команд LDIR,LDDR Эмулятор : Z80, UKV (наверное... точно не помню) Комментарии: В Z80 неверно обрабатываются команды блочных операций. Там считается, что, например, LDIR выполняется "за один проход", то есть процессор извлекает код операции (два байта), затем выполняет команду, а потом переходит к следующей. В реальности же (даже в книгах Родионова/Ларченко про это написано!) выполнение LDIR/LDDR работает циклически - перед каждой пересылкой процессор читает код операции. Если настроить параметры в HL, DE и BC так, чтобы LDIR заполнял область памяти, в которой он сам находится, например, нулями, то на реальном Z80 выполнение LDIR произойдёт досрочно - уничтожится команда, вернее её "половинка" префикс #ED, и дальше "заработает" другая половинка (#B0 = OR B). Естественно, после этого никакого LDIRа не будет. MEMTST EQU #4000 LD HL,CNTLDR LD DE,MEMTST LD BC,3+ELDR-CNTLDR PUSH DE LDIR;Переносим тестирующий фрагмент в "ненужное" ;место ( для многократного тестирования ) RET ;Переходим на тест CNTLDR LD HL,MEMTST LD DE,MEMTST+1 LD BC,ELDR-CNTLDR-1; Длина взята с таким расчётом, ;чтобы в глюкавом эмуляторе "убилась" команда перехода ;JP NO_LDR - выход, если реальный Z80 LD (HL),0 LDIR JP NO_LDR; в итоге на эмуляторе после LDIR ;выполятся 3 NOP и мы узнаем, что у нас эмуль. ELDR JP EMUL NO_LDR .... <----───────────────────────────────────────────────────────---> 4. "Глюк OUT (C),R" Теория : Команды OUT (C),R в эмуляторе изменяют флаговый ре- гистр F. Эмулятор : Z80 Комментарии: Вообще, я никогда бы до этого не додумался, узнал про это от MAXSOFT'a, так что: (C) MAXSOFT/Speed Co/XTM. С характернейшими проявлением глюка можно познакомиться запистив под LUNTER Z80 один украинский boot (вроде как от SVS'а) там где окошко в центре, а по экрану сеточки летают, каждый раз разные... LD C,#FE;чтоб не мучить животину, возьмём безобидный ;порт для экзекуции XOR A LD L,A LD H,A PUSH HL POP AF;тотально очищаем AF OUT (C),A PUSH AF POP DE;запоминаем "грязный" F в E CP E;сравниваем JP NZ,EMUL ;второй этап: проверка на сброс битов в F ;а всё из-за того, что лень тянуть исходники ;на Z80 - вот и проверяю все биты F на все ;изменения ;( LD L,#FF PUSH HL POP AF OUT (C),A PUSH AF POP DE INC E JP NZ,EMUL .... <----───────────────────────────────────────────────────────---> 5. "Точки перехвата TR-DOS" Теория : В эмуляторе UKV характерные точки входа в TR-DOS заменяются маской. Эмулятор : UKV Комментарии: Для более эффективной работы с дискетами эмулятор, чтобы не "париться" с каждой командой TR-DOS, заменяет фрагменты выполнения характерных процедур на свои, родные ПЦ-шные. А узнаёт эмулятор про это по тому, что в "его" TR-DOSе в точках перехвата расставлены маски - байты #49. Слово автору: "На место точек TEST, SEEK1, SEEK2, RDWR, FORMAT, RESET эмулятор кладёт байт 49h (LD C,C), и сразу за ним - команду RET" Внимание! Перед тем, как тестировать TR-DOS, проверьте наличие присутствия самого' TR-DOS! Не все же эмуляторы писаны под Beta-Disk Interface! LD HL,#2A53 CALL DOSRD;см.ниже... прочитали 16 байт из #2A53 в #5CDD LD HL,(#5CDD) LD DE,#79ED OR A SBC HL,DE JR NZ,E_DST;так как мне было в лом вычислять точки для ;версии 5.01,проверяем TR-DOS на версии 5.03 и её клоны. ;по адресу #2A53(самая используемая процедура ;)) в 5.03 ;стоит OUT (C),A. Если нет,то не отчаивайтесь - напишите ;сами случай для 5.01 LD HL,#801 CALL DOSRD CP #1C JP Z,EMUL;заодно проверим-ка команду AND #1F - в Z80- ;эмуле там стоит AND #1C. По словам автора доработки ;В.А.Мочалина/Vitasoft, это необходимо для того, чтобы: ;"...если дать какую - либо команду (например "CAT") ;после инициализации дисковода, то на незащищенном от ;записи диске будет отформатирована нулевая дорожка. ;Чтобы избавиться от этого, пришлось изменить один байт ;в системе по адресу 801 НЕХ (было 0FCh, а стало 1Ch) ;для версий 5.03 и 5.04T..." ;(Выдержка из описания Z80TRDOS) LD HL,#3DAD;ну а дальше конвейер... CALL DOSRD JP Z,EMUL LD HL,#3EB5 CALL DOSRD JP Z,EMUL LD HL,#1FFD CALL DOSRD JP Z,EMUL .... DOSRD LD C,#13 IM 1 CALL #3D13;читаем ПЗУ TR-DOS с помощью самого TR-DOS DI LD A,(#5CDD) CP #49;вот и она, эта пресловутая проверка RET <----───────────────────────────────────────────────────────---> 6. "Глюк быстродействия OUT (C),R" Теория : Команда OUT (C),R выполняется с разным временем для разных значений порта в BC Эмулятор : SHALAEV. Вот и добрались до него, родимого.А то всё Z80 да UKV... Комментарии: На этот баг наткнулся я сам, поэтому копирайт (С)ALK/SoK. В общем-то, понятно, внешние устройства эмулируются по-разному, поэтому и время выполнения для них разное. И что интересно - даже если в самом эмуляторе подключить addition TIMES.ADD, который и нужен для обеспечения стабильности относительного быстродействия (количества тактов между прерываниями), то и в этом случае время OUT (C),R всё равно разное! Если сей addition в эмуле Шалаева отключен, то и тут данная ловушка сработает - при этом сильно "гуляет" быстродействие одних и тех же команд (даже не OUTы). CALL INIINT;саму процедуру см. в последнем примере ;установили IM 2, таблица прерываний в #8000-#8101 ;HL указывает на предполагаемый обработ.прерываний ;по адресу #8181 LD DE,EXTOUT LD (HL),#C3;<= там установим JP EXTOUT INC HL LD (HL),E INC HL LD (HL),D LD BC,#BFFD XOR A CALL CHKZL2;замеряем количество циклов в одном ;прерывании по команде OUT (#BFFD),0 ;кто не знает - регистр данных AY - в SHALAEVе есть PUSH HL;запоминаем количество циклов LD BC,#7FFD LD A,#10 CALL CHKZL2;тоже самое проделываем для команды ;OUT (#7FFD),#10 ;кто не знает - я не виноват ;) PUSH HL;аналогично запоминаем LD HL,TSOUT2 CALL CHKZL;и наконец, "контрольный" замер эквивалентного ;OUT (C),A по времени куска из 3-х NOP (12 тактов) EX DE,HL;DE - контрольное число POP HL;кол-во для OUT (#7FFD),#10 CALL CHEK_E1; сравнение "по модулю" POP HL;кол-во для OUT (#BFFD),0 JP NC,EMUL CALL CHEK_E1 JP NC,EMUL ..... ..... EXTOUT POP AF; Это типа того, что обработчик, на самом деле LD A,#C9;ловушка для выхода из бесконечного цикла ;срабатывает один раз LD (#8181),A; ставит RET на обраб. EX DE,HL RET CHKZL2 LD HL,TSOUT1; адрес для теста OUT CHKZL LD DE,1 PUSH AF LD A,#C9 LD (#8181),A LD A,#C3 EI HALT EI HALT;"стабилизационные" HALTы LD (#8181),A; ставит JP на псевдо-обработчик POP AF EI JP (HL) TSOUT1 OUT (C),A;в DE накапливается результат INC DE JP (HL);HL=TSOUT1 TSOUT2 NOP NOP NOP INC DE JP (HL);HL=TSOUT2 CHEK_E1 OR A;сравнение результатов SBC HL,DE JR NC,NNG1 LD A,H;Вобщем, если что-то меньше чего-то, ;то приводим разность к положительному числу ;подобная конструкция ещё будет встречаться далее... CPL LD H,A LD A,L CPL LD L,A INC HL OR A NNG1 LD BC,8; Здесь 8 - максимальный разброс количества ;для поправки на реальном Спектруме. Я конечно, понимаю, ;что тут достаточно поставить два(+/-1 цикл погрешность) ;но это стоит на всякий случай,мало-ли какие самопальные ;"паталоги" бывают SBC HL,BC;NC - разброс большой - эмулятор RET Посткомментарий: - "А если отключить AY в режиме 48K ?" - ну что можно сказать? Есть ещё такие добрые порты: #xxFE(border), #xx1F(kempston joystick), #FDFD(memory selector PROFI) - дерзайте и молитесь, чтобы в неSHALAEVе это не заглючило. И вот ещё что. Может быть, это у меня такой глюкавый PC486, но по последним данным этот тест не сработал у одного самарского спектрумиста - Сергея Зотова. При запуске на 486-ом калькуляторе под SHALAEV'ым пятого OBERON наличие эмулятора не опознавалось до тех пор, пока не пускалась в ход мышка (PC'шная). У меня работает! 10 раз проверял... Возможно просто еденичный случай :) <----───────────────────────────────────────────────────────---> 7. "Нестабильность быстродействия" Теория : По значительному разбросу относительного быстродей- ствия можно сделать вывод о выполнении программы в эмуляторе. Эмулятор : Z80,UKV,SHALAEV, Spectrum Emulator ZX32 for Windows Комментарии: Этот тест - логическое продолжение предыдущего. Суть теста заключается в проверке изменений быстродействия в течении заданного отрезка времени. (c) ALK/SoK CALL INIINT;инициализируем 2-й режим прерываний LD B,20;я думаю, что 20-ти замеров будет достаточно LOOP PUSH BC CALL TSPERF;собственно, оно... (см. ниже) POP BC DJNZ LOOP LD A,(FLTSP+1);если там 0, то эмулятор OR A JP Z,EMUL .... TSPERF LD HL,#BF00;адрес, где будет сформирована процедура ;замера количества тактов между прерываниями ;желательно разместить её в пределах #8000- ;#BFFF, помня о так называемой "медленной" ;памяти CALL PERFOR;в BC - число тактов/10 (подробнее см.ниже) OLDTAC LD HL,0; предыдущее значение кол-ва тактов, сначала =0 OR A SBC HL,BC RET Z;если равно предыдущему, то выход ;...иногда (на реальных Спектрумах) не срабатывает ;(погрешность +/- один цикл) EX AF,AF';запомним на будущее результат вычитания FLTSP LD A,#FF;если ловушка уже сработала, то пропуск - ;холостые циклы OR A JR Z,EN_PERF LD DE,(OLDTAC+1);эта проверка нужна ;для предотвращения ложного срабатывания программы ;при первом цикле вычислений - просто пропуск LD A,D OR E JR Z,EN_PERF PUSH BC;запоминаем "новое" значение кол-ва тактов EX AF,AF';вспоминаем результат разности JR NC,NNG2;приводим разность в отн.быстродействии ;к положительному числу LD A,H CPL LD H,A LD A,L CPL LD L,A INC HL OR A NNG2 LD BC,5;если разброс меньше 50 тактов (опять-таки ;перестраховка), то пропуск SBC HL,BC JR C,EN_PRF1 SCMD LD A,7;уменьшение счётчика "плохих" изменений DEC A LD (SCMD+1),A JR NZ,EN_PRF2;если ещё не семь раз, то ;продолжаем - на выход LD (FLTSP+1),A;если семь раз подряд скакнуло ;быстродействие, то это эмуль - сброс флажка - ;остальные скачки' игнорируются JR EN_PRF2 EN_PRF1 LD A,7;если очередной скачок был в пределах нормы ;то устанавливаем снова счётчик "скачков подряд" LD (SCMD+1),A EN_PRF2 POP BC;вспоминаем текущее значение кол-ва тактов EN_PERF LD (OLDTAC+1),BC;теперь оно "старое" RET ;Процедура замера кол-ва тактов между прерываниями ;Идея (C) VAV/Exploder/Extreme PERFOR LD BC,3;Начальное значение счётчика тактов LD A,#C9 LD (#8181),A;Обработчик на RET LD DE,EXPRFT LD (#8182),DE;Подготовка настоящего обр. LD (HL),3; <= INC BC <=6 тактов INC HL LD (HL),#E9;<= JP (HL) <=4 такта DEC HL LD A,#C3 EI HALT;Стабилизация LD (#8181),A EI JP (HL) ;Начальное значение BC=3 не случайно,если сложить время трёх ;выше переч. команд + команда RET в обработчике, то получим 25 ;тактов. Процедура подсчёта состоит из 2-х команд, один цикл ;занимает 10 тактов - очень удобно для вывода! Впрочем, для ;нашего случая такая точность не так важна... EXPRFT POP AF;аналогично как в предыдущем примере LD A,#C9 LD (#8181),A RET ;процедура инициализации прерываний второго рода INIINT DI LD A,#80 LD BC,#100 LD I,A LD H,A LD D,A LD L,C LD E,B INC A LD (HL),A LDIR LD H,A LD L,A IM 2 RET Посткомментарий:Данный алгоритм был реализован во вступительном тесте к Oberon5, и к своему сожалению, позже здесь я обнаружил самый что ни на есть BUG! Кстати, в тексте он тоже есть ;) Если сидеть перед экраном тихо, и главное - ничего не трогать, то всё работает железно (на эмуляторах всё нормально). BUG касается реальных Спектрумов. Вы же знаете наших юзеров... Им только дай пощёлкать разными примочками, кнопками Turbo, Slow, Stop etc. Так вот, если быстро переключать режим Turbo/Normal и обратно (теоретически семь раз), то лёгким движением руки Спектрум превращается... в обыкновенный РС! Bug исправляется просто: в процедуре TSPERF вместо RET Z ставится JR Z,EN_PRF3 и добавляется: EN_PRF3 LD A,7 LD (SCMD+1),A RET Поэтому при выполнении тестов N6 и N7 я строго не рекомендую щёлкать кнопками и двигать мышками. Но все семь примеров жестокой борьбы - ничто по сравнению с последним... <----───────────────────────────────────────────────────────---> Под занавес можно преподнести вам небольшую сенсацию! Авторское право, он же Copyright закреплён за: (C)1998 Иван Рощин, Москва 8. В это трудно поверить: "В Z80 есть ошибка!" Теория : Неправильное выполнение команд LD A,R / LD A,I на настоящем Z80. Эмулятор : Z80, UKV, SHALAEV, ZX32 for Windows,WARAJEVO,ZX-JAM ZX-EMUL by Lion17, X128 by James McKay (все проверены), и наверное во всех остальных эмуляторах тоже, так как никто про эту ошибку не знал! Комментарии: Если непосредственно при выполнении команд (при разрешённых прерываниях любого рода) LD A,R или LD A,I "вклинивается" прерывание, то флаг F, а именно бит P/V формируется ошибочно. Вот выдержка из статьи И.Рощина: "Hо как могут быть связаны прерывания и работа команды LD A,R? Эта команда помещает во флаг P/V содержимое триггера прерываний IFF2. При разрешенных прерываниях этот триггер равен 1, а когда приходит импульс прерывания, он автоматически сбрасывается в 0, чтобы исключить повторную обработку прерывания. Hо обработка запроса на прерывание начинается во время выполнения последнего такта выполняемой команды (т.е. команды LD A,R). И, видимо, уже сброшенный триггер IFF2 копируется во флаг P/V (действительно, с точки зрения процессора, прерывания в этот момент уже запрещены). Всё сказанное относится и к команде LD A,I. Приведенная информация была проверена на оригинальном процессоре Z80 фирмы ZILOG и на отечественном аналоге КP1858ВМ1". CALL INIINT LD (HL),#FB;<= EI Обработчик состоит из двух INC HL LD (HL),#C9;<= RET команд. EI LD BC,0;За 65536 циклов прерывание обязательно ;"вклинится" в LD A,R хоть один раз LTSBUG LD A,R JP PO,REALZ80;если флаг сброшен - то настоящий Z80 DEC BC LD A,C OR B JR NZ,LTSBUG EMUL ..............;Эмулятор определён RET REALZ80 ..............;Нет эмулятора RET Посткомментарий: Это серьёзный удар! По этому поводу у меня есть предложение: Необходимо сделать запрос в команию Zilog с описанием этого глюка - может, так оно и надо? ;) Естественно, в этом многотрудном деле поможет I-Net. Потому, если есть человек, одинаково хорошо "шарящий" в Интернете и в английском, и если он ещё и спектрумист, то всё прогрессивное человечество в лицах остальных спектрумистов будет должно ему по пиву (каждый) за эту неоценимую услугу. О как сказал! <----───────────────────────────────────────────────────────---> P.S. И нафига он здесь столько распинался, скажете вы, когда с помощью последнего теста можно однозначно определить _любой_ на 1998 год эмулятор? - Просто я описывал все тесты в том порядке, в котором я про них узнавал/придумывал, а уж про последнее я узнал в декабре 1998, так что не обессудьте... Good Luck! <----───────────────────────────────────────────────────────---> От редакции: Конечно, по хорошему, надо было поместить всю статью господина Рощина о глюке в Z80, так как это во истинну сенсационный материал который не всплыл на поверхность за вот уже почти 20 лет со времен создания первого процессора Zilog Z80. Однако, Иван Рощин сделав такое знаменательное открытие и даже написав статью по этому поводу не предоставил (?) ее в какое либо электронное издания, а отпустил на "свободу" - в сеть FidoNet, где она и была выловленна нами. Перепечатывать этот материал было бы опрометчиво, так как наверняка ряд других около-сетевых газет тоже не пройдут мимо подобного лакомого кусочка... Хотя метод И.Рощина позволяет 100% определять эмуляторы, все те прибамбасы которые описал ALK тоже могут пригодиться. Они позволят вам не только определить эмулятор, но и его версию. А тут можно и выводы о машине сделать. Например если Z80 - значит "тачка" у товарища хилая, если UKV, то он либо хаккер, либо тормоз, ну а если ZXEMUL031 by Lion17 то... Дерзайте!
Другие статьи номера:
Похожие статьи:
В этот день... 21 ноября