虫趣:拯救Visual Studio 2012(错误: 0xC01E0009)

欢迎转载:作者:张佩】【原文:http://www.yiiyee.cn/Blog/vs-1/

引子

今天运行一款很流行的第三方驱动更新软件(名称省略)时,提示我的显卡驱动需要更新。打开设备管理器一看,发现当前使用的这一版显卡驱动还是2012年的。果然是够老的。果断选择下载并安装。安装结束后,我没有选择立刻重启系统,因为还有一大堆工作需要继续。

一切都很正常地进行着,直到我启动了Visual Studio 2012。Splash画面持续了5秒钟之后,弹出程序崩溃对话框,并开始收集错误,然后弹出新的对话框,问我是否向微软提交本次崩溃信息。选择拒绝后,程序直接关闭。试过几次,都是如此。

寻找问题

在此之前,Visual Studio 2012一直很稳健地在我的系统里面运行着。这忽然怎么了?我百思不得其解。于是果断上调试器。在调试器中打开Visual Studio 2012的执行文件,路径是:

c:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\Devenv.exe。

运行起来,不过五秒,调试器断住了。细看它的调用栈:

0:010:x86> kc
KERNELBASE!DebugBreak
d3d9!NTStatusToHResult
d3d9!RenderCB
WARNING: Stack unwind information not available....may be wrong.
igdumdim32!OpenAdapter
igdumdim32!OpenAdapter
d3d9!CreateDeviceLHDDI
d3d9!D3D9CreateDirectDrawObject
d3d9!FetchDirectDrawData
d3d9!InternalDirectDrawCreate
d3d9!CEnum::CreateDeviceImpl
d3d9!CEnum::CreateDeviceEx
wpfgfx_v0400!CD3DDeviceManager::CreateNewDevice
wpfgfx_v0400!CD3DDeviceManager::GetD3DDeviceAndPresentParams
wpfgfx_v0400!CHwDisplayRenderTarget::Create
wpfgfx_v0400!CDesktopRenderTarget::Init
wpfgfx_v0400!CDesktopRenderTarget::Create
wpfgfx_v0400!CMILFactory::CreateDesktopRenderTarget
wpfgfx_v0400!CSlaveHWndRenderTarget::EnsureRenderTargetInternal
wpfgfx_v0400!CSlaveHWndRenderTarget::Render
wpfgfx_v0400!CPartitionManager::LogEvent
wpfgfx_v0400!CComposition::Render
wpfgfx_v0400!CComposition::ProcessComposition
wpfgfx_v0400!CComposition::Compose
wpfgfx_v0400!CPartitionThread::RenderPartition
wpfgfx_v0400!CPartitionThread::Run
wpfgfx_v0400!CPartitionThread::ThreadMain
kernel32!BaseThreadInitThunk
ntdll_77a90000!__RtlUserThreadStart
ntdll_77a90000!_RtlUserThreadStart

这个调用栈可以分成两个部分,首先它是一个WPF线程,WPF是Vista以后出现的基于.net和DirectX的图形库。可见VS的界面是基于WPF的。然后它使用了D3D9接口,所以可以看到相关的调用接口。中间还调用了Intel用户层3D驱动的接口函数(igdumdim32模块)。

看到它位于栈顶的帧调用了KERNELBASE!DebugBreak函数,这是用来下断点的函数。当执行的时候,系统会寻找活动的调试器,让调试器挂载到出错的程序。查看第二个函数(NTStatusToHResult)的汇编代码,看到这样几句上下文:

537ff5e0 e90b56ffff  jmp     d3d9!NTStatusToHResult+0xd0 (537f4bf0)
537ff5e5 e831170a00  call    d3d9!DetectedDriverMismatch (538a0d1b)
537ff5ea e9a0f40400  jmp     d3d9!NTStatusToHResult+0x81 (5384ea8f)

原来它调用的是函数d3d9!DetectedDriverMismatch。这个函数经过了编译优化,所以在栈上没有显示出来。看下面的实现是怎样的:

