为什么不能在64位Windows放松用户内核用户异常?(Why can't 64-bit W

2019-06-26 03:19发布

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

这整个问题的背景来自于:

在消失的OnLoad异常的情况下-在64位用户模式回调异常

背景

在32位Windows,如果我在用户模式下的代码抛出一个异常,这是从内核模式代码打电话回来,这是从我的用户模式代码,例如叫:

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

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

但不是在64位Windows

Windows的64位版本不能做到这一点:

对于复杂的原因,我们不能传播异常回到64位操作系统 (AMD64和IA64)。 这已经是自2003年服务器在x86的第64位版本不断的情况下,这种情况并非如此 - 除了获得通过内核的边界而传播,并最终会走在后面的帧

而且,由于没有办法往回走了可靠的堆栈跟踪在这种情况下,不得不做出一个决定:让你看到不无意义异常,或者干脆隐藏它:

当时的内核架构师决定采取保守的程序兼容性友好的方式 - 隐藏例外,并希望最好的。

文章接着说说如何,这是所有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的应用程序崩溃, 程序兼容性助手通知。 如果应用程序没有Windows 7的清单 ,我们展示了一个对话框,告诉你,PCA已申请了应用程序兼容性垫片 。 这是什么意思? 这意味着,在下次运行应用程序时,Windows会效仿Server 2003的行为,使异常消失。 请记住,PCA不能在Server 2008 R2的存在,所以这个建议并不适用。

所以问题

问题是,为什么是64位Windows无法放松堆栈回通过内核过渡, 而Windows的32位版本可以吗?

唯一的线索是:

对于复杂的原因,我们不能传播异常回到64位操作系统 (AMD64和IA64)。

该提示是很复杂的

我可能不明白的解释,因为我不是一个操作系统开发者 - 但我想射击在知道为什么。


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

微软已经发布了一个修补程序使32位应用程序也不再具有抑制异常:

KB976038:这是从在Windows的64位版本上运行的应用程序引发的异常被忽略

  • 是抛出一个回调例程运行在用户模式下的异常。

在这种情况下,这种异常不会导致应用程序崩溃。 相反,应用程序进入不一致的状态。 然后,应用程序将引发一个不同的异常和崩溃。

用户模式回调函数通常是由内核模式组件调用的应用程序定义的函数。 用户模式回调函数的例子都是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:

  • 这是从64位应用程序引发的异常是在Windows Server 2003的Windows XP专业版或64位版本上运行会被忽略

一个提示

我发现了另一个提示使用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),你不能走内核堆栈,因为有可能是页面出在磁盘上。

Answer 1:

我是谁一loooooooong时间前写此修复程序的开发者以及博客文章。 最主要的原因是,当你过渡到内核空间,出于性能考虑全寄存器文件并不总是捕获。

如果你做一个正常的系统调用,在64位应用程序二进制接口 (ABI)只需要你保持非易失性寄存器 (类似于做一个正常的函数调用)。 然而,正确解开例外需要你有所有的寄存器,所以这是不可能的。 基本上,这是一个关键的场景PERF之间的选择(即,潜在地发生每秒数千次的情形)与100%正确处理病理情形(崩溃)。

奖金阅读

  • 64位调用约定的概述
  • 86软件规范-注册使用


Answer 2:

一个很好的问题。

我可以给的,为什么“传播”跨越内核用户界面的例外是有点问题的提示。

从你的问题引用:

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

原因很简单:没有,如“叠跨越边界的内核”的事情。 调用内核模式功能绝不相当于一个标准的函数调用。 它无关,与实际调用堆栈。 正如你可能知道,内核模式内存是简单地从用户模式下无法使用。

调用一个内核模式功能(又名系统调用 )通过触发一个软件中断(或类似的机制)来实现。 用户模式代码把一些值到寄存器(即识别所需的内核模式的服务),并调用一个CPU指令(例如sysenter其传输的CPU进入内核模式,并传递控制到OS)。

此外,还有一个处理请求的系统调用内核模式代码。 它运行在一个单独的内核模式堆栈(即没有任何与用户模式堆栈来做)。 该请求被处理之后 - 控制被返回到用户模式代码。 根据特定的系统调用用户模式返回地址可能是调用内核模式的交易之一,也是因为它可能是不同的地址。

有时候,你称之为“中间”应该调用用户模式调用内核模式功能。 它可能看起来像由一个用户内核用户代码调用堆栈,但它只是一个仿真 。 在这种情况下,内核模式代码传送控制到它包装您的用户模式功能的用户模式代码。 此包装代码调用你的函数,并在它的回归立即触发内核模式交易。

现在,如果用户模式代码“从内核模式调用”引发了异常 - 这是应该发生的事情:

  1. 该包装的用户模式代码处理SEH异常(即停止其传播,但不执行堆栈展开尚未)。
  2. 传递控制到内核模式(OS),如在正常的程序流的情况下。
  3. Kenrel模式代码适当地响应。 它完成所要求的服务。 根据是否有一个用户模式的例外 - 的处理可能会有所不同。
  4. 在返回给用户模式 ​​- 如果有一个嵌套异常的内核模式代码可以指定。 在一个异常堆栈不会恢复到其原始状态的情况下(由于没有尚未退绕)。
  5. 用户模式代码检查是否有这样一个例外。 如果是 - 调用栈被伪造以包括嵌套的用户模式调用,异常传播。

所以跨越内核用户界面该异常是一个仿真设备。 有没有这样的事情本身。



文章来源: Why can't 64-bit Windows unwind user-kernel-user exceptions?