Подтвердить что ты не робот

Почему 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 Vista​​li >
  • 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:

Прохождение стека в Xperf

Отключить пейджинговый исполнитель

Чтобы трассировка работала на 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) вам не разрешается ходить с ядрами, потому что на диске могут быть страницы.

4b9b3361

Ответ 1

Я разработчик, который написал это исправление loooooooong времени назад, а также сообщение в блоге. Основная причина заключается в том, что полный файл регистра не всегда захватывается при переходе в пространство ядра по соображениям производительности.

Если вы делаете обычный syscall, x64 Application Binary Interface (ABI) требует только сохранения не- volatile registers (аналогично выполнению обычного вызова функции). Однако, правильно разматывая исключение, вы должны иметь все регистры, поэтому это невозможно. В принципе, это был выбор между первичным в критическом сценарии (т.е. Сценарий, который потенциально происходит тысячи раз в секунду) против 100% правильной обработки патологического сценария (авария).

Чтение бонусов

Ответ 2

Очень хороший вопрос.

Я могу дать понять, почему "распространение" исключения на границе ядра и пользователя несколько проблематично.

Цитата из вашего вопроса:

Почему не может 64-разрядная Windows разворачивать стек во время исключения, если стек пересекает границу ядра - когда 32-разрядная Windows может?

Причина очень проста: нет такой вещи, как "стек пересекает границу ядра". Вызов функции режима ядра никоим образом не сопоставим со стандартным вызовом функции. Фактически это не имеет никакого отношения к стеку вызовов. Как вы, вероятно, знаете, память в режиме ядра просто недоступна из пользовательского режима.

Вызов функции режима ядра (aka syscall) реализуется путем запуска программного прерывания (или аналогичного механизма). Код пользовательского режима помещает некоторые значения в регистры (которые идентифицируют нужный сервис режима ядра) и вызывает инструкцию ЦП (например, sysenter), которая передает ЦП в режим ядра и передает управление ОС.

Затем появляется код режима ядра, который обрабатывает запрошенный syscall. Он работает в отдельном стеке режима ядра (что не имеет ничего общего с стеком пользовательского режима). После обработки запроса - элемент управления возвращается в код режима пользователя. В зависимости от конкретного syscall адрес возврата в пользовательский режим может быть тем, который вызывается транзакцией режима ядра, а также может быть другим адресом.

Иногда вы вызываете функцию режима ядра, которая "посередине" должна вызывать вызов пользовательского режима. Он может выглядеть как стек вызовов, состоящий из кода пользователя-пользователя-пользователя, но это всего лишь эмуляция . В этом случае код режима ядра передает управление в код пользовательского режима, который обертывает вашу функцию пользовательского режима. Этот код обертки вызывает вашу функцию, и сразу же после его возврата запускается транзакция в режиме ядра.

Теперь, если код режима пользователя, "вызванный из kernelmode", вызывает исключение - вот что должно произойти:

  • Код режима пользовательского режима оболочки обрабатывает исключение SEH (т.е. останавливает его распространение, но еще не выполняет раскрутку стека).
  • Пропускает элемент управления в режиме ядра (ОС), как в случае с обычной программой.
  • Код режима Kenrel отвечает соответствующим образом. Он завершает запрашиваемую услугу. В зависимости от того, было ли исключение пользовательского режима - обработка может быть различной.
  • По возвращении в пользовательский режим - код режима ядра может указывать, было ли вложенное исключение. В случае исключения стек не восстанавливается в исходное состояние (поскольку еще не было разматывания).
  • Код режима пользователя проверяет, было ли такое исключение. Если бы это было - стек вызовов был подделан для включения вложенного вызова режима пользователя, и исключение распространяется.

Таким образом, исключение, пересекающее границу ядра и пользователя, является эмулятором . Ничего подобного нет.