0:010:x86> u  d3d9!DetectedDriverMismatch
d3d9!DetectedDriverMismatch:
538a0d1b 68300d8a53      push    offset d3d9!`string' (538a0d30)
538a0d20 ff15e0157f53    call    dword ptr [d3d9!_imp__OutputDebugStringW (537f15e0)]
538a0d26 ff25d8157f53    jmp     dword ptr [d3d9!_imp__DebugBreak (537f15d8)]

确实是它调用了DebugBreak函数,但在调用前,通过OutputDebugStringW输出了一段调试信息。可以看到调试信息的内容:

0:010:x86> du 538a0d30
538a0d30  "User/kernel-mode driver mismatch"

我在调试器的命令窗口中搜索了一下,果然看到有这个调试输出。这个信息的内容还是很明确的,是指“用户和内核驱动不匹配”。到底是什么驱动的用户和内核模块不匹配呢?考虑到当前是一个D3D相关线程,会想到就是显卡驱动。

为了找到原因,有必要去分析d3d9!NTStatusToHResult函数,因为正是它最终调用了d3d9!DetectedDriverMismatch函数。分析它的汇编代码后,发现了下面的逻辑:

537ff5e5 e831170a00  call    d3d9!DetectedDriverMismatch (538a0d1b)
……//省略
5384ea6c 3d09001ec0  cmp     eax,0C01E0009h
5384ea77 0f84680bfbff je     d3d9!NTStatusToHResult+0x7c (537ff5e5)

这段逻辑判断寄存器eax,如果其值等于0C01E0009h,就跳到地址0x537ff5e5 处执行函数DetectedDriverMismatch。寄存器Eax中保存的是当前函数的输入参数。为了查询这个输入参数的由来,我在它的父函数d3d9!RenderCB开始的地方设置了断点,并重新启动devenv.exe程序。跟踪后发现了下面的逻辑:

0:010:x86> uf d3d9!RenderCB

537f65de ff15ac929953  call    dword ptr [d3d9!pfnOsThunkDDIRender]
537f65e4 894508        mov     dword ptr [ebp+8],eax
……//省略
5385a234 8b7508        mov     esi,dword ptr [ebp+8]
5385a24c 56            push    esi
5385a24d e873a9f9ff    call    d3d9!NTStatusToHResult (537f4bc5)

它在调用了函数OsThunkDDIRender后,检查返回值eax,如果eax不等于0,就转入NTStatusToHResult做错误处理。查看此时的eax值:

0:007:x86> r eax
eax=c01e0009

这个值正是NTStatusToHResult中所判断的特征值。用错误查看工具查看c01e0009这个值,发现它对应的描述信息如下:

0xc01e0009(NT_STATUS): The kernel driver detected a version mismatch between it and the user mode driver.(内核驱动程序检测到它和用户模式驱动程序之间的版本不匹配。)

解决办法

问题有点眉目了,Visual Studio使用WPF画图的时候,使用D3D API但是发现用户模块和内核模块的版本已经不匹配了,在这种情况下,3D的用户驱动就拒绝进一步响应3D请求,并调用DebugBreak人为地设置一个断点。

十有八九是更新显卡驱动坏的事!重新启动也没用,甚至系统在重启后运行winsat评估程序的时候,就发生了崩溃。而winsat也是使用D3D9接口的。

现在我有两种选择,一种选择是继续研究,找到驱动更新时到底把哪个文件更新坏了(或漏了)。另外一个选择,就是到显卡厂商的官方网站上,下载官方最新驱动,更新以观其效。对于我来讲,第二种选择显然更划算一些。10分钟之后,驱动下载并安装成功,我仍然没有重启系统,再尝试运行VS,发现已经可以正常启动软件了。

再次使用调试器运行VS的执行程序,在D3D9!RenderCB中调用OsThunkDDIRender的地方下断点,检测其返回值,发现为0——即期望的正确返回值。

我从厂商官方网站下载的驱动版本,和第三方驱动更新软件的驱动版本是完全一样的。为什么会有截然不同的效果呢?其实我的系统里面有两块显卡,一块是Intel的集成显卡(HD4000),一块是独立显卡(Nvidia GTX 670M)。由于Intel的集成显卡装机量非常大,所以AMD和Nvidia都有专门的驱动,针对Intel的集成显卡进行节能优化,让用户选择把指定的3D程序运行在独立显卡或集成显卡上。这样一来,显卡厂商会把同一版驱动,分成几种不同的分发版本:有针对独立显卡的(系统里面只有一块独立显卡),有针对多显卡的(既有独立显卡,也有Intel集成显卡)。后者的驱动安装包中,同时也包含了Intel显卡驱动的部分文件,在安装时进行更新。第三方驱动更新软件如果没有考虑到这一点,用独立显卡驱动包,安装在多显卡系统中,就会导致这样的问题

4,484 total views, 2 views today

《虫趣:拯救Visual Studio 2012(错误: 0xC01E0009)》有一个想法

发表评论

电子邮件地址不会被公开。