c# - 対策 - vb.net stackoverflowexception 回避




.NETでStackOverflowExceptionの原因を追跡するにはどうすればよいですか? (3)

その例外が発生した場合、コールスタックパネルに記録された内容を確認してください。 コールスタック自体は、多くを伝えることができます。

また、SOS.dllとWinDbgを使用した低レベルのデバッグでも多くのことが分かります。

次のコードを実行すると、 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_Click_Aux()はいくつかのメソッドを再入力させます。 しかし、あなたはあなたの質問からそのコードを不可解に省略したので、誰もそれにコメントすることはできません。


私はそれが起こるのを止める方法を知っている

なぜ私はそれが(まだ)それを引き起こすのか分かりません。 そして、あなたは実際に.Net BCLか、おそらくはJITのいずれかでバグを発見したようです。

私はちょうどMyButton_Click_Auxメソッドのすべての行をMyButton_Click_Aux 、次にそれらを1つずつ戻し始めました。

static intからvolatileを取り除くと、もはやExceptionが発生しなくなります。

今、なぜ研究していますか?明らかにメモリバリアと関係することが問題を引き起こしています - おそらく何とかMyButton_Click_Auxメソッドを強制的に呼び出して...

更新

さて、他の人々が.Net 3.5は問題ではないことを知っています。

私は.Nt 4も使用していますので、これらのコメントはそれに関連しています:

私が言ったように、揮発性を取り除くとそれは動作します。

同じように、もしあなたが揮発性のバックを入れて、try / finallyを削除すると、それも動作します:

private static void MyButton_Click_Aux()
{
  //try { /*remove because s 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は揮発性です。 それは間違いなく実行されます。 ここに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のボクシングとバーチャルコール - 私が見つけた2つです。 そこで私はこれらの2つのクラスをセットアップしました:

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) { }
}

そして、違反する方法のこれらの4つのバージョンのそれぞれを試みました:

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 - Exception

そして:

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

再びおっと! Exception

私たちはさらにこれに行くことができる - しかし、問題は、私は明らかに、try / catchのfinallyブロックの内部にある間、volatile intのボクシングによって引き起こされる...私はtry内のコードを入れ、問題はありません。 私はcatch節を追加し(そしてそこにコードを入れた)、問題はなかった。

それは私が推測する他の値の種類のボクシングにも適用することができます。

つまり、.Net 4.0では、デバッグとリリースビルドの両方で要約すると、最終的にブロック内の揮発性intのボクシングは、JITにスタックを満たす最後のコードを生成させるように見えます。 スタックトレースが単に「外部コード」を表示しているという事実も、この命題をサポートしています。

いつも再現することができず、try / finallyによって生成されたコードのレイアウトやサイズにも依存する可能性があります。 これは、間違ったjmpや、間違った場所に生成された同様のものとは明らかに関係しており、最終的にスタックに1つ以上のプッシュコマンドを繰り返します。 それが実際にボックス操作によって引き起こされているという考えは、率直に、魅力的です!

最終的な最終更新

あなたが@Hasty Gが見つけたMS Connectのバグを見れば(さらに答えてみると)、バグは同様の方法で現れますが、 キャッチステートメントではバブルが激しくなります。

また、 - MSはこれをreproにした後に修正をキューに入れましたが、まだ7ヶ月後に修正プログラムはありません。 私はMS Connectのサポートとして以前に記録に残っているので、私はもう言わないだろう - 私は必要とは思わない!

ファイナルファイナルファイナル・アップデート(23/02/2011)

修正済みですが、まだリリースされていません。 MS ConnectバグのMSチームからの引用:

はい、修正されました。 私たちは、修正プログラムをどのように配布するのが最適かを把握しています。 これは4.5で修正されていますが、4.5リリース前にコード生成バグのバッチを修正したいと思っています。







stack-overflow