Почему 64-разрядная версия Windows не отключает пользовательские исключения для пользователя?
Почему не может 64-разрядная Windows разворачивать стек во время исключения, если стек пересекает границу ядра - когда 32-битная Windows может?
Контекст всего этого вопроса исходит из:
Случай исчезновения исключения OnLoad - исключения обратного вызова пользовательского режима в x64
Фон
В 32-разрядной Windows, если я выкидываю исключение в моем коде режима пользователя, который был вызван из кода режима ядра, который был вызван из моего кода режима пользователя, например:
User mode Kernel Mode
------------------ -------------------
CreateWindow(...); ------> NtCreateWindow(...)
|
WindowProc <---------------------+
Structured Exception Handling (SEH) в Windows может разматывать стек, отматывать обратно через режим ядра, обратно в мой код пользователя, где я могу обрабатывать исключение, и я вижу допустимую трассировку стека.
Но не в 64-битной Windows
64-разрядные версии Windows не могут этого сделать:
По сложным причинам мы не можем распространять исключение в 64-разрядных операционных системах (amd64 и IA64). Это было так с первой 64-разрядной версии Server 2003. На x86 это не так - исключение распространяется через границу ядра и заканчивается тем, что обращается назад назад.
И так как в этом случае нет способа найти надежную трассировку стека, нужно было принять решение: пусть вы увидите ненужное исключение или вообще скройте его:
Архитекторы ядра на тот момент решили принять консервативный подход, основанный на AppCompat - скрыть исключение и надеяться на лучшее.
В статье рассказывается о том, как это было во всех 64-битных операционных системах Windows:
- 64-разрядная версия Windows XP
- 64-разрядная версия Windows Server 2003
- 64-разрядная версия Windows Vistali >
- 64-разрядная версия Windows Server 2008
Но начиная с Windows 7 (и Windows Server 2008) архитекторы передумали - вроде. Для 64-разрядных приложений только (не 32-разрядных приложений) они (по умолчанию) останавливают, подавляя эти исключения пользовательского ядра. Итак, по умолчанию, на:
- 64-разрядная версия Windows 7
- Windows Server 2008
все 64-разрядные приложения будут видеть эти исключения, где они никогда не видели их.
В Windows 7, когда происходит сбой приложения native x64 таким образом, уведомляется помощник по совместимости программ. Если приложение не имеет Windows 7 Manifest, мы показываем диалоговое окно с сообщением о том, что PCA применила прокси-совместимость приложений. Что это значит? Это означает, что при следующем запуске приложения Windows будет эмулировать поведение Server 2003 и исключить исключение. Имейте в виду, что PCA не существует на сервере 2008 R2, поэтому этот совет не применяется.
Итак, вопрос
Вопрос в том, почему 64-разрядная Windows не может отключить стек обратно через переход ядра, а 32-разрядные версии Windows могут?
Единственный намек:
По сложным причинам мы не можем распространять исключение в 64-разрядных операционных системах (amd64 и IA64).
Подсказка усложняется.
Я не могу понять объяснение, так как я не разработчик операционной системы, но я хотел бы понять, почему.
Обновление: исправление, чтобы остановить подавление 32-разрядных приложений
Microsoft выпустила исправление, позволяющее использовать 32-разрядные приложения, также больше не имеет исключений:
KB976038: Исключения, которые выбрасываются из приложения, работающего в 64-разрядной версии Windows, игнорируются
- Исключение, которое выбрано в процедуре обратного вызова, выполняется в пользовательском режиме.
В этом случае это исключение не приводит к сбою приложения. Вместо этого приложение переходит в несогласованное состояние. Затем приложение выдает другое исключение и сбой.
Функция обратного вызова в режиме пользователя обычно является функцией, определяемой приложением, которая вызывается компонентом режима ядра. Примерами функций обратного вызова пользовательского режима являются процедуры Windows и процедуры перехвата. Эти функции вызывают Windows для обработки сообщений Windows или для обработки событий Windows.
Это исправление позволяет вам отключить Windows от использования исключений по всему миру:
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options
DisableUserModeCallbackFilter: DWORD = 1
или для каждого приложения:
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\Notepad.exe
DisableUserModeCallbackFilter: DWORD = 1
Поведение также было зарегистрировано на XP и Server 2003 в KB973460:
Подсказка
Я нашел еще один намек при исследовании с использованием xperf для захвата трассировки стека в 64-битной Windows:
Отключить пейджинговый исполнитель
Чтобы трассировка работала на 64-битной Windows, вам нужно установить ключ реестра DisablePagingExecutive. Это говорит операционной системе, чтобы не нажимать на драйверы режима ядра и системный код на диск, что является предпосылкой для получения 64-битных стеков вызовов с использованием xperf, поскольку ходьба по 64-битным стекам зависит от метаданных в исполняемых изображениях, а в некоторых ситуациях xperf код стека стека не разрешается касаться выгружаемых страниц. Выполнение следующей команды из командной строки с повышенными правами приведет к установке этого раздела реестра.
REG ADD "HKLM\System\CurrentControlSet\Control\Session Manager\Memory Management" -v
DisablePagingExecutive -d 0x1 -t REG_DWORD -f
После установки этого раздела реестра вам необходимо перезагрузить систему, прежде чем вы сможете записывать стеки вызовов. Наличие этого флага означает, что ядро Windows блокирует больше страниц в ОЗУ, поэтому это, вероятно, будет потреблять около 10 МБ дополнительной физической памяти.
Это создает впечатление, что в 64-битной Windows (и только в 64-разрядной Windows) вам не разрешается ходить с ядрами, потому что на диске могут быть страницы.
Ответы
Ответ 1
Я разработчик, который написал это исправление loooooooong времени назад, а также сообщение в блоге. Основная причина заключается в том, что полный файл регистра не всегда захватывается при переходе в пространство ядра по соображениям производительности.
Если вы делаете обычный syscall, x64 Application Binary Interface (ABI) требует только сохранения не- volatile registers (аналогично выполнению обычного вызова функции). Однако, правильно разматывая исключение, вы должны иметь все регистры, поэтому это невозможно. В принципе, это был выбор между первичным в критическом сценарии (т.е. Сценарий, который потенциально происходит тысячи раз в секунду) против 100% правильной обработки патологического сценария (авария).
Чтение бонусов
Ответ 2
Очень хороший вопрос.
Я могу дать понять, почему "распространение" исключения на границе ядра и пользователя несколько проблематично.
Цитата из вашего вопроса:
Почему не может 64-разрядная Windows разворачивать стек во время исключения, если стек пересекает границу ядра - когда 32-разрядная Windows может?
Причина очень проста: нет такой вещи, как "стек пересекает границу ядра". Вызов функции режима ядра никоим образом не сопоставим со стандартным вызовом функции. Фактически это не имеет никакого отношения к стеку вызовов. Как вы, вероятно, знаете, память в режиме ядра просто недоступна из пользовательского режима.
Вызов функции режима ядра (aka syscall) реализуется путем запуска программного прерывания (или аналогичного механизма). Код пользовательского режима помещает некоторые значения в регистры (которые идентифицируют нужный сервис режима ядра) и вызывает инструкцию ЦП (например, sysenter
), которая передает ЦП в режим ядра и передает управление ОС.
Затем появляется код режима ядра, который обрабатывает запрошенный syscall. Он работает в отдельном стеке режима ядра (что не имеет ничего общего с стеком пользовательского режима). После обработки запроса - элемент управления возвращается в код режима пользователя. В зависимости от конкретного syscall адрес возврата в пользовательский режим может быть тем, который вызывается транзакцией режима ядра, а также может быть другим адресом.
Иногда вы вызываете функцию режима ядра, которая "посередине" должна вызывать вызов пользовательского режима. Он может выглядеть как стек вызовов, состоящий из кода пользователя-пользователя-пользователя, но это всего лишь эмуляция . В этом случае код режима ядра передает управление в код пользовательского режима, который обертывает вашу функцию пользовательского режима. Этот код обертки вызывает вашу функцию, и сразу же после его возврата запускается транзакция в режиме ядра.
Теперь, если код режима пользователя, "вызванный из kernelmode", вызывает исключение - вот что должно произойти:
- Код режима пользовательского режима оболочки обрабатывает исключение SEH (т.е. останавливает его распространение, но еще не выполняет раскрутку стека).
- Пропускает элемент управления в режиме ядра (ОС), как в случае с обычной программой.
- Код режима Kenrel отвечает соответствующим образом. Он завершает запрашиваемую услугу. В зависимости от того, было ли исключение пользовательского режима - обработка может быть различной.
- По возвращении в пользовательский режим - код режима ядра может указывать, было ли вложенное исключение. В случае исключения стек не восстанавливается в исходное состояние (поскольку еще не было разматывания).
- Код режима пользователя проверяет, было ли такое исключение. Если бы это было - стек вызовов был подделан для включения вложенного вызова режима пользователя, и исключение распространяется.
Таким образом, исключение, пересекающее границу ядра и пользователя, является эмулятором . Ничего подобного нет.