【问题标题】:How to debug a stackoverflowexception in .NET如何在 .NET 中调试 stackoverflowexception
【发布时间】:2011-06-11 15:46:26
【问题描述】:

场景:

我刚刚完成并实现了一堆代码,但是当我执行它时,我得到了一个 StackOverflowException? StackOverflowException 没有堆栈跟踪,所以我被卡住了。我知道为什么会发生堆栈溢出,但要修复它,我需要知道它的根在哪里。

我得到的只是:tag-you're-it.dll 中发生了“System.StackOverflowException”类型的未处理异常

选项:

  1. 浏览所有更改并尝试找出问题所在。 (可能很慢)
  2. 使用调试器并单步执行,直到找到问题为止。 (可能比 1 更好。)
  3. 使用配置文件并查找调用次数最多的方法。
  4. ?

PS:

这是一种假设情况(尽管并不少见),因此没有可用的代码。

【问题讨论】:

标签: .net debugging


【解决方案1】:

WinDbg 可以完成工作,甚至包括获得合理的 (clr) 堆栈跟踪。 你需要get WinDbg,除非你已经用 Visual Studio 或 Windows SDK 安装了它。 注意:带有新 GUI 的“WinDbg 预览”对我来说效果很好。

我建议从 WinDbg 启动您的进程,当然如果这更适合您,您也可以将其附加到正在运行的进程中。

注意:启动进程后,CLR 没有立即加载,.loadby SOS.dll clr 将失败(“无法找到模块 'clr')。您必须等待 CLR 加载。停止执行一次发生执行:

  • sxe ld clr

加载 CLR 后,您必须执行以下步骤才能中断 StackOverflowException(在命令窗口/行中输入):

  • .loadby SOS.dll clr不是.loadby sos clr——这会导致扩展被加载两次)
  • !stoponexception -create System.StackOverflowException
  • g(继续调试)

触发 StackOverflowException / 等待它发生

  • !clrstack(将打印堆栈跟踪)

著名来源:

【讨论】:

  • 不错!下次遇到这个我会试一试。
  • 我不得不用 .loadby SOS.dll 替换 .loadby SOS.dll clr
  • 我正在使用商店中的 WinDbg Preview,这些命令都不能正常工作,除了 g :(
  • @LOST 你到底在做什么,你使用什么命令,你得到什么响应?
  • @BatteryBackupUnit,nvm,解决了我的问题。我不知道 WinDBG 的 .NET 程序开始时 CLR 尚未加载,.loadby SOS.dll clr 会打印一条令人困惑的消息:“无法找到模块 'clr'”,我认为这是一个问题SOS,但实际上这意味着我必须在加载 CLR 并运行上面的这些命令后以某种方式中断。
【解决方案2】:

这几乎总是由于递归。要么是调用自身的方法,要么是调用回调它的方法的方法,以此类推。

要找到它:

  • 已更新:我没有意识到,但显然您无法获得 StackOverflowException 的堆栈跟踪(我想这与无法捕获一个有关)。但是,有一些方法可以获取转储 as mentioned here
  • ReSharper 将显示调用自身的方法(它会在边栏中为递归调用放置一个绿色小圆圈),但它不会捕获涉及两个或多个方法的递归。
  • 使用 ANTS Profiler 之类的工具来查看调用次数最多的方法。
  • 留意触发的事件,这些事件可能会调用代码,这意味着相同的事件会再次触发,从而导致循环。

有时你也会遇到这样的错别字:

private string name;

public string Name
{
    get { return Name; } // Ooops! This is recursive, all because of a typo...
}

这是我个人现在更喜欢使用自动属性的原因之一。

【讨论】:

  • 1.您不会得到带有 SO 异常的堆栈跟踪。 2. 递归可能是罪魁祸首,resharper 可能会有所帮助,但是由于其他构造,它并不总是有助于找到循环。 3. 个人资料看起来是个不错的选择。
  • stackoverflow.com/questions/39004357/… 表明这不是递归引起的。在这种情况下如何调试 SOexception?此答案假定这是由递归引起的。
【解决方案3】:

转到调试、异常并选中“公共语言运行时异常”中的已抛出复选框。现在,当您导致 stackoverflow 异常时,调试器将(最终)停止并向您显示调用堆栈。

【讨论】:

  • 您确定它适用于堆栈溢出异常吗?它们的处理方式有点特别。
  • 也许值得强调的是,您需要查看 Visual Studio 的调用堆栈窗口而不是不会设置的异常堆栈跟踪(可能是因为一旦堆栈已满,它就无法做任何需要进一步堆栈使用)。
  • 已超出 Visual Studio 支持的最大堆栈帧数。
【解决方案4】:

您可以在调试模式下执行程序并暂停它。在当前的调用栈上你可以看到有一个方法或一组方法多次出现,这些都是有问题的方法。给这个方法打断点,看看它一直在调用什么。

【讨论】:

  • 没错,我将添加调试步骤作为另一个选项。
  • 谢谢!我只是偶尔会遇到严重的错误,所以我没有使用调用堆栈的习惯。我确定我没有递归问题,但是,当然,我在一个意想不到的位置遇到了递归问题,一旦我停止试图凭直觉判断出了什么问题,修复就很容易了。 :-)
【解决方案5】:

