如何在.NET中追踪StackOverflowException的原因?

时间:2011-02-02 21:03:36

标签: c# .net winforms volatile stack-overflow

运行以下代码时,我得到StackOverflowException

private void MyButton_Click(object sender, EventArgs e) {
  MyButton_Click_Aux();
}

private static volatile int reportCount;

private static void MyButton_Click_Aux() {
  try { /*remove because stack overflows without*/ }
  finally {
    var myLogData = new ArrayList();
    myLogData.Add(reportCount);
    myLogData.Add("method MyButtonClickAux");
    Log(myLogData);
  }
}

private static void Log(object logData) {
  // my log code is not matter
}

可能导致StackOverflowException

的原因

4 个答案:

答案 0 :(得分:44)

我知道如何阻止它发生

我只是不知道为什么会导致它(还)。看起来你确实在.Net BCL中发现了一个错误,或者更可能发现在JIT中。

我刚评论了MyButton_Click_Aux方法中的所有行,然后开始逐个将它们重新输入。

从静态int中取消volatile,您将不再获得StackOverflowException

现在研究为什么......显然,与内存障碍有关的问题导致了一个问题 - 也许某种程度上迫使MyButton_Click_Aux方法自称......

<强>更新

好的,其他人发现.Net 3.5不是问题。

我正在使用.Nt 4,所以这些评论与此相关:

正如我所说的那样,把挥发性物质关掉它就可以了。

同样,如果你重新启用volatile并删除try / finally它也可以:

private static void MyButton_Click_Aux()
{
  //try { /*remove because stack overflows without*/ }
  //finally
  //{
    var myLogData = new ArrayList(); 
    myLogData.Add(reportCount); 
    //myLogData.Add("method MyButtonClickAux");
    //Log(myLogData);
  //}
}  

我还想知道当try / finally进入时,是否与未初始化的reportCount有关。但是如果你把它初始化为零则没有任何区别。

我现在正在看IL - 虽然它可能需要有一些ASM人员参与......

最终更新 正如我所说,这真的需要分析JIT输出才能真正理解发生了什么,同时我发现分析汇编程序很有趣 - 我觉得这可能是微软某人的工作,所以这个错误实际上可以被确认和修复!这就是说 - 它似乎是一个相当狭窄的环境。

我已经转移到发布版本以摆脱所有IL噪声(nops等)进行分析。

然而,这对诊断产生了复杂的影响。我以为我有它,但没有 - 但现在我知道它是什么。

我试过这段代码:

private static void MyButton_Click_Aux()
{
  try { }
  finally
  {
    var myLogData = new ArrayList();
    Console.WriteLine(reportCount);
    //myLogData.Add("method MyButtonClickAux");
    //Log(myLogData);
  }
}

将int作为volatile。它运行没有错。这是IL:

.maxstack 1
L_0000: leave.s L_0015
L_0002: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()
L_0007: pop 
L_0008: volatile. 
L_000a: ldsfld int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile) WindowsFormsApplication1.Form1::reportCount
L_000f: call void [mscorlib]System.Console::WriteLine(int32)
L_0014: endfinally 
L_0015: ret 
.try L_0000 to L_0002 finally handler L_0002 to L_0015

然后我们查看再次获取错误所需的最小代码:

private static void MyButton_Click_Aux()
{
  try { }
  finally
  {
    var myLogData = new ArrayList();
    myLogData.Add(reportCount);
  }
}

这是IL:

.maxstack 2
.locals init (
    [0] class [mscorlib]System.Collections.ArrayList myLogData)
L_0000: leave.s L_001c
L_0002: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()
L_0007: stloc.0 
L_0008: ldloc.0 
L_0009: volatile. 
L_000b: ldsfld int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile) WindowsFormsApplication1.Form1::reportCount
L_0010: box int32
L_0015: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
L_001a: pop 
L_001b: endfinally 
L_001c: ret 
.try L_0000 to L_0002 finally handler L_0002 to L_001c

区别?那么我发现了两个 - 挥动int的装箱和虚拟通话。所以我设置了这两个类:

public class DoesNothingBase
{
  public void NonVirtualFooBox(object arg) { }
  public void NonVirtualFooNonBox(int arg) { }

  public virtual void FooBox(object arg) { }
  public virtual void FooNonBox(int arg) { }
}

public class DoesNothing : DoesNothingBase
{
  public override void FooBox(object arg) { }
  public override void FooNonBox(int arg) { }
}

然后尝试了这四种版本的违规方法:

try { }
finally
{
  var doesNothing = new DoesNothing();
  doesNothing.FooNonBox(reportCount);
}

哪个有效。

try { }
finally
{
  var doesNothing = new DoesNothing();
  doesNothing.NonVirtualFooNonBox(reportCount);
}

哪个也有效。

try { }
finally
{
  var doesNothing = new DoesNothing();
  doesNothing.FooBox(reportCount);
}

糟糕 - StackOverflowException

try { }
finally
{
  var doesNothing = new DoesNothing();
  doesNothing.NonVirtualFooBox(reportCount);
}

再次哎呀! StackOverflowException

我们可以更进一步 - 但问题是,我觉得,显然是由于在try / catch的finally块内部的volatile int的装箱...我将代码置于try中,并且没有问题。我添加了一个catch子句(并将代码放在那里),也没问题。

它也可以应用于我猜的其他值类型的装箱。

因此,总结一下 - 在.Net 4.0中 - 在调试和发布版本中 - 在finally块中装入volatile int似乎会导致JIT生成最终填满堆栈的代码。堆栈跟踪只显示“外部代码”这一事实也支持这一主张。

甚至有可能不能总是复制它,甚至可能取决于try / finally生成的代码的布局和大小。这显然与错误的jmp或类似的东西生成到错误的位置有关,最终会向堆栈重复一个或多个推送命令。坦率地说,这实际上是由盒子操作引起的这个想法很有意思!

最终的最终更新

如果你看看@Hasty G找到的MS Connect错误(进一步回答) - 你会看到bug以类似的方式表现出来,但在 catch 语句中有一个易变的bool

此外 - MS在重新启动后排队修复此问题 - 但7个月后还没有可用的修补程序。我之前已经记录在支持MS Connect,所以我不会再说了 - 我认为我不需要!

最终决赛最终更新(2011年2月23日)

它是固定的 - 但尚未发布。来自MS Team的MS Connect错误:

  

是的,它是固定的。我们正在弄清楚如何最好地发布修复程序。它已在4.5中修复,但我们真的想在4.5版本之前修复一批代码生成错误。

答案 1 :(得分:12)

该错误存在于您的代码中。据推测,MyButton_Click_Aux()会导致某些方法被重新输入。但是,你已经莫名其妙地从你的问题中省略了该代码,因此没有人可以对它进行评论。

答案 2 :(得分:0)

Log会回拨给Log吗?这也会导致SO。

答案 3 :(得分:0)

当发生异常时,为什么不检查调用堆栈面板中记录的内容?调用堆栈本身可以说明很多。

此外,使用SOS.dll和WinDbg的低级调试也可以告诉你很多。