68

我正在尝试调试一些处理大文件的工作。代码本身可以工作,但 .NET 运行时本身会报告零星错误。就上下文而言,这里的处理是一个 1.5GB 的文件(仅加载到内存中一次)在循环中处理和释放,故意尝试重现这个不可预测的错误。

我的测试片段基本上是:

try {
    byte[] data =File.ReadAllBytes(path);
    for(int i = 0 ; i < 500 ; i++)
    {
        ProcessTheData(data); // deserialize and validate

        // force collection, for tidiness
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
    }
} catch(Exception ex) {
    Console.WriteLine(ex.Message);
    // some more logging; StackTrace, recursive InnerException, etc
}

(加上一些时间和其他东西)

对于不确定的迭代次数,循环将完全成功地处理 - 没有任何问题;然后该过程将突然终止。异常处理程序未命中。该测试确实涉及大量内存使用,但它在每次迭代期间都非常好地锯齿(没有明显的内存泄漏,而且我有足够的空间 -在锯齿中最糟糕的点有 14GB 未使用的主内存) . 该进程是 64 位的。

Windows 错误日志包含 3 个新条目,其中(通过退出代码 80131506)表明执行引擎错误 - 一个讨厌的小动物。一个相关的答案,暗示了一个 GC 错误,并带有一个“修复”来禁用并发 GC;然而,这个“修复”并不能阻止这个问题。

澄清:这个低级错误不会影响CurrentDomain.UnhandledException事件。

澄清:GC.Collect这只是为了监视锯齿状内存,检查内存泄漏并保持可预测性;删除它不会让问题消失:它只是让它在迭代之间保留更多内存,并使 dmp 文件更大;p

通过添加更多控制台跟踪,我观察到它在以下每个过程中都会出现故障:

  • 在反序列化期间(大量分配等)
  • 在 GC 期间(在 GC“方法”和 GC“完成”之间,使用 GC 通知 API)
  • 在验证期间(只是foreach在一些数据上) - 奇怪的是在验证期间 GC“完成”之后

所以很多不同的场景。

我可以获得崩溃转储(dmp)文件;我如何进一步调查这个问题,看看系统在发生如此严重的故障时在做什么?

4

5 回答 5

23

如果您有内存转储,我建议您使用 WinDbg 来查看它们,假设您还没有这样做。

尝试运行注释!EEStack(混合本机和托管堆栈跟踪),并查看堆栈跟踪中是否有任何可能跳出的内容。在我的测试程序中,我发现这是发生 FEEE 的堆栈跟踪之一(我故意破坏堆):

0:000> !EEStack
---------------------------------------------
线程 0
当前帧:ntdll!NtWaitForSingleObject+0xa
Child-SP RetAddr 调用者、被调用者
00000089879bd3d0 000007fc586610ea KERNELBASE!WaitForSingleObjectEx+0x92,调用 ntdll!NtWaitForSingleObject
00000089879bd400 000007fc5869811c KERNELBASE!RaiseException+0x68,调用 ntdll!RtlRaiseException
[...]
00000089879bec80 000007fc49109cf6 clr!WKS::gc_heap::gc1+0x96,调用 clr!WKS::gc_heap::mark_phase
00000089879becd0 000007fc49109c21 clr!WKS::gc_heap::garbage_collect+0x222,调用 clr!WKS::gc_heap::gc1
00000089879bed10 000007fc491092f1 clr!WKS::GCHeap::RestartEE+0xa2,调用 clr!Thread::ResumeRuntime
00000089879bed60 000007fc4910998d clr!WKS::GCHeap::GarbageCollectGeneration+0xdd,调用 clr!WKS::gc_heap::garbage_collect
00000089879bedb0 000007fc4910df9c clr!WKS::GCHeap::Alloc+0x31b,调用 clr!WKS::GCHeap::GarbageCollectGeneration
00000089879bee00 000007fc48ff82e1 clr!JIT_NewArr1+0x481

由于这可能与垃圾收集器的堆损坏有关,我会尝试该!VerifyHeap命令。至少您可以确保堆完好无损(并且您的问题在其他地方)或发现您的问题实际上可能与 GC 或某些 P/Invoke 例程损坏它有关。

如果您发现堆已损坏,我可能会尝试发现有多少堆已损坏,您可以通过!HeapStat. 不过,这可能只是显示整个堆从某个点损坏。

很难建议任何其他方法通过 WinDbg 进行分析,因为我对您的代码在做什么或它的结构没有真正的线索。

我想如果你发现它是堆的问题,因此意味着它可能是 GC 怪异,我会查看 Windows 事件跟踪中的CLR GC 事件


