我最近一直在为一个使用了相当多C++/CLI模块的C#应用程序中的崩溃问题苦苦挣扎,这些模块大多是用来包装本地库以访问设备驱动程序的。崩溃并不总是容易重现,但我已经收集了半打的崩溃转储文件,显示程序总是在垃圾回收期间发生访问冲突而崩溃。这是本地调用堆栈和最后一个事件日志:
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=????????
当试图解除引用为null的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个固定句柄-不多不少-这对我来说是一个奇怪的巧合..)。
编辑:忘记提到我们正在使用.NET框架的3.5版本。我看到在.NET 4中当后台GC处于活动状态时会出现类似的崩溃报告(某处提到这是.NET的一个bug),但我认为这与此无关,因为据我所知,.NET 3.5中没有后台GC。