|
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 то... Дерзайте!
Другие статьи номера:
Похожие статьи:
В этот день... 13 ноября