C# 如何在.NET中查找StackOverflowException的原因?
当我运行以下代码时,我得到一个C# 如何在.NET中查找StackOverflowException的原因?,c#,.net,winforms,volatile,stack-overflow,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
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的原因是什么?错误在代码中。大概,
MyButton\u Click\u Aux()
会导致重新输入某些方法。然而,您在问题中莫名其妙地忽略了该代码,因此没有人可以对此进行评论。Log是否会调用Log?这也会导致SO。我知道如何阻止它发生
我只是不知道为什么会这样。看起来您确实在.Net BCL中或者更可能在JIT中发现了一个bug
我刚刚注释掉了MyButton\u Click\u Aux
方法中的所有行,然后开始一行一行地把它们带回来
从static int中去掉volatile
,您将不再获得堆栈溢出异常
现在来研究一下为什么。。。很明显,与内存障碍有关的东西正在引发一个问题——也许是某种原因迫使MyButton\u Click\u Aux
方法调用自己
更新
好吧,其他人发现.NET3.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噪声(NOP等)进行分析
然而,这对诊断产生了复杂的影响。我以为我有,但没有,但现在我知道它是什么了
我尝试了以下代码:
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
区别是什么?我发现了两个——volatile 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);
}
Oops-StackOverflowException
以及:
哎呀<代码>堆栈溢出异常
我们可以更进一步,但我觉得问题显然是由于在try/catch的finally块中对volatile int进行装箱引起的。。。我把代码放在try中,没有问题。我添加了一个catch子句(并将代码放在其中),也没有问题
我想它也可以应用于其他值类型的装箱
因此,总结一下,在.Net 4.0中,在调试和发布版本中,在finally块中装箱一个volatile int似乎会导致JIT生成最终填充堆栈的代码。堆栈跟踪仅显示“外部代码”这一事实也支持这一观点
甚至有一种可能性,它不能总是被复制,甚至可能取决于try/finally生成的代码的布局和大小。很明显,这与错误的jmp
或类似的东西生成到错误的位置有关,最终会向堆栈重复一个或多个push命令。坦率地说,这种现象实际上是由盒子操作引起的,这是一种迷人的想法
最终更新
如果你看一下@Hasty G发现的MS-Connect错误(再往下回答),你会发现这个错误以类似的方式出现,但是在catch语句中有一个不稳定的bool
另外,MS在将其提交给Repo后排队等待修复程序,但7个月后还没有可用的热修复程序。我之前已经公开表示支持MS Connect,所以我不再多说了——我认为我不需要这样做
最终更新(2011年2月23日)
它是固定的,但尚未发布。引用MS团队关于MS Connect错误的话:
是的,已经修好了。我们正在研究如何最好地发布修复程序。它已经在4.5版本中修复,但我们确实希望在4.5版本之前修复一批代码生成错误
当异常发生时,为什么不检查调用堆栈面板中记录的内容?调用堆栈本身可以说明很多问题
此外,使用SOS.dll和WinDbg进行低级别调试也可以告诉您很多信息。“因此它一定是.net中的bug”可能不是。请发布
MyButton\u Click\u Aux
的代码,请在MyButton\u Click\u Aux中包含代码。顺便说一句,我应该指出,您无法捕获StackOverflowException
。StackOverflowException
是一种致命的情况,因为嵌套调用太多,导致执行堆栈崩溃。这意味着你的程序已经病入膏肓,应该拔掉插头。CLR就是这么做的,它会杀死你的进程。-9?他只是在寻求帮助@Jonathan Wood我没有复述我知道递归我有很多c编码经验这是我认为必须的形式bug@PRASHANT:不,你还没有确定。我很高兴您更新了问题中的代码,但是try
块中没有显示的是什么?我是否从注释中了解到丢失的代码是导致溢出的原因?无论如何,VisualStudio有一个很棒的调试器。逐步完成代码。然后可以确定f
try { }
finally
{
var doesNothing = new DoesNothing();
doesNothing.NonVirtualFooBox(reportCount);
}