如果堆栈跨越内核边界,为什么 64 位 Windows 不能在异常期间展开堆栈 - 而 32 位 Windows 可以?
整个问题的背景来自:
背景
在 32 位 Windows 中,如果我在我的用户模式代码中抛出异常,该异常是从内核模式代码回调的,它是从我的用户模式代码调用的,例如:
User mode Kernel Mode
------------------ -------------------
CreateWindow(...); ------> NtCreateWindow(...)
|
WindowProc <---------------------+
Windows 中的结构化异常处理 (SEH) 可以展开堆栈,通过内核模式展开,回到我的用户代码,在那里我可以处理异常并且我看到有效的堆栈跟踪。
但不是在 64 位 Windows 中
64 位版本的 Windows 不能这样做:
由于复杂的原因,我们无法将异常传播回 64 位操作系统(amd64 和 IA64)。自从 Server 2003 的第一个 64 位版本以来,情况就一直如此。在 x86 上,情况并非如此——异常通过内核边界传播,最终将帧返回
而且由于在这种情况下无法返回可靠的堆栈跟踪,因此必须做出决定:让您看到无意义的异常,或者完全隐藏它:
当时的内核架构师决定采取保守的 AppCompat-friendly 方法——隐藏异常,并寄希望于最好的结果。
本文继续讨论所有 64 位 Windows 操作系统的行为方式:
- Windows XP 64 位
- Windows Server 2003 64 位
- Windows Vista 64 位
- Windows Server 2008 64 位
但从 Windows 7(和 Windows Server 2008)开始,架构师改变了他们的想法——在某种程度上。仅对于64 位应用程序(不是 32 位应用程序),它们将(默认情况下)停止抑制这些用户-内核-用户异常。因此,默认情况下,打开:
- Windows 7 64 位
- 视窗服务器 2008
所有 64 位应用程序都会看到这些异常,它们以前从未见过它们。
在 Windows 7 中,当原生 x64应用程序以这种方式崩溃时,程序兼容性助手会收到通知。如果应用程序没有Windows 7 清单,我们会显示一个对话框,告诉您 PCA 已应用应用程序兼容性 shim。这是什么意思?这意味着,下次您运行应用程序时,Windows 将模拟 Server 2003 的行为并使异常消失。请记住,Server 2008 R2 上不存在 PCA,因此此建议不适用。
所以这个问题
问题是为什么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注册表项。这告诉操作系统不要将内核模式驱动程序和系统代码分页到磁盘,这是使用 xperf 获取 64 位调用堆栈的先决条件,因为 64 位堆栈遍历依赖于可执行映像中的元数据,并且在某些情况下xperf堆栈遍历代码不允许触摸分页页面。从提升的命令提示符运行以下命令将为您设置此注册表项。
REG ADD "HKLM\System\CurrentControlSet\Control\Session Manager\Memory Management" -v DisablePagingExecutive -d 0x1 -t REG_DWORD -f
设置此注册表项后,您需要重新启动系统才能记录调用堆栈。设置此标志意味着 Windows 内核将更多页面锁定到 RAM 中,因此这可能会消耗大约 10 MB 的额外物理内存。
这给人的印象是,在 64 位 Windows 中(并且仅在 64 位 Windows 中),您不允许遍历内核堆栈,因为磁盘上可能有页面输出。