Почему 64-разрядные Windows не могут разматывать исключения пользователя-ядра-пользователя?
Почему 64-разрядная Windows не может размотать стек во время исключения, если стек пересекает границу ядра - когда 32-разрядная Windows может?
контекст всего этого вопроса исходит из:
случай исчезающего исключения OnLoad-исключения обратного вызова в пользовательском режиме в x64
фон
в 32-битных окнах, если я создам исключение в моем режим пользователя код, который был вызван из ядра режим код, который был вызван из моего режим пользователя код, электронная.г:
User mode Kernel Mode
------------------ -------------------
CreateWindow(...); ------> NtCreateWindow(...)
|
WindowProc <---------------------+
структурированная обработка исключений (SEH) в Windows может разматывать стек, разматываясь через режим ядра, обратно в мой пользовательский код, где я могу обрабатывать исключение, и я вижу допустимую трассировку стека.
но не в 64-битных Windows
64-разрядные версии Windows не могут этого сделать:
по сложным причинам, мы не может распространяться на исключение для 64-разрядных операционных систем (amd64 и IA64). Это происходит с момента первого 64-разрядного выпуска Server 2003. На x86 это не так – исключение распространяется через границу ядра и в конечном итоге будет проходить кадры назад
и поскольку в этом случае нет возможности вернуться к надежной трассировке стека, пришлось принять решение: пусть вы увидите несущественное исключение или спрячете его полностью:
архитекторы ядра в то время решили принять консервативный AppCompat-дружественный подход – скрыть исключение и надеяться на лучшее.
далее в статье рассказывается о том, как вели себя все 64-разрядные операционные системы Windows:
- Windows XP 64-бит
- 64-разрядная версия Windows Server 2003
- 64-разрядная версия Windows Vista
- 64-разрядная версия Windows Server 2008
но начиная с Windows 7 (и Windows Server 2008), архитекторы передумали - своего рода. Для только 64-разрядные приложения (не 32-разрядные приложения), они будут (по умолчанию) остановка подавление этих исключений user-kernel-user. Так, по умолчанию:
- Windows 7 64-бит
- Windows Server 2008
все 64-разрядные приложения будут посмотреть эти исключения, где они никогда не видели их.
В Windows 7, когда для x64 сбой приложения таким образом,Помощник По Совместимости Программ уведомления. Если приложение не было!--76-->Манифест Windows 7, мы показываем диалог, сообщающий вам, что PCA применил совместимость приложений ШИМ. Что это значит? Это означает, что при следующем запуске приложения Windows будет эмулировать поведение сервера 2003 и сделает исключение исчезает. Имейте в виду, что PCA не существует на сервере 2008 R2, поэтому этот совет не применяется.
поэтому вопрос
вопрос почему 64-разрядная Windows не может размотать стек обратно через переход ядра,в то время как 32-разрядные версии Windows могут?
единственный намек-это:
по сложным причинам, мы не удается распространить исключение обратно на 64-бит операционные системы (amd64 и IA64).
подсказка это сложно.
Я не могу понять объяснение, так как я не разработчик операционной системы, но я хотел бы знать, почему.
обновление: исправление для остановки подавления 32-разрядных приложений
Microsoft выпустила a исправления включает 32-разрядные приложения также больше не имеют исключений подавлено:
KB976038: исключения, которые выбрасываются из приложения, которое работает в 64-разрядной версии Windows, игнорируются
- исключение, возникающее в подпрограмме обратного вызова, выполняется в пользовательском режиме.
в этом случае это исключение не приводит к сбою приложения. Вместо этого приложение переходит в несогласованное состояние. Затем приложение создает другое исключение и аварийно завершает работу.
функция обратного вызова пользовательского режима обычно определяется приложением и вызывается компонентом режима ядра. Примерами функций обратного вызова пользовательского режима являются процедуры Windows и процедуры подключения. Эти функции вызываются Windows для обработки сообщений Windows или событий Windows hook.
исправление позволяет остановить Windows от употребления исключений по всему миру:
HKLMSOFTWAREMicrosoftWindows NTCurrentVersionImage File Execution Options DisableUserModeCallbackFilter: DWORD = 1
или per-application:
HKLMSOFTWAREMicrosoftWindows NTCurrentVersionImage File Execution OptionsNotepad.exe DisableUserModeCallbackFilter: DWORD = 1
поведение также было задокументировано на XP и Server 2003 в KB973460:
подсказки
Я нашел еще один намек при исследовании использования xperf для захвата трассировок стека на 64-бит Windows:
стек ходить в Xperf
Отключить Paging Executive
для того, чтобы трассировка работала на 64-битных Windows, вам нужно установить DisablePagingExecutive ключ реестра. Это говорит операционной системе не выводить драйверы режима ядра и системный код на диск, что является необходимым условием для получения 64-разрядных стеков вызовов с помощью xperf, поскольку ходьба 64-разрядного стека зависит от метаданных в исполняемые изображения, а в некоторых ситуациях xperf stack walk code не разрешается касаться выгружаемых страниц. Выполнение следующей команды из командной строки с повышенными правами установит этот раздел реестра для вас.
REG ADD "HKLMSystemCurrentControlSetControlSession ManagerMemory Management" -v DisablePagingExecutive -d 0x1 -t REG_DWORD -f
после установки этого раздела реестра вам нужно будет перезагрузить систему, прежде чем вы сможете записывать стеки вызовов. Наличие этого флага означает, что ядро Windows блокирует больше страниц в ОЗУ, поэтому это, вероятно, потребляет около 10 МБ дополнительных физическая память.
создается впечатление, что в 64-разрядных окнах (и только в 64-разрядных окнах) вам не разрешено ходить по стекам ядра, потому что на диске могут быть страницы.
2 ответов
Я разработчик, который написал это исправление loooooooong время назад, а также в блоге. Основная причина заключается в том, что полный файл реестра не всегда захватывается при переходе в пространство ядра по соображениям производительности.
Если вы делаете обычный syscall, x64 Двоичный Интерфейс Приложения (ABI) требуется только сохранить энергонезависимые регистры (аналогично вызову обычной функции). Однако правильно разматывать исключение требует, чтобы вы все регистры, так что это не возможно. В принципе, это был выбор между perf в критическом сценарии (т. е. сценарии, который потенциально происходит тысячи раз в секунду) и 100% правильно обрабатывать патологический сценарий (сбой).
Бонус Чтение
очень хороший вопрос.
Я могу дать подсказку, почему "распространение" исключения через границу ядра-пользователя несколько проблематично.
цитата из вашего вопроса:
почему 64-разрядная Windows не может размотать стек во время исключения, если стек пересекает границу ядра - когда 32-разрядная Windows может?
причина очень проста: нет такой вещи, как "стек пересекает границу ядра". Зовущий функция режима ядра никоим образом не сопоставима со стандартным вызовом функции. На самом деле это не имеет ничего общего со стеком вызовов. Как вы, вероятно, знаете, память в режиме ядра просто недоступна из пользовательского режима.
вызов функции режима ядра (aka syscall) реализуется путем запуска программного прерывания (или аналогичного механизма). Код пользовательского режима помещает некоторые значения в регистры (которые идентифицируют необходимую службу режима ядра) и вызывает инструкцию CPU (например,sysenter
), который переводит процессор в режим ядра и передает управление ОС.
затем есть код режима ядра, который обрабатывает запрошенный syscall. Он работает в отдельном стеке режима ядра (который не имеет ничего общего со стеком пользовательского режима). После обработки запроса-элемент управления возвращается в код пользовательского режима. В зависимости от конкретного syscall адрес возврата пользовательского режима может быть тем, который вызвал транзакцию в режиме ядра, а также может быть разные адреса.
иногда вы вызываете функцию режима ядра, которая "посередине" должна вызывать вызов пользовательского режима. Это может выглядеть как стек вызовов, состоящий из кода user-kernel-user, но это просто эмулятор. В этом случае код режима ядра передает элемент управления в код пользовательского режима, который обертывает функцию пользовательского режима. Этот код оболочки вызывает вашу функцию и сразу же после ее возврата запускает транзакцию в режиме ядра.
теперь, если код пользовательского режима "вызывается из kernelmode" вызывает исключение - вот что должно произойти:
- код пользовательского режима оболочки обрабатывает исключение SEH (т. е. останавливает его распространение, но еще не выполняет размотку стека).
- передает управление в режим ядра (ОС), как в обычном случае потока программы.
- код Kenrel-mode отвечает соответствующим образом. Он завершает запрошенную услугу. В зависимости от того, было ли исключение пользовательского режима- обработка может быть разной.
- при возвращении в пользовательский режим-код режима ядра может указать, было ли вложенное исключение. В случае исключения стек не восстанавливается в исходное состояние (так как еще не было размотки).
- код пользовательского режима проверяет, было ли такое исключение. Если это было - стек вызовов подделывается для включения вложенного вызова пользовательского режима, и исключение распространяется.
Так что исключение, которое пересекает kernel-user граница-это эмулятор. Нет такой вещи изначально.