ProcDump 实用程序已帮助我们将问题作为described in details here 进行调试。步骤:

  1. 下载工具
  2. 运行进程,记下它的 ID
  3. 通过运行procdump -accepteula -e 1 -f C00000FD.STACK_OVERFLOW -g -ma <process ID> d:\home\DebugTools\Dumps 附加调试器(目录必须存在)
  4. 使异常发生,procdump 将使您成为转储。
  5. 在 Visual Studio 中打开转储文件。对于我的示例应用,VS 在打开转储文件后立即突出显示了 SO 发生的行。

我们可以通过启用 CrashDiagnoser 扩展在 Azure 上使用相同的技术,例如 described here。基本上它执行与上述相同的步骤。它生成的转储文件可以在 Visual Studio 中下载和打开。

【讨论】:

    【解决方案6】:

    我怀疑如果导致堆栈溢出的线程的堆栈大小大于 Visual Studio 调试器可以跟踪的某个阈值,则调用堆栈不可用。

    一种解决方法是生成一个堆栈大小小于默认堆栈大小的线程,因此 Visual Studio 调试器可以跟踪调用堆栈。

            (new Thread(delegate ()
            {
                ProduceAStackOverFlowHere() ;
            }, 256 * 1024)).Start();//Default size for 32 bit process is 1MB, 64 bit process is 4MB. So I'll set the size at 256KB.
    

    【讨论】:

      【解决方案7】:

      在作为失败操作的“入口点”的方法处,放置一个断点。单步执行代码并观察相同的方法调用序列是否以相同的模式一遍又一遍地发生,这样调用堆栈就会越来越深。

      一旦你注意到这一点,就在当前位置放置一个断点,无论它在哪里。继续执行(Visual Studio 中的 F5) - 如果您走在正确的轨道上,那么调试器将很快停止在同一位置,并且调用堆栈会更深。

      现在您可以检查一个“实时”堆栈帧,以确定如何确保此递归正确终止。

      【讨论】:

      • 其他方式(按“最旧”排序答案)
      • ;-) 吉普,猜猜我的排序是错误的。我最初也给你投了票,以防万一我点错了。
      【解决方案8】:

      就我个人而言,我喜欢尽可能将其缩小到特定的代码部分。例如,我只有一个。奇怪的是它只发生在我无法直接调试的机器上。

      我有两个线程并行运行,所以我阻止了一个线程运行(或者你可以取消并行)。

      然后我浏览了我的函数并添加了类似打印输出的函数,例如: 就像函数开始一样:

      Console.WriteLine("<Enter method: {0}", DebuggingHelper.GetCurrentMethod());
      

      就在函数返回之前:

      Console.WriteLine(">Exit method: {0}", DebuggingHelper.GetCurrentMethod());
      

      其中GetCurrentMethod定义为:

      [MethodImpl(MethodImplOptions.NoInlining)]
      public static string GetCurrentMethod()
      {
          StackTrace st = new StackTrace();
          StackFrame sf = st.GetFrame(1);
          return sf.GetMethod().Name;
      }
      

      然后我运行它,也许我没有添加到所有函数,但足以缩小代码中发生的位置。然后在该部分中添加更多内容。

      您还可以在运行某些方法时添加检查点。

      然后再次运行,你会发现在这些语句之间会出现 StackOverFlow 异常。继续缩小范围,直到找到为止。

      以这种方式非常简单快捷地找到发生的位置。

      【讨论】:

        【解决方案9】:

        只是想补充一下关于使用 WinDbg 的答案,以及我发现的用于调试 dotnet core 应用程序的内容

        1. 确保你已经安装了windbg
        2. 通过命令行使用 dotnet run 启动您的应用程序
        3. 使用windbg附加到正在运行的进程
        4. 从命令行输入.loadby sos coreclr(这应该会检测到您正在使用的.net core 的版本,但如果没有,您可以使用.load C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.5\sos,其中2.05 是您正在使用的.netcore 的版本
        5. 现在可以通过输入!help 获得命令
        6. 使用!dso 获取堆栈转储

        在我的情况下,它告诉我 stackoverflow 异常发生的确切位置

        【讨论】:

          【解决方案10】:

          如果您有代码并且能够从 Visual Studio 运行您的程序,它应该在遇到 System.StackOverflowException 时中断调试器(如果启用了第一次机会异常)。从那里您可以检查调用堆栈并查看哪些调用正在破坏堆栈。

          我已经确认这适用于 Visual Studio 2010 和 Visual C# 2010 Express。

          【讨论】:

            【解决方案11】:

            在 IKVM.MET 的开发过程中,我想出了一个技巧来调试静态和动态程序集中的堆栈溢出错误,因为 IKVM.NET 广泛使用动态程序集。我将在 Visual Studio 调试器中运行可执行文件,当发生堆栈溢出时,Visual Studio 调试器将暂停该进程,而不是终止它。然后,我使用 dotnet-dump 获取核心转储,最后,我将终止进程以释放 RAM 并使用 dotnet-dump 分析核心转储。请注意,这仅适用于 .NET 核心应用程序。

            创建核心转储的命令

            dotnet-dump collect -p <Process ID here>
            

            可以使用任务管理器获取进程 ID。 查看堆栈跟踪的命令:

            dotnet-dump analyze <file name of dump> -c clrstack -all
            

            How to use dotnet-dump, if you want to do further debugging

            注意:这种调试技术是有风险的,因为进程可能已损坏,并且可能无法每次都正常工作。

            【讨论】:

              猜你喜欢
              • 2016-12-24
              • 2010-09-11
              • 2011-11-19
              • 1970-01-01
              • 1970-01-01
              • 2011-04-03
              • 2011-01-02
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多