概述

一次 IIS w3wp 崩溃 dump 的完整分析记录:AjaxPro 同步请求触发业务层 E_FAIL 异常,经 AOP 与 AjaxPro 两层反射 invoke 包装成三层 TargetInvocationException 冒泡至宿主顶层。全程使用 cdb + SOS 完成定位,含 DAC 版本不匹配的实用 workaround。

关键字: 调试技术/windbg 调试技术/dump 编程语言/dotnet/异常 iis/w3wp

现场

项值
进程w3wp.exe(IIS 工作进程),81+ 线程
宿主系统Windows 10 19044 (21H2) x64,12 逻辑处理器
.NET 运行时.NET Framework 4.8,clr.dll 4.8.4614.0(NET48REL1LAST_B),webengine4 4.8.4494.0
业务应用用友 Ufida.T.EAP / Ufida.T.BAP(门户 Portal,AjaxPro AJAX 框架)
触发异常CLR exception e0434352(未处理托管异常),内嵌 HRESULT 0x80131604 = COR_E_TARGETINVOCATION
Dumpminidump,6.3MB(由进程内安装的崩溃收集组件生成)

异常链:三层包裹剥洋葱

dump 记录的最终异常是 COR_E_TARGETINVOCATION,但它只是最外层包装。通过 db 直读异常对象内存 + UTF-16 解码 _stackTraceString,还原出完整三层链:

第 0 层(根因):ExternalException,HResult 0x80004005 (E_FAIL)

在 Ufida.T.BAP.BusinessApplication.PagePreCondition.PagePreCondition.get_Type()
在 Ufida.T.BAP.BusinessApplication.PagePreCondition.PagePreConditionFactory.Create(String url, String code, String[] busOperations, Object[] pars)
在 Ufida.T.BAP.BusinessApplication.PagePreCondition.Service.PagePreConditionService.Validate(String url, String code, String[] busOperations, Object[] pars)

异常抛在业务应用的 PagePreCondition.get_Type() 属性 getter 里。E_FAIL 0x80004005「外部组件发生异常」是 COMException/ExternalException 的默认文案——典型来源是 getter 内部的 COM/interop 调用失败。

第 1 层:AOP 反射包装

System.RuntimeMethodHandle.InvokeMethod → RuntimeMethodInfo.Invoke → MethodBase.Invoke
Ufida.T.EAP.Aop.Util.ServiceMethodResvol.SimpleAutoTranInvoke
Ufida.T.EAP.Aop.Util.ServiceMethodResvol.InvokeNormalMethod
Ufida.T.EAP.Aop.Dynamic.LocalCallDynamicProxyImpl.NormalInvoke / Invoke
System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke
... → Portal.GetMutexInfo → Portal.ReregMutex

应用的 AOP 动态代理通过反射 invoke 调用服务方法,异常被自动包了一层 TargetInvocationException。

第 2 层:AjaxPro 反射包装(dump 记录的最终异常)

AjaxPro.AjaxProcHelper.Run()
AjaxPro.AjaxSyncHttpHandler.ProcessRequest(HttpContext)
System.Web.HttpApplication.*(ExecuteStep/PipelineStepManager.ResumeSteps 等)
System.Web.HttpRuntime.ProcessRequestNotificationPrivate → PipelineRuntime.ProcessRequestNotification(IIS 集成管线)

AjaxPro 的同步 handler 再次反射调用,又包一层。最终异常沿 IIS 集成管线冒泡到宿主顶层,无人 catch。

原生栈(.ecxr)

KERNELBASE!RaiseException+0x69
clr!…(异常分发内部)
webengine4+0x18bf(IIS ASP.NET 宿主)

结论与责任判定

  1. 崩溃性质:一次 AjaxPro 同步请求触发的业务异常。业务代码 get_Type() 抛 E_FAIL,经 AOP/AjaxPro 两层反射 invoke 各包一层 TIE 后冒泡到宿主顶层。
  2. 与进程内安全组件的关系:托管栈全文 + 原生栈中没有任何安全组件帧——进程内安装的崩溃收集组件(dump 即其产物)只扮演了 crash dump 收集者角色,对未处理 CLR 异常(宿主重抛阶段)做了捕获采样。
  3. 责任判定:可归因为第三方应用自身未处理异常,与进程内其他组件无关。

给业务的排查方向

  • get_Type() 内部若有 COM 组件/原生互操作(E_FAIL 是 COMException 默认 HResult),检查对应 COM 组件注册、许可、依赖 DLL 是否完好;
  • 关注 ReregMutex/GetMutexInfo(互斥锁重新注册)请求的入参是否触发了特定的前置条件校验分支;
  • 异常冒泡到管线顶层说明 AjaxPro handler 与应用层均未 catch——应用侧应加异常处理,避免请求级失败演变成宿主级事件。

附:分析过程关键命令与坑

DAC 版本不匹配 workaround

本机 SOS/DAC 与 dump 的 CLR 版本不一致时,msdl 平名查找(mscordacwks_AMD64_AMD64_<ver>.dll)可能失效。Workaround:

  1. symsrv 缓存目录里找到已下载的 mscordacwks_AMD64_AMD64_4.8.4614.00.dll\<timestamp>\ 文件
  2. 复制为 dac\mscordacwks.dll(去掉平名目录结构)
  3. .cordll -ve -lp <dac目录> 直连

⚠️ 注意 .cordll -u 是禁用 CLR 调试,别带。

minidump 里 SOS 显示字段失败时:裸内存解析

mscorlib.ni 无镜像导致 !do/!pe 显示失败时,直接 db 异常对象内存裸解析。.NET 4.8 x64 Exception 对象布局(实测):

偏移字段
+0x08_message
+0x30_innerException(TIE 链沿此走)
+0x58_stackTraceString(文本)
+0x8CHResult

栈文本是 UTF-16LE,用 Python 解码绕开控制台代码页丢字。

其他

  • 镜像 key = 时间戳+裁剪后 Size(如 63995BC5b34000);.reload 拉不动镜像时 .exepath+ <缓存目录> 直指
  • clr.pdb 的 GUID 可用 Python 解析 PE debug directory 拿到后手工从 msdl 拉
  • msdl 不托管 mscorlib.ni.dll 二进制(404),只托管其 pdb