Недавно, когда я изучал досовые бут-секторы, я обнаружил довольно загадочную вещь. DOS распознает, является ли бут-сектор загрузочным, по нескольким критерям, и один из них (странно, да?) — есть ли в первых двух байтах jump-инструкция, которая указывает, соответственно, куда-нибудь за BPB. В модуле MSDISK.INC из MS-DOS 3.21 OAK как раз и происходят такие проверки. Модуль проверяет, начинается ли BPB с прыжка, при этом за верную jump-инструкцию он принимает опкоды EBh (short JMP), E9h (JMP), или 69h. Стоп. 69? Это же IMUL!
Не-а, не IMUL. Комментарий в коде утверждает, что это «direct jump»:
cmp byte ptr cs:[DiskSector],069H ; Is it a direct jump? je Check_Signature ; don't need to find a NOP cmp byte ptr cs:[DiskSector],0E9H ; DOS 2.0 jump? je Check_Signature ; no need for NOP cmp byte ptr cs:[DiskSector],0EBH ; How about a short jump. jne BadDisk
Хорошо, вот только про 69h в документации 8086 ничего не сказано! Да, в 8186 и далее — это опкод IMUL, но то, что 8086 умеет IMUL — крайне маловероятно. Тем более, комментарий ясно указывает, что это прыжок.
Может быть, на процессорах 8086 69h ведёт себя как прыжок? Вопрос хороший, вот только информации по этому поводу почти нет. Я подумал, что кто-нибудь из олдовой хакерской тусовки ранних PC наверняка бы точно выяснил, что именно делают незадокументированные инструкции, но я ошибся — довольно глубокий поиск по всему интернету не дал никаких вразумительных результатов, и даже книжка авторства Frank van Gilluwe «Undocumented PC», в которой довольно много сказано про неописанные в документации операционные коды, помогла несильно. Наверняка, когда-нибудь, где-то кто-то что-то, да опубликовал...
Сначала хорошо бы отметить, что начиная с 80186, в отличие от 8086/8088, попытка исполнения неправильного (недокументированного) опкода вызовет соответствующее исключение (сейчас обозначается как #UD). То есть сейчас опкод либо является стандартным и исполняется, либо срабатывает #UD, и, соответственно, нет никакого смысла пытаться исполнять что-либо, кроме описанных в документации инструкций. Ну разумеется тут есть исключения: D6h и F1h — Intel ясно дал понять, что стандарты его не волнуют… но вернёмся опять к 8086.
Для 8086 не было описано, как тот должен себя вести, если на нём пытаются исполнить неверную инструкцию. Неопределённое поведение значит, что каждая, даже неописанная инструкция, точно делает что-нибудь, и это что-нибудь вполне может повесить процессор. По крайней мере это не запрещено.
Существует несколько опкодов 8086, которые не описаны в документации Intel, но поведение которых было давно установлено:
- POP CS (0Fh). Довольно просто угадать, что он делает. Смотрите: 06h (push es) — 07h (pop es), 16h (push ss) — 17h (pop ss), 1Eh (push ds) — 1Fh (pop ds), 0Eh (push cs) — и, соответственно, 0Fh (pop cs). Эта инструкция, хоть и работает, но почти бесполезна. Потому-то она никогда не была документирована, и, начиная с 80286, опкоду 0Fh соответствует другая инструкция.
- MOV CS (8Eh). То же, что и с POP CS — довольно просто угадать, и опять же — бесполезна.
- SETALC/SALC (D6h). Делает то же, что и SBB AL, AL, но не трогает флаги, то есть в AL будет или FFh, или 0 — в зависимости от CF. Эта инструкция присутствует в современных центральных процессорах Intel, но она всё равно незадокументирована.
Три инструкции — хорошо, но для 8086 также неопределены 60h — 6Fh (в числе которых наша 69h), C0h — C1h, C8h — C9h, и F1h. Также есть несколько пробелов в opcode extensions (это когда инструкция описывается не только первым байтом, но и частью бит в следующим за ним байтом), особенно — группа GRP4, вот.
Чтобы прояснить это дело, Raúl Gutiérrez Sanz взял свой Siemens 8086-2 (произведён в 1990) и разобрался, что именно происходит при исполнении опкодов, неописанных в официальной документации.
Этот процессор произведён под лицензией Intel, и, без каких-либо подозрений, он функционально не отличается от оригинальных процессоров Intel.
Разгадка
К сожалению, поведение большинства недокументированных опкодов весьма прозаично. Никаких магических секретных инструкций — эти инструкции — всего лишь алиасы к другим, документированным. Другими словами, процессор 8086, при декодировании таких инструкций, просто игнорирует некоторые биты слова, и всё :(
Незадокументированные опкоды 60h — 6Fh соответствуют тем же самым инструкциям, что и документированные 70h — 7Fh — процессор игнорирует четвёртый бит опкода, то есть 60h соответствует 70h, 61h — 71h, ..., 6Eh — 7Eh, 6Fh — 7Fh, и делают они ровно то же самое. Довольно разумно, особенно — для медленного 8086 — никакого потенциально опасного поведения, да и микрокода лишнего писать не нужно. С другой стороны, эти изначально незадокументированные опкоды потом можно назначить для других инструкций в процессорах нового поколения, сохраняя обратную совместимость (ведь предполагается, что неописанные опкоды никто в коде использовать не будет).
Так, в диапазоне C0h — CFh ЦПУ игнорирует первый бит слова: C0h означает C2h, C1h — C3h, C8h — CAh, а C9h — CBh.
Поведение опкода F1h в настоящий момент остаётся тайной. На новых процессорах F1h — недокументированная инструкция ICEBP, или INT1. В документации Intel она не описана, хотя у AMD присутствует.
На 8088, F1h скорее даже не инструкция, а префикс. Мы определили это, пошагово пройдя по последовательности, состоящей из повторяющихся опкодов F1h и ещё какого-нибудь документированного. Микропроцессор перешагивает через всю последовательность, что фактически доказывает, что F1h — некоторого рода префикс.
Думается, что F1h — это алиас к F0h — префикс LOCK. Как-нибудь доказать или опровергнуть это у нас не получилось, потому что требуется какая-нибудь железяка, способная отслеживать сигнал LOCK# на шине.
Вернёмся к бут-сектору. Мог ли бут-сектор начинаться с 69h? На 8088, скорее всего, это могло бы сработать. Если опкод 69h — это алиас к инструкции JNS, то, если знаковый флаг не установлен, то будет выполнен short jump. По крайней мере на IBM PC состояние флагов на начало исполнения бут-сектора предсказуемо, таким образом 69h мог бы сработать.
Но в чём смысл ставить в начало бут-сектора 69h и кому это могло бы понадобиться? Хороший вопрос. На данный момент мне неизвестно, существовали ли какие-нибудь бут-секторы DOS, начинающиеся с опкода 69h. Для чего кому-то надо было использовать недокументированные инструкции совершенно непонятно. Может, неправильная реализация системы защиты от копирования… но факт в том, что DOS отдельно проверяет начало бут-сектора на наличие опкода 69h, и это совершенно точно доказывает, что такие бут-секторы определённо существовали. Идеи?
P. S. Есть ещё несколько недокументированных опкодов в диапазоне расширенных кодов! Подробнее об этом в следующий раз...
Автор: dbanet