【问题标题】:How can I debug an internal error in the .NET Runtime?如何调试 .NET 运行时中的内部错误?
【发布时间】:2013-01-09 15:28:16
【问题描述】:

我正在尝试调试一些处理大文件的工作。代码本身工作,但 .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)表明执行引擎错误 - 一个讨厌的小动物。 related answer,表示 GC 错误,带有“修复”以禁用并发 GC;但是这个“修复”并不能阻止这个问题。

澄清:此低级错误不会触发CurrentDomain.UnhandledException 事件。

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

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

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

这么多不同的场景。

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

【问题讨论】:

  • 很好奇为什么要显式调用 GC,因为在极少数情况下可以将其视为良好做法。鉴于您的代表,我相信您有充分的理由并很好奇它是什么。
  • @EricJ 这不是生产代码; GC 收集只是为了让每次迭代都进入已知状态,而不是在中间随机进行 GC。删除它并不能修复错误:它只会让观察锯齿变得更加困难;p 这整个代码块的存在纯粹是为了对此进行压力测试,以重现报告的错误。
  • 不确定是否相关,但根据MSDN,垃圾收集器在重负载下会产生此错误:In some cases, an application that targets the .NET Framework may throw an ExecutionEngineException exception during garbage collection when an application or the system on which it is running is under a heavy load. As a workaround, you can disable concurrent garbage collection by modifying the application's configuration file. For more information, see How to: Disable Concurrent Garbage Collection.
  • 您是否设法找出造成这种情况的原因?
  • @Nahum 当我问这个问题时,它是通过支持电子邮件发送给我的一个开源项目的,我可以在本地复制。我们似乎不太可能遇到完全相同的 RAM 故障。

标签: c# .net


【解决方案1】:

如果您有内存转储,我建议您使用 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 events


如果您获得的 minidump 没有被删除并且您使用的是 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 交互并连接到窗口站,但我不记得是如何完成的。这也很不方便,因为如果您需要访问已打开的任何现有窗口,则必须在会话之间来回切换。

【讨论】:

  • 你如何用 x64dbg 做到这一点
【解决方案2】:

Tools-&gt;Debugging-&gt;General-&gt;Enable .Net Framework Debugging

+

Tools-&gt;IntelliTace-&gt; IntelliTaceEbents And Call Information

+

Tools-&gt;IntelliTace-&gt; Set StorIntelliTace Recordings in this directory

然后选择一个目录

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

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

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

【讨论】:

  • 执行涉及 10+GB 内存并且每次迭代需要一分钟以上的工作,并且可能不会发生很长时间,这可能是过多的日志记录。不过是个好主意。
【解决方案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);

【讨论】:

  • 我希望他已经尝试过了。他说:“异常处理程序没有被命中。”在问题中。
  • 唉,这是一个较低级别的“异常”——80131506 是一个 ExecutionEngineException;之后,no 托管代码将运行。好主意,但行不通。
  • 我假设他的异常处理程序是他围绕循环编写的 catch 块。
  • 明确地说:是的,我试过了;不,那也不会被击中
  • afaik ExecutionEngineException 会导致自 .NET 4.0 以来立即终止进程,因此很遗憾这不会有帮助。
【解决方案4】:

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

如果你在 Windows 上运行你的东西,有很多不错的选择,例如 callgrind 的verysleepy,如下所示:
Is there a good Valgrind substitute for Windows?

如果你真的想调试.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 为中心的人,this post 说 Windows 最接近的是 WinDbg 的 Logger.exe,但 ltrace 信息没有那么广泛。

单声道源代码可在此处获得:
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

【讨论】:

    【解决方案5】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多