关闭时触发Exception code c0020001:如何定位违规托管方法?
定位C++/CLI应用关闭时的托管方法违规调用问题
我有一个大型C++/CLI应用,托管了同是C++/CLI编写的第三方二进制文件。应用关闭时偶尔触发崩溃,异常代码c0020001(该代码实际对应ERROR_INVALID_FUNCTION - 函数不正确,有时会被误解析为RPC_NT_INVALID_STRING_BINDING - 字符串绑定无效),已知原因是CLI运行时已关闭后仍有托管方法被调用。
核心问题:如何确定违规调用的托管方法?已通过现有方法解决部分场景(栈追踪1),但栈追踪2的场景无法用相同方法定位。
已尝试操作
- 捕获崩溃的完整转储文件,发现两种不同的栈追踪:
- 栈追踪1:包含
clr!UMThunkStubRareDisableWorker,通过调试步骤成功定位到违规调用的托管方法 - 栈追踪2:包含
clr!TheUMEntryPrestubWorker,内部调用clr!CanRunManagedCode后触发异常,现有调试指南无法覆盖该场景
- 栈追踪1:包含
栈追踪1
00 00000000`2e09e520 00007ff8`712659bf KERNELBASE!RaiseException+0x69 01 00000000`2e09e600 00007ff8`70cd22e8 clr!UMThunkStubRareDisableWorker+0x3f 02 00000000`2e09e630 00007fff`ff20e4dc clr!UMThunkStub+0x128 03 00000000`2e09e6c0 00007ff8`831bcf13 The3rdParty+0xe4dc 04 00000000`2e09e700 00007ff8`83130752 combase!<lambda_ef8a5b5bdab9c6b69f38e6b21c5d3987>::operator()+0xbb [onecore\com\combase\dcomrem\stdid.cxx @ 1409] 05 00000000`2e09e740 00007ff8`831307f5 combase!ObjectMethodExceptionHandlingAction<<lambda_ef8a5b5bdab9c6b69f38e6b21c5d3987>>+0xe [onecore\com\combase\dcomrem\excepn.hxx @ 128] 06 00000000`2e09e790 00007ff8`831bca36 combase!CStdIdentity::ReleaseCtrlUnk+0x81 [onecore\com\combase\dcomrem\stdid.cxx @ 1412] (...additional combase and rpcrt4 frames...)
栈追踪2
00 00000000`000cf500 00007ff8`71265833 KERNELBASE!RaiseException+0x69 01 00000000`000cf5e0 00007ff8`70cd217e clr!TheUMEntryPrestubWorker+0x43 02 00000000`000cf630 00007fff`fdcd47a4 clr!TheUMEntryPrestub+0x3e 03 00000000`000cf6a0 00007fff`fe07cc74 the3rdparty+0x47a4 04 00000000`000cf6d0 00007ff8`828d42d6 the3rdparty!DllUnregisterServer+0x11b694 05 00000000`000cf700 00007ff8`828d41fb ucrtbase!<lambda_f03950bc5685219e0bcd2087efbe011e>::operator()+0xa6 06 00000000`000cf750 00007ff8`828d41b4 ucrtbase!__crt_seh_guarded_call<int>::operator()<<lambda_7777bce6b2f8c936911f934f8298dc43>,<lambda_f03950bc5685219e0bcd2087efbe011e> &,<lambda_3883c3dff614d5e0c5f61bb1ac94921c>>+0x3b 07 00000000`000cf780 00007fff`fdf7f475 ucrtbase!execute_onexit_table+0x34 08 00000000`000cf7b0 00007fff`fdf7f59a the3rdparty!DllUnregisterServer+0x1de95 09 00000000`000cf7f0 00007ff8`7248ae44 the3rdparty!DllUnregisterServer+0x1dfba 0a 00000000`000cf850 00007ff8`755c1bad mscoreei!CorDllMain+0x1b4 0b 00000000`000cf8d0 00007ff8`755c1c07 mscoree!ShellShim__CorDllMain+0xf5 0c 00000000`000cf910 00007ff8`85089a1d mscoree!CorDllMain_Exported+0x37 0d 00000000`000cf940 00007ff8`850cdcda ntdll!LdrpCallInitRoutine+0x61 0e 00000000`000cf9b0 00007ff8`850cda8d ntdll!LdrShutdownProcess+0x22a 0f 00000000`000cfac0 00007ff8`8479e3bb ntdll!RtlExitUserProcess+0xad 10 00000000`000cfaf0 00007ff8`72471cd5 kernel32!ExitProcessImplementation+0xb
栈追踪1的调试步骤
0:036> k # Child-SP RetAddr Call Site 00 00000000`2e09e520 00007ff8`712659bf KERNELBASE!RaiseException+0x69 01 00000000`2e09e600 00007ff8`70cd22e8 clr!UMThunkStubRareDisableWorker+0x3f 02 00000000`2e09e630 00007fff`ff20e4dc clr!UMThunkStub+0x128 (...) 0:036> .loadby sos clr 0:036> .sympath+ srv* ; .cordll -ve -u -l 0:036> u clr!UMThunkStub L50 clr!UMThunkStub: (...) 00007ff8`70cd22d9 4c895548 mov qword ptr [rbp+48h],r10 ; 存储r10,其中包含回调的方法描述符 00007ff8`70cd22dd 498bcc mov rcx,r12 00007ff8`70cd22e0 498bd2 mov rdx,r10 00007ff8`70cd22e3 e898365900 call clr!UMThunkStubRareDisableWorker (00007ff8`71265980) 00007ff8`70cd22e8 4c8b5548 mov r10,qword ptr [rbp+48h] ; 栈中的返回地址 0:036> !dumpmd poi(poi(@rbp+48)+0x8) Method Name: <Module>.ATL.CComObject<The3rdParty::ClassINeedToUninit>.__vecDelDtor(ATL.CComObject<The3rdParty::ClassINeedToUninit>*, UInt32) Class: 00007ff812089678 MethodTable: 00007ff8120947b0 mdToken: 000000000600007e Module: 00007ff812075010 IsJitted: yes CodeAddr: 00007ff811f7b920 Transparency: Safe critical
栈追踪2的调试解决方案
步骤1:定位第三方代码中的调用点
栈追踪2显示崩溃发生在the3rdparty+0x47a4调用clr!TheUMEntryPrestub之后,先反汇编该地址查看调用的桩:
0:000> u the3rdparty+0x47a4 L10 the3rdparty+0x47a4: 00007fff`fdcd47a4 ff1516b80000 call qword ptr [the3rdparty+0x10000] ; 此处是CLR生成的托管方法入口桩 00007fff`fdcd47aa 4883c420 add rsp,20h 00007fff`fdcd47ae 5f pop rdi 00007fff`fdcd47af 5b pop rbx 00007fff`fdcd47b0 c3 ret
步骤2:解析托管方法描述符
CLR生成的入口桩内部存储了方法描述符的指针,用!dumpmd命令解析:
0:000> !dumpmd poi(the3rdparty+0x10000) ; 若上述命令无结果,尝试调整偏移(不同CLR版本可能有差异): 0:000> !dumpmd poi(poi(the3rdparty+0x10000)+0x8)
步骤3:关联全局对象析构(可选)
从栈帧可以看出,崩溃发生在ucrtbase!execute_onexit_table阶段,说明是第三方的全局对象析构函数调用了托管方法。可以通过以下方式定位全局对象:
- 反汇编
the3rdparty!DllUnregisterServer+0x1de95(栈帧8),找到注册到onexit表的函数 - 查看该函数对应的全局对象,确认其析构逻辑中调用的托管方法
替代方案:手动解析CLR内部结构
若sos命令无法使用(CLR已关闭),可反汇编clr!TheUMEntryPrestubWorker查看异常触发前的寄存器值:
0:000> u clr!TheUMEntryPrestubWorker L30 clr!TheUMEntryPrestubWorker: 00007ff8`71262830 48895c2408 mov qword ptr [rsp+8],rbx 00007ff8`71262835 57 push rdi 00007ff8`71262836 4883ec20 sub rsp,20h 00007ff8`7126283a e871ffffff call clr!CanRunManagedCode (00007ff8`712627b0) 00007ff8`7126283f 85c0 test eax,eax 00007ff8`71262841 7517 jne clr!TheUMEntryPrestubWorker+0x2a (00007ff8`7126285a) ; 若CanRunManagedCode返回0,触发异常 00007ff8`71262843 488d0dba3d5900 lea rcx,[clr!g_umEntryPrestubException (00007ff8`71266604)] 00007ff8`7126284a e8f1fcffff call clr!RaiseTheExceptionInternalOnly (00007ff8`71262540)
此时rdi寄存器通常存储着托管方法的相关上下文,可通过!dumpmd解析rdi指向的结构:
0:000> !dumpmd poi(rdi+0x8)
内容的提问来源于stack exchange,提问作者Jonathan
相关产品推荐
相关产品推荐

