Ian*_*oyd 37 windows 64-bit structured-exception windows64 windows-appcompat-platform
为什么64位Windows在异常期间不能展开堆栈,如果堆栈跨越内核边界 - 当32位Windows可以?
整个问题的背景来自:
消失的OnLoad异常 - x64中的用户模式回调异常的情况
在32位Windows中,如果我在我的用户模式代码中抛出异常,那是从内核模式代码调用的,这是从我的用户模式代码调用的,例如:
User mode Kernel Mode
------------------ -------------------
CreateWindow(...); ------> NtCreateWindow(...)
|
WindowProc <---------------------+
Run Code Online (Sandbox Code Playgroud)
Windows中的结构化异常处理(SEH)可以展开堆栈,通过内核模式展开回到我的用户代码,在那里我可以处理异常,并且我看到有效的堆栈跟踪.
64位版本的Windows无法执行此操作:
由于复杂的原因,我们无法在64位操作系统(amd64和IA64)上传播异常.自从Server 2003的第一个64位版本发布以来,情况一直如此.在x86上,情况并非如此 - 异常通过内核边界传播,并最终将帧移回
由于在这种情况下无法回溯可靠的堆栈跟踪,因此必须做出决定:让您看到非荒谬的异常,或者完全隐藏它:
当时的内核架构师决定采用保守的AppCompat友好方法 - 隐藏异常,并希望最好.
本文接着讨论了所有64位Windows操作系统的表现如何:
但是从Windows 7(和Windows Server 2008)开始,架构师改变了主意 - 有点像.对于仅 64位应用程序(不是32位应用程序),它们(默认情况下)会停止抑制这些用户内核用户异常.所以,默认情况下,在:
所有64位应用程序都会看到这些异常,他们从来没有看到它们.
在Windows 7中,当本机x64应用程序以这种方式崩溃时,将通知程序兼容性助手.如果应用程序没有Windows 7清单,我们会显示一个对话框,告诉您PCA已应用了应用程序兼容性填充程序.这是什么意思?这意味着,下次运行应用程序时,Windows将模拟Server 2003行为并使异常消失.请记住,Server 2008 R2上不存在PCA,因此该建议不适用.
问题是为什么 64位Windows无法通过内核转换解除堆栈,而32位版本的Windows可以?
唯一的提示是:
由于复杂的原因,我们无法在64位操作系统(amd64和IA64)上传播异常.
暗示是复杂的.
我可能不理解这个解释,因为我不是一个操作系统开发人员 - 但我想知道为什么会这样做.
微软发布了一个修复程序,使32位应用程序也不再有被抑制的异常:
KB976038:忽略从64位版本的Windows中运行的应用程序引发的异常
- 在回调例程中引发的异常在用户模式下运行.
在这种情况下,此异常不会导致应用程序崩溃.相反,应用程序进入不一致状态.然后,应用程序抛出一个不同的异常并崩溃.
用户模式回调函数通常是由内核模式组件调用的应用程序定义的函数.用户模式回调函数的示例是Windows过程和挂钩过程.Windows调用这些函数来处理Windows消息或处理Windows挂钩事件.
然后,此修补程序可让您阻止Windows全局使用异常:
Run Code Online (Sandbox Code Playgroud)HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options DisableUserModeCallbackFilter: DWORD = 1
或每次申请:
Run Code Online (Sandbox Code Playgroud)HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\Notepad.exe DisableUserModeCallbackFilter: DWORD = 1
在KB973460中的XP和Server 2003上也记录了这种行为:
我在调查使用xperf捕获64位Windows上的堆栈跟踪时发现了另一个提示:
禁用分页执行
要使跟踪在64位Windows上运行,您需要设置DisablePagingExecutive注册表项.这告诉操作系统不要将内核模式驱动程序和系统代码分页到磁盘,这是使用xperf获取64位调用堆栈的先决条件,因为64位堆栈行走取决于可执行映像中的元数据,在某些情况下xperf 堆栈遍历代码不允许触摸分页页面.从提升的命令提示符运行以下命令将为您设置此注册表项.
Run Code Online (Sandbox Code Playgroud)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中)中,不允许您遍历内核堆栈,因为磁盘上可能存在页面.
Ana*_*tts 15
我是开发人员之前写过这个Hotfix的loooooooong以及博客文章.主要原因是,出于性能原因,当您转换到内核空间时,并不总是捕获完整的寄存器文件.
如果进行正常的系统调用,x64 应用程序二进制接口(ABI)只需要保留非易失性寄存器(类似于进行正常的函数调用).但是,正确解除异常需要您拥有所有寄存器,因此无法实现.基本上,这是在关键场景中的perf(即可能每秒发生数千次的场景)与100%正确处理病态场景(崩溃)之间的选择.
一个非常好的问题.
我可以给出一个暗示为什么跨内核用户边界"传播"异常有些问题.
引用你的问题:
为什么64位Windows在异常期间不能展开堆栈,如果堆栈跨越内核边界 - 当32位Windows可以?
原因很简单:没有"堆栈跨越内核边界"这样的东西.调用内核模式函数绝不能与标准函数调用相比.它实际上与调用堆栈无关.您可能知道,内核模式内存在用户模式下无法访问.
调用内核模式函数(也称为syscall)是通过触发软件中断(或类似机制)来实现的.用户模式代码将一些值放入寄存器(标识所需的内核模式服务)并调用CPU指令(例如sysenter),该指令将CPU传输到内核模式并将控制传递给OS.
然后是一个处理请求的系统调用的内核模式代码.它在一个单独的内核模式堆栈中运行(与用户模式堆栈无关).处理完请求后 - 控件返回到用户模式代码.根据特定的系统调用,用户模式返回地址可以是调用内核模式事务的地址,也可以是不同的地址.
有时你会调用一个"中间"应该调用用户模式调用的内核模式函数.它可能看起来像一个由用户内核用户代码组成的调用堆栈,但它只是一个仿真.在这种情况下,内核模式代码将控件传输到用户模式代码,该代码包装您的用户模式功能.此包装器代码调用您的函数,并在其返回时立即触发内核模式事务.
现在,如果用户模式代码"从内核模式调用"引发了异常 - 这就应该发生:
因此跨越内核用户边界的异常是一种仿真.本机没有这样的东西.