为什么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 – 这是应该发生的事情:

  1. 包装用户模式代码处理SEHexception(即停止其传播,但不执行堆栈展开)。
  2. 将控制权交给内核模式(OS),就像在正常的程序stream程中一样。
  3. Kenrel模式代码适当地响应。 它完成所请求的服务。 取决于是否存在用户模式exception – 处理可能不同。
  4. 在返回到用户模式时,内核模式代码可以指定是否存在嵌套的exception。 在发生exception的情况下,堆栈未恢复到原始状态(因为还没有退卷)。
  5. 用户模式代码检查是否有这样的exception。 如果是 – 调用堆栈被伪造成包含嵌套的用户模式调用,并且exception传播。

所以跨越内核用户边界的exception是一个仿真 。 本地没有这样的事情。