如何在.NET中调试stackoverflow异常

45

场景:

我刚刚完成了一堆代码的编写,但当我执行它时,会抛出一个StackOverflowException异常。这个异常没有堆栈跟踪信息,所以我被卡住了。我知道为什么可能会发生堆栈溢出,但要修复它,我需要知道它的根源在哪里。

我只看到了这个信息: 未处理的类型“System.StackOverflowException”异常发生在tag-you're-it.dll文件中

选项:

  1. 浏览所有更改并尝试确定问题所在。(可能很慢)
  2. 使用调试器并逐步查找问题。(比1好)
  3. 使用性能分析,查找最常调用的方法。

PS:

这是一个假设情况(虽然并不罕见),因此没有可用的代码。


打印出堆栈跟踪?当遇到堆栈溢出异常时,VS往往会出现问题。 - leppie
导致这个问题的代码已经发布在以下链接中:http://stackoverflow.com/questions/39004357/how-to-fix-stackoverflowexception-in-net - Andrus
11个回答

29

WinDbg可以完成任务,甚至可以获取一个合理(clr)的堆栈跟踪。 除非您已经使用Visual Studio或Windows SDK安装了它,否则您需要下载WinDbg。 注意:“带有新GUI的WinDbg Preview”对我来说很好用。

我建议从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(将打印堆栈跟踪)

值得注意的来源:


不错!下次遇到这种情况我会试一试。 - Christo
1
我不得不将.loadby SOS.dll clr替换为只有.loadby SOS.dll。 - LOST
@LOST,你具体做什么,用了哪些命令,得到了什么响应? - BatteryBackupUnit
1
@BatteryBackupUnit,nvm,我已经弄清楚了我的问题。我不知道从WinDBG开始.NET程序时CLR尚未加载,而.loadby SOS.dll clr会打印一个令人困惑的消息:“无法找到模块'clr'”,我以为这是SOS的问题,但实际上它意味着我必须在CLR加载后中断并运行上面的命令。 - LOST
@binki 抱歉,多打了一个 . ... 我已经修复了:!stoponexception -create System.StackOverflowException - BatteryBackupUnit
显示剩余2条评论

16

这几乎总是由于递归造成的。要么是一个方法调用了自身,要么是一个方法调用了另一个方法再回调它本身。

如何找到问题:

  • 更新:我不知道,但显然你不能获得StackOverflowException的堆栈跟踪(可能与无法捕获之一有关)。但是,有办法获取转储如此提到的
  • ReSharper会显示调用自身的方法(对递归调用在侧边栏中放置了一个小绿色圆圈),但它不会捕获涉及两个或多个方法的递归。
  • 使用类似ANTS Profiler这样的工具查看最常调用的方法。
  • 注意触发事件,可能会调用代码,导致再次触发相同的事件,从而引起循环。

偶尔还会出现这种错别字:

private string name;

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

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


5
  1. SO异常不会生成堆栈跟踪。
  2. 递归可能是罪魁祸首,resharper可能有所帮助,但它并不总是能够发现由于其他结构而导致的循环圈。
  3. 性能分析似乎是一个不错的选择。
- Christo
http://stackoverflow.com/questions/39004357/how-to-fix-stackoverflowexception-in-net/显示这不是由递归引起的。在这种情况下如何调试SOexception?本答案假设这是由递归引起的。 - Andrus

7

前往调试,异常并勾选“公共语言运行时异常”复选框。现在当您引发stackoverflow异常时,调试器将停止(最终)并显示调用堆栈。


3
你确定它能处理堆栈溢出异常吗?对于它们的处理方式有些特殊。 - Anders Abel
4
值得强调的是,您需要查看Visual Studio的“调用堆栈”窗口,而不是异常的“堆栈跟踪”,因为后者不会被设置(可能是因为一旦堆栈已满,它就无法执行需要进一步使用堆栈的任何操作)。 - Appetere
Visual Studio 支持的堆栈帧数已超过最大限制。 - CMS

6
ProcDump实用程序已帮助我们调试问题,如此处详细描述的那样。以下是步骤:
  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上使用相同的技术,如此处所述。基本上,它执行与上述相同的步骤。它生成的转储文件可以下载并在Visual Studio中打开。

5

您可以在调试模式下执行程序并暂停它。在当前的调用堆栈中,您可以看到有一个方法或一组方法出现了多次,这些都是有问题的方法。在此方法上设置断点,并查看它是否一直在调用自己。


没问题,我会添加单步调试作为另一个选项。 - Christo
谢谢!我只是偶尔会遇到棘手的错误,所以我不习惯使用调用堆栈。我确信我没有递归问题,但当然,在意料之外的地方出现了递归问题,一旦我停止仅仅试图直觉出错在哪里,修复起来很容易。 :-) - clweeks

3

我怀疑如果导致堆栈溢出的线程的堆栈大小大于某个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.

1

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

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

然后我检查了我的函数,并添加了一些打印输出函数,例如: 在函数开始时:

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 异常将在这些语句之间发生。继续缩小范围直到找到它。
通过这种方式很容易快速地找到它发生的位置。

1

我想补充一下有关使用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获取堆栈转储

在我的情况下,这告诉我堆栈溢出异常发生的确切位置


1
在操作失败的“入口点”方法中,设置断点。逐步执行代码,并观察相同的方法调用序列以相同的模式重复发生,使调用堆栈变得越来越深。
一旦注意到这一点,请在当前位置设置断点,无论它在哪里。继续执行(在Visual Studio中按F5)-如果您走上了正确的轨道,那么调试器将非常快地停止在相同的位置,并且调用堆栈将更加深入。
现在,您有一个“活动”的堆栈帧可以检查,以便找出如何确保此递归将正确终止。

按“最早”的顺序排序答案(将答案倒序排列)。 - Daniel Earwicker
嗯,我猜我的排序有问题了。我最初也投了你的票,以防我把顺序弄错了。 - Christo

0
如果您有代码并能够从Visual Studio运行程序,当遇到System.StackOverflowException时,它应该会在调试器中断(如果启用了首次机会异常)。从那里,您可以检查调用堆栈并查看哪些调用正在炸毁堆栈。 enter image description here

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接