如果您获得的小型转储没有切割它并且您使用的是 Windows 7/2008R2 或更高版本,则可以在进程无异常终止时使用全局标志 (gflags.exe) 附加调试器,如果您正在没有收到 WER 通知。

Silent Process Exit选项卡中,输入可执行文件的名称,而不是它的完整路径(即。TestProgram.exe)。使用以下设置:

  • 选中启用静默进程退出监控
  • 检查启动监视器进程
  • 对于监控进程,使用 {path to debugging tools}\cdb.exe -server tcp:port=5005 -g -G -p %e.

并应用设置。

当您的测试程序崩溃时,cdb 将附加并等待您连接到它。启动 WinDbg,键入 Ctrl+R,然后使用连接字符串:tcp:port=5005,server=localhost.

您可能可以跳过使用远程调试,而是使用{path to debugging tools}\windbg.exe %e. 但是,我建议使用远程的原因是WerFault.exe,我认为读取注册表并启动监视进程的 会在会话 0 中启动调试器。

您可以使会话 0 交互并连接到窗口站,但我不记得是如何完成的。这也很不方便,因为如果您需要访问已打开的任何现有窗口,则必须在会话之间来回切换。

于 2013-01-16T00:02:14.027 回答
7

Tools->Debugging->General->Enable .Net Framework Debugging

+

Tools->IntelliTace-> IntelliTaceEbents And Call Information

+

Tools->IntelliTace-> Set StorIntelliTace Recordings in this directory

并选择一个目录

应该允许您进入 .net 代码并跟踪每个函数调用。我在一个小型示例项目上进行了尝试,它可以工作

在每个调试会话之后,它应该创建调试会话的记录。如果我没记错的话,即使 CLR 死了,它也是设置的目录

这应该允许您在 CLR 崩溃之前进行确切的调用。

于 2013-01-09T16:20:40.190 回答
3

尝试编写一个通用的异常处理程序,看看是否有未处理的异常杀死你的应用程序。

    AppDomain currentDomain = AppDomain.CurrentDomain;
    currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyExceptionHandler);

static void MyExceptionHandler(object sender, UnhandledExceptionEventArgs e) {
        Console.WriteLine(e.ExceptionObject.ToString());
        Console.WriteLine("Press Enter to continue");
        Console.ReadLine();
        Environment.Exit(1);
于 2013-01-09T15:37:22.070 回答
3

我通常用 Valgrind 和 gdb 研究与内存相关的问题。

如果你在 Windows 上运行你的东西,有很多很好的替代品,例如 callgrind 的verysleepy,如下所示:
Windows 有没有好的 Valgrind 替代品?

如果你真的想调试 .NET 运行时的内部错误,你的问题是既没有类库也没有 VM 的源。

由于你无法调试你没有的东西,我建议(除了用 ILSpy 反编译有问题的 .NET 框架库,并将它们添加到你的项目中,这仍然不包括 vm)你可以使用单声道运行时。
那里既有类库的来源,也有 VM 的来源。
也许你的程序在单声道上运行良好,那么你的问题就会得到解决,至少只要它只是一个一次性处理任务。

如果没有,有一个关于调试的广泛常见问题解答,包括GDB支持
http://www.mono-project.com/Debugging

Miguel 也有这篇关于 valgrind 支持的帖子:http:
//tirania.org/blog/archive/2007/Jun-29.html

除此之外,如果您让它在 Linux 上运行,您还可以使用strace来查看系统调用中发生了什么。如果您没有大量使用 winforms 或 WinAPI 调用,.NET 程序通常在 Linux 上运行良好(对于文件系统区分大小写的问题,您可以循环挂载不区分大小写的文件系统和/或使用MONO_IOMAP)。

如果你是以 Windows 为中心的人,这篇文章 说 Windows 最接近的是 WinDbg 的 Logger.exe,但 ltrace 信息没有那么广泛。

Mono 源代码可在此处获得:http:
//download.mono-project.com/sources/

您可能对最新单声道版本的来源感兴趣
http://download.mono-project.com/sources/mono/mono-3.0.3.tar.bz2

如果您需要框架 4.5,则需要 mono 3,您可以在此处找到预编译包
https://www.meebey.net/posts/mono_3.0_preview_debian_ubuntu_packages/

如果你想修改源代码,编译方法如下:
http ://ubuntuforums.org/showthread.php?t=1591370

于 2013-02-13T21:51:30.170 回答
1

存在无法捕获的 .NET 异常。查看:http: //msdn.microsoft.com/en-us/magazine/dd419661.aspx

于 2013-01-15T21:27:36.960 回答