一段时间以来,我一直在努力解决 C# 应用程序崩溃的问题,该应用程序也使用相当多的 C++/CLI 模块,这些模块主要是本地库的包装器来访问设备驱动程序。
崩溃并不总是很容易重现,但我能够收集六个崩溃转储,这些崩溃转储表明程序总是在垃圾收集期间因访问冲突而崩溃。这是本机调用堆栈和最后一个事件日志:
0:000> k
ChildEBP RetAddr
0012d754 79f95a8f mscorwks!WKS::gc_heap::find_first_object+0x62
0012d7dc 79f933bb mscorwks!WKS::gc_heap::mark_through_cards_for_segments+0x493
0012d814 79f92cbf mscorwks!WKS::gc_heap::mark_phase+0xc3
0012d838 79f93245 mscorwks!WKS::gc_heap::gc1+0x62
0012d84c 79f92f5a mscorwks!WKS::gc_heap::garbage_collect+0x253
0012d878 79f94e26 mscorwks!WKS::GCHeap::GarbageCollectGeneration+0x1a9
0012d904 79f926ce mscorwks!WKS::gc_heap::try_allocate_more_space+0x15b
0012d918 79f92769 mscorwks!WKS::gc_heap::allocate_more_space+0x11
0012d938 79e73291 mscorwks!WKS::GCHeap::Alloc+0x3b
0:000> .lastevent
Last event: 7e8.88: Access violation - code c0000005 (first/second chance not available)
debugger time: Mon Sep 26 11:34:53.646 2011 (UTC + 2:00)
因此,让我首先提出我的问题,并在下面提供更多详细信息。我的问题是:除了托管堆损坏之外,还有其他原因导致垃圾收集期间崩溃吗?
现在详细说明一下,我问这个问题的原因是因为我很难识别破坏托管堆的代码,并且似乎无法找到(据称)被覆盖的内存的模式。
我已经尝试注释所有“危险”C++/CLI 代码(特别是使用固定句柄的部分),但这没有帮助。在试图找到内存中被覆盖的模式时,我查看了崩溃时的反汇编代码:
0:000> u .-a .+a
mscorwks!WKS::gc_heap::find_first_object+0x54:
79f935b9 89450c mov dword ptr [ebp+0Ch],eax
79f935bc 8bd0 mov edx,eax
79f935be 8b02 mov eax,dword ptr [edx]
79f935c0 83e0fe and eax,0FFFFFFFEh
79f935c3 f70000000080 test dword ptr [eax],80000000h <<<<CRASH
79f935c9 0f84b1000000 je mscorwks!WKS::gc_heap::find_first_object+0x73
0:000> r
eax=00000000 ebx=01c81000 ecx=01c80454 edx=01c82fe0 esi=012f0000 edi=000027e1
eip=79f935c3 esp=0012d738 ebp=0012d754 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
mscorwks!WKS::gc_heap::find_first_object+0x62:
79f935c3 f70000000080 test dword ptr [eax],80000000h ds:0023:00000000=????????
当尝试取消引用空的 EAX 寄存器时,会发生崩溃。现在,我看到 EAX 是从 EDX 寄存器指向的内容加载的,所以我查看了存储在那里的地址:
0:000> dd @edx-10
01c82fd0 06542778 00000000 00000000 01c82494
01c82fe0 00000000 00000000 00000000 00000000
01c82ff0 01b641d0 00000000 00000000 01c82380
编辑:我现在发现我的分析是错误的,缺乏对 x86 寻址模式的理解。
所以我可以看到从地址 01c82fed(存储在 EDX 中的值)开始,接下来的 16 个字节为空。
但是,当查看另一个类似的故障转储时,我看到以下内容:
0:000> dd @edx-10
018defd4 00000000 00000000 00000000 00000000
018defe4 00000000 00000000 018b468c 01742354
018deff4 00e0907f 00000000 00000000 00000000
所以这里 EDX 指向的地址之前的 16 个字节和接下来的 8 个字节为空。同样的情况也发生在我拥有的其他故障转储中,我在这里没有看到任何模式,即似乎某些代码片段只是简单地覆盖了内存的该区域。
回到这个问题,我想知道的是,除了一段不应该覆盖内存的代码之外,是否还有其他对崩溃的解释。或者有任何关于如何进行的建议,我真的迷失在这个地方了..
(固定手柄会导致问题吗?我们有很多这样的手柄,我认为有趣的是,我总是看到 137 - 不多也不少 - 在崩溃时带有 !gchandles 的固定手柄,它是对我来说这是一个奇怪的巧合..)。
EDIT:忘记提及我们正在使用 .Net 框架的 3.5 版本。当后台 GC 处于活动状态时,我在 .Net 4 中看到类似崩溃的报告(某处提到这是 .Net 中的一个错误),但我认为这与此无关,因为 AFAIK 中没有后台 GC .Net 3.5。