为什么64位Windows不能解除用户内核用户exception?
为什么64位Windows在exception期间展开堆栈,如果堆栈穿越内核边界–32位Windows可以?
这整个问题的背景来自:
消失OnLoadexception的情况 – 在x64中的用户模式callbackexception
背景
在32位Windows中,如果我在我的用户模式代码中抛出exception,则从内核模式代码中调用该代码,这是从我的用户模式代码中调用的,例如:
User mode Kernel Mode ------------------ ------------------- CreateWindow(...); ------> NtCreateWindow(...) | WindowProc <---------------------+
Windows中的结构化exception处理(SEH)可以展开堆栈,通过内核模式展开,返回到我的用户代码,在那里我可以处理exception,我看到一个有效的堆栈跟踪。
但不是在64位的Windows
Windows的64位版本不能这样做:
出于复杂的原因,我们不能在64位操作系统 (amd64和IA64) 上传播exception 。 自从Server 2003的第一个64位版本以来,情况一直如此。在x86上,情况并非如此 – exception通过内核边界传播,并最终走回帧
由于在这种情况下无法回溯可靠的堆栈跟踪,所以必须作出决定:让您看到非无意义的exception,或者完全隐藏它:
当时的内核架构师决定采取保守的AppCompat友好的方法 – 隐藏exception,并希望最好的。
本文继续讨论所有64位Windows操作系统的performance如何:
- Windows XP 64位
- Windows Server 2003 64位
- Windows Vista 64位
- Windows Server 2008 64位
但从Windows 7(和Windows Server 2008)开始,架构师改变了他们的想法。 对于只有 64位应用程序(不是32位应用程序),它们(默认情况下)会停止抑制这些用户内核用户exception。 所以,默认情况下,在:
- Windows 7 64位
- Windows Server 2008
所有的64位应用程序都会看到这些例外,他们从来没有看过它们。
在Windows 7中,当本机x64应用程序以这种方式崩溃时,会通知程序兼容性助手 。 如果应用程序没有Windows 7清单 ,我们将显示一个对话框,告诉您PCA已经应用了应用程序兼容性填充 。 这是什么意思? 这意味着,下次运行应用程序时,Windows将模拟Server 2003的行为并使exception消失。 请记住,Server 2008 R2上不存在PCA,因此此build议不适用。
所以这个问题
问题是为什么 64位Windows无法通过内核转换退回堆栈, 而32位版本的Windows可以?
唯一的提示是:
出于复杂的原因,我们不能在64位操作系统 (amd64和IA64) 上传播exception 。
提示是复杂的 。
我可能不明白的解释,因为我不是一个操作系统开发人员 – 但我想知道为什么一枪。
更新:修补程序停止禁止32位应用程序
微软已经发布了一个修补程序,使32位应用程序也不再有例外情况被压制:
KB976038:从在64位版本的Windows中运行的应用程序引发的exception将被忽略
- 在callback例程中引发的exception以用户模式运行。
在这种情况下,此exception不会导致应用程序崩溃。 相反,应用程序进入不一致的状态。 然后,应用程序抛出一个不同的exception并崩溃。
用户模式callback函数通常是由内核模式组件调用的应用程序定义的函数。 用户模式callback函数的例子是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中的行为也logging在KB973460中:
- 从64位版本的Windows Server 2003或Windows XP Professional中运行的64位应用程序抛出的exception将被忽略
一个提示
在调查使用xperf在64位Windows上捕获堆栈跟踪时,我发现了另一个提示:
堆栈走在Xperf
禁用分页执行
为了使跟踪在64位Windows上工作,您需要设置DisablePagingExecutiveregistry项。 这告诉操作系统不要将内核模式驱动程序和系统代码分页到磁盘,这是使用xperf获取64位调用栈的先决条件,因为64位堆栈行走取决于可执行映像中的元数据,在某些情况下xperf 堆栈行走代码不允许触摸分页出页面 。 从提升的命令提示符处运行以下命令将为您设置此registry项。
REG ADD "HKLM\System\CurrentControlSet\Control\Session Manager\Memory Management" -v DisablePagingExecutive -d 0x1 -t REG_DWORD -f
设置此registry项后,您需要重新启动系统,然后才能录制调用堆栈。 设置此标志意味着Windows内核将更多页面locking到RAM中,因此这可能会消耗大约10 MB的额外物理内存。
这给人的印象是,在64位Windows(只有在64位Windows中),你不能走内核栈,因为可能有磁盘页面。
我是开发人员,他写了这个修补程序一个loooooooong前,以及博客文章。 主要原因是出于性能方面的原因,在转换到内核空间时,并不总是捕获全寄存器文件。
如果您进行正常的系统调用,则x64 应用程序二进制接口 (ABI)仅要求您保留非易失性寄存器 (类似于进行正常的函数调用)。 但是,正确解除exception需要你拥有所有的寄存器,所以这是不可能的。 基本上,这是一个关键场景(即每秒可能发生数千次的场景)与100%正确处理病态场景(崩溃)之间的select。
奖金阅读
- x64调用约定概述
- x86软件约定 – registry使用
一个很好的问题。
我可以给出一个暗示,为什么在内核用户边界上“传播”一个exception是有点问题的。
引用你的问题:
为什么64位Windows在exception期间展开堆栈,如果堆栈穿越内核边界 –32位Windows可以?
原因很简单:没有“堆栈越界”这样的事情。 调用一个内核模式函数决不可与一个标准的函数调用相媲美。 它实际上和调用堆栈没有任何关系。 正如您可能知道的,内核模式内存在用户模式下是无法访问的。
调用一个内核模式函数(aka syscall )是通过触发一个软件中断(或类似的机制)来实现的。 用户模式代码将一些值放入寄存器(标识所需的内核模式服务)中,并调用CPU指令(如sysenter
),将CPU传送到内核模式并将控制权交给操作系统。
然后有一个处理请求的系统调用的内核模式代码。 它运行在一个独立的内核模式堆栈中(与用户模式堆栈无关)。 请求处理完成后,控制权将返回到用户模式代码。 根据具体的系统调用,用户模式返回地址可能是调用内核模式事务的地址,也可能是不同的地址。
有时你可以调用一个内核模式的函数,在“中间”应该调用一个用户模式调用。 它看起来像一个由用户内核用户代码组成的调用栈,但这只是一个仿真 。 在这种情况下,内核模式代码将控制权转交给包装用户模式function的用户模式代码。 这个包装代码调用你的函数,并立即返回触发一个内核模式事务。
现在,如果用户模式代码“从核心模式调用”引发exception – 这是应该发生的事情:
- 包装用户模式代码处理SEHexception(即停止其传播,但不执行堆栈展开)。
- 将控制权交给内核模式(OS),就像在正常的程序stream程中一样。
- Kenrel模式代码适当地响应。 它完成所请求的服务。 取决于是否存在用户模式exception – 处理可能不同。
- 在返回到用户模式时,内核模式代码可以指定是否存在嵌套的exception。 在发生exception的情况下,堆栈未恢复到原始状态(因为还没有退卷)。
- 用户模式代码检查是否有这样的exception。 如果是 – 调用堆栈被伪造成包含嵌套的用户模式调用,并且exception传播。
所以跨越内核用户边界的exception是一个仿真 。 本地没有这样的事情。