You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关闭时触发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

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 16:54:51