为什么64位Windows无法解除用户内核用户异常?

时间:2012-07-07 16:36:30

标签: windows 64-bit windows64 structured-exception windows-appcompat-platform

为什么64位Windows不能在异常期间展开堆栈,如果堆栈跨越内核边界 - 当32位Windows可以?

整个问题的背景来自:

The case of the disappearing OnLoad exception – user-mode callback exceptions in x64

背景

在32位Windows中,如果我在用户模式代码中抛出异常,那是从内核模式代码中调用的,那是从我的调用的>用户模式代码,例如:

User mode                     Kernel Mode
------------------            -------------------
CreateWindow(...);   ------>  NtCreateWindow(...)
                                   |
WindowProc   <---------------------+                                   

Windows中的结构化异常处理(SEH)可以展开堆栈,通过内核模式展开回到我的用户代码,在那里我可以处理异常并看到有效的堆栈跟踪。

但不是64位Windows

64位版本的Windows无法执行此操作:

  

由于复杂的原因,我们无法在64位操作系统上传播异常(amd64和IA64)。自从Server 2003的第一个64位版本发布以来,情况一直如此。在x86上,情况并非如此 - 异常通过内核边界传播并最终将帧移回框架

由于在这种情况下无法回溯可靠的堆栈跟踪,因此必须做出决定:让您看到非荒谬的异常,或者完全隐藏它:

  

当时的内核架构师决定采取保守的AppCompat友好方法 - 隐藏异常,并希望最好。

本文接着讨论了所有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位
  • Windows Server 2008

所有64位应用程序都会看到这些异常,他们从来没有看到它们。

  

在Windows 7中,当本机x64 应用程序以这种方式崩溃时,会通知Program Compatibility Assistant。如果应用程序没有Windows 7 Manifest,我们会显示一个对话框,告诉您PCA已应用Application Compatibility shim。这是什么意思?这意味着,下次运行应用程序时,Windows将模拟Server 2003行为并使异常消失。请记住,Server 2008 R2上不存在PCA,因此该建议不适用。

所以问题

问题是为什么 64位Windows无法通过内核转换展开堆栈,而32位版本的Windows可以吗?

唯一的提示是:

  

由于复杂的原因,我们无法在64位操作系统上传播异常(amd64和IA64)。

提示它很复杂

我可能不理解这个解释,因为我不是一个操作系统开发人员 - 但我想知道为什么会这样做。


更新:停止抑制32位应用程序的修补程序

Microsoft已发布a hotfix enables 32-bit applications也不再禁止例外:

  

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

KB973460中的XP和Server 2003上也记录了这种行为:


提示

我在调查使用xperf捕获64位Windows上的堆栈跟踪时发现了另一个提示:

Stack Walking in 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)中,不允许您遍历内核堆栈,因为磁盘上可能存在页面。

2 个答案:

答案 0 :(得分:16)

我是时间以前写过这个Hotfix的开发人员以及博客文章。主要原因是,出于性能原因,当您转换到内核空间时,不会始终捕获完整的寄存器文件。

如果进行正常的系统调用,x64 Application Binary Interface(ABI)只需要保留the non-volatile registers(类似于进行正常的函数调用)。但是,正确展开异常需要您拥有所有寄存器,因此无法实现。基本上,这是在关键场景中的perf(即可能每秒发生数千次的场景)与100%正确处理病态场景(崩溃)之间的选择。

奖金阅读

答案 1 :(得分:8)

一个非常好的问题。

我可以给出一个暗示为什么跨内核用户边界“传播”异常有些问题。

引用你的问题:

  

为什么64位Windows在异常期间不能展开堆栈,如果堆栈越过内核边界 - 当32位Windows可以时?

原因很简单:没有“堆栈跨越内核边界”这样的东西。调用内核模式函数绝不能与标准函数调用相比。它实际上与调用堆栈无关。您可能知道,内核模式内存在用户模式下无法访问。

通过触发软件中断(或类似机制)来实现调用内核模式函数(又名 syscall )。用户模式代码将一些值放入寄存器(标识所需的内核模式服务)并调用CPU指令(例如sysenter),该指令将CPU传输到内核模式并将控制传递给OS。

然后有一个内核模式代码来处理请求的系统调用。它在一个单独的内核模式堆栈中运行(与用户模式堆栈无关)。处理完请求后 - 控件返回到用户模式代码。根据特定的系统调用,用户模式返回地址可能是调用内核模式事务的地址,也可能是不同的地址。

有时你会调用一个“中间”应该调用用户模式调用的内核模式函数。它可能看起来像一个由用户内核用户代码组成的调用堆栈,但它只是一个仿真。在这种情况下,内核模式代码将控件传输到用户模式代码,该代码包装您的用户模式功能。这个包装器代码调用你的函数,并在它返回时立即触发内核模式事务。

现在,如果用户模式代码“从kernelmode调用”引发异常 - 这就应该发生:

  1. 包装器用户模式代码处理SEH异常(即停止其传播,但尚未执行堆栈展开)。
  2. 将控件传递给内核模式(OS),就像在正常的程序流程中一样。
  3. Kenrel模式代码适当地响应。它完成了所请求的服务。取决于是否存在用户模式异常 - 处理可能不同。
  4. 返回用户模式后,内核模式代码可以指定是否存在嵌套异常。如果发生异常,堆栈不会恢复到其原始状态(因为还没有展开)。
  5. 用户模式代码检查是否存在此类异常。如果是 - 调用堆栈被伪造成包含嵌套的用户模式调用,并且异常传播。
  6. 因此跨越内核 - 用户边界的异常是仿真。本机没有这样的东西。