c# - 垃圾收集期间崩溃的原因

标签 c# debugging windbg

一段时间以来,我一直在为 C# 应用程序的崩溃而苦苦挣扎,该应用程序还使用相当多的 C++/CLI 模块,这些模块主要是 native 库的包装器以访问设备驱动程序。 崩溃并不总是很容易重现,但我能够收集到六个崩溃转储,这些转储表明程序在垃圾收集期间总是因访问冲突而崩溃。这是 native 调用堆栈和最后的事件日志:

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崩溃,这对我来说是一个奇怪的巧合..)。

编辑:忘记提及我们使用的是 .Net 框架的 3.5 版。当后台 GC 处于事件状态时,我在 .Net 4 中看到类似崩溃的报告(某处提到这是 .Net 中的错误)但我认为这与这里无关,因为 AFAIK 中没有后台 GC .Net 3.5。

最佳答案

不确定这是否有帮助,但通常不要使用析构函数或让 GC 处理非托管内存。改为使用 Dispose 模式,并将所有析构函数代码移至终结器:

ref class MyClass
{
  UnsafeObject data;
  MyClass()
  {
    data = CreateUnsafeDataObject();
  }
  !MyClass()  // IDisposable.Dispose()
  {
    DeleteUnsafeDataObject(data);
  }
  ~MyClass()  // Destructor
  {

  }
}

这将在对象上实现 IDisposable 模式。调用 Dispose 来清除非托管数据,在最坏的情况下,您将有更好的机会弄清楚到底发生了什么。

关于c# - 垃圾收集期间崩溃的原因,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7580577/

相关文章:

c# - 如何从 C# 中唯一标识 USB key ?

windows - 如何防止 WinDbg 附加到特定的子进程?

Delphi XE7 : Debug points not appearing, 调试时未命中断点并且 CPU 窗口打开

python - 将 Python 与 Kivy 结合使用

c++ - Qt Creator 无法中断抛出的异常(使用 CDB 作为调试器时)

debugging - WinDbg:如何知道 WinDbg 中发生了中断?

javascript - 使用 Selenium 单击伪元素

c# - C# 中的正则表达式如何仅替换捕获组而不替换非捕获组

javascript - 在另一个 View 中加载数据后加载部分 View (或多个 View )

c++ - Netbeans 远程调试 C++ 无控制台输出