Как отследить причину возникновения StackOverflowException в .NET? - PullRequest
32 голосов
/ 03 февраля 2011

Я получаю 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 ]

44 голосов
/ 04 февраля 2011

Я знаю, как это предотвратить

Я просто не знаю, почему это происходит (пока).И может показаться, что вы действительно нашли ошибку либо в .Net BCL, либо, что более вероятно, в JIT.

Я просто закомментировал все строки в методе MyButton_Click_Aux, а затем начал возвращать их обратно.в, один за другим.

Снимите volatile со статического int, и вы больше не получите 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);
  //}
}  

Мне также было интересно, было ли это как-то связано с неинициализированным reportCount при попытке /наконец-то в. Но не имеет значения, если вы инициализируете его в ноль.

Я ищусейчас в IL - хотя для этого может потребоваться кто-то с некоторыми ASM-партнерами ...

Окончательное обновление Как я уже сказал, для этого действительно потребуется анализ выходных данных JIT длядействительно понимаю, что происходит, и хотя мне интересно анализировать ассемблер - я чувствую, что это, возможно, работа для кого-то в Microsoft, так что эту ошибку можно фактически подтвердить и исправить!Тем не менее, это кажется довольно узким набором обстоятельств.

Я перешел к сборке релиза, чтобы избавиться от всего шума 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

разница?Ну, есть два, которые я заметил - бокс 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);
}

Упс - StackOverflowException.

И:

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

Упс снова!StackOverflowException!

Мы могли бы пойти дальше с этим - но я чувствую, что проблема явно связана с боксом volatile int, в то время как внутри блока finally try / catch ... Я помещаюКод внутри попробовать, и нет проблем.Я добавил предложение catch (и поместил туда код), также без проблем.

Это также может относиться к боксу других типов значений, я думаю.

Итак, для подведения итогов - в.Net 4.0 - как в сборках отладки, так и в выпуске - бокс volatile int в блоке finally, как представляется, заставляет JIT генерировать код, который в итоге заполняет стекТот факт, что трассировка стека просто показывает «внешний код», также поддерживает это предложение.

Существует даже вероятность того, что она не всегда может быть воспроизведена и может даже зависеть от компоновки и размера генерируемого кода.попробуй / наконец.Это явно связано с ошибкой jmp или с созданием чего-то подобного в неправильном месте, которое в конечном итоге повторяет одну или несколько команд push в стек.Откровенно говоря, идея о том, что это вызвано на самом деле операцией с ящиком, захватывающая!

Окончательное финальное обновление

Если вы посмотрите на ошибку MS Connect, то @HastyG найдено (ответьте ниже) - вы видите, что ошибка проявляется похожим образом, но с изменчивым bool в выражении catch .

Также - MS поставила исправление в очередь после того, как запустила его для воспроизведения, но через 7 месяцев исправление недоступно До этого я записывался как участник поддержки MS Connect, поэтому больше ничего не скажу - не думаю, что мне нужно!

Окончательное окончательное окончательное обновление (23/02/2011)

Исправлено - но еще не выпущено. Цитата из MS Team об ошибке MS Connect:

Да, это исправлено. Мы находимся в процессе выяснения, как лучше всего отправить исправление. Это уже исправлено в 4.5, но мы действительно хотели бы исправить пакет ошибок генерации кода до выпуска 4.5.

12 голосов
/ 03 февраля 2011

Ошибка в вашем коде.Предположительно, MyButton_Click_Aux() вызывает повторный ввод какого-либо метода.Однако вы необъяснимым образом опустили этот код в своем вопросе, поэтому никто не может его комментировать.

0 голосов
/ 06 февраля 2011

Когда происходит это исключение, почему бы не проверить, что было записано на панели стека вызовов? Сам стек вызовов может многое рассказать.

Кроме того, низкоуровневая отладка с использованием SOS.dll и WinDbg также может многое вам рассказать.

0 голосов
/ 03 февраля 2011

Log перезванивает в Log?Это также может вызвать SO.

...