Гуннар Далснес задался вопросом, как ud0 и ud1 могли иметь параметры, если они были неопределёнными? «Означает ли это, что они не были полностью неопределёнными, просто не документированными и не до конца реализованными?»
Опкоды ud0 и ud1 не имеют определения, но это не то же самое, что быть архитектурно «неопределённой инструкцией». Они находятся в некоем чистилище, где им не присвоено значение, но они также официально не объявлены бессмысленными.
Изначально эти последовательности байтов проходили через декодер инструкций и как будто «проскакивали» через несколько щелей, пока кто‑то в конце концов не заметил: «Постойте-ка, я не знаю, как это выполнить».
Это видно, если посмотреть на инструкции, кодируемые как 00001111 11111xxx, начиная с Pentium III. Последовательности байтов 0F FB и 0F FF ещё не имели присвоенного значения. Можно представить, как декодер инструкций мог пойти по упрощённому пути и сказать: «Ну, все инструкции в этом диапазоне, или по крайней мере все, которые меня интересуют, берут регистры mm и операнд mm/m64, так что я сэкономлю транзисторы и декодирую их все с двумя параметрами (mm, mm/m64).» А затем после декодирования он использовал бы бит 3, чтобы решить, настроить ли арифметическое устройство для сложения или вычитания, и биты 0 и 1, чтобы решить, как подразделить биты на насыщаемые единицы.
И если бы вы подали 0F FF, именно на последнем этапе декодер понял бы: «Ох уж это сочетание битов 11, я не знаю, что с ним делать. Подниму исключение недопустимой инструкции.» Исключение недопустимой инструкции возникало после разбора операндов.
Проблемы, которые создал 0F FF, стали особенно заметны, когда эти пустые слоты начали заполняться инструкциями SSE. Естественное место для инструкции PADDQ было 0F FF, но люди уже использовали 0F FF в ожидании того, что он вызовет исключение illegal instruction. Превращение его в допустимую инструкцию сломало бы такие программы, поэтому Intel пришлось переместить PADDQ в довольно неуклюжее место 0F D4.
Бонус: неопределённые инструкции с параметрами на самом деле встречаются не так уж редко. Например, в AArch64 выделен диапазон из 65 536 инструкций как постоянно неопределённый, поэтому инструкция udf принимает 16‑битный немедленный операнд, чтобы указать, какой недопустимый опкод вы хотите. В PDP‑10 опкод 000 был зарезервирован как постоянно незаконная инструкция, и он нес в себе регистр и адрес в памяти как параметры (потому что все инструкции PDP‑10 несут регистр и адрес памяти как параметры).
Также существуют так называемые «неофициальные опкоды» — инструкции, которые не входят в архитектуру набора команд, но поведение которых люди обратным путём восстановили и начали на него полагаться. (Процессор 6502 хорошо известен в узких кругах тем, что подвергался такому анализу.) 0F FF — один из таких «неофициальных опкодов», который был достаточно популярен, чтобы Intel оказалась под давлением поддерживать обратную совместимость с ним, хотя он никогда официально не документировался и не поддерживался архитектурно.
Ещё один бонус: похоже, мнемоника ud1 была введена ассемблером nasm. Кажется очевидным, что ud1 — это также имя, которое Intel дала внутренне этой наследственной инструкции. Иначе не было бы необходимости называть новую инструкцию ud2.
Рэймонд (автор) участвует в эволюции Windows более 30 лет. В 2003 году он завёл веб‑сайт под названием The Old New Thing, который стал гораздо популярнее, чем он мог представить, что по‑прежнему вызывает у него смутное беспокойство. Сайт породил книгу с тем же названием (Addison Wesley, 2007). Иногда он появляется в аккаунте Windows Dev Docs в Twitter, чтобы рассказывать истории, не несущие полезной информации.
В 65C02, CMOS‑версии 6502, добавили несколько режимов адресации, но всё ещё оставалось много неопределённых опкодов. Они поступили интересно: в 65C02 все неопределённые опкоды стали NOP. Но некоторые — это однобайтовые NOP, другие — двух- или трёхбайтовые. Причина? Они готовили почву для 65816, 16‑битного преемника, который уже разрабатывался. Размеры NOP в 65C02 в основном соответствуют размерам инструкций 65816 с теми же опкодами.
Надо учитывать, что в 6502 не было механизма ловли недопустимого опкода и он не был нужен: opcode BRK (breakpoint) был 00, самое распространённое значение в данных, так что если вы пытались выполнить не код, рано или поздно вы наталкивались бы на BRK. И BRK сам по себе был двухбайтовой инструкцией (адрес, помещаемый на стек при возникновении программного прерывания, был на два байта после опкода BRK), возможно, с намерением позволить скрытому параметру использоваться как номер функции, если BRK использовался как системный вызов.
Старая инструкция AAD (только в x86‑32 допустима, в x86‑64 вызывает #UD) тоже имела скрытый параметр: D5 0A — множитель 10 (0A) можно было заменить на другой немедленный и она бы работала с ним. У неё даже было реальное применение вне спецификаций — для октальной коррекции. Сейчас это полностью бесполезно, но в 80‑е годы это использовали часто (некоторые утверждают, что это был один из продающих аргументов 8086, но это чистая выдумка).
О, эти секреты обфускации дизассемблера и трюков/обнаружения эмуляторов — многие из нас это помнят (иначе говоря, «я так стар»). Кто‑нибудь помнит кошмары декодирования x87 и присвоения мнемоник?
В общем, звонить в службу поддержки продукта в пьяном виде не рекомендуется.
Каким алгоритмом Windows XP выбирал вашу начальную пользовательскую картинку?
Получать уведомления о новых публикациях.
Вы уверены, что хотите удалить это?
