Подтвердить что ты не робот

Как определить причину исключения StackOverflow в .NET?

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

4b9b3361

Ответ 1

Я знаю, как остановить это.

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

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

Удалите volatile из статического int, и вы больше не получите StackOverflowException.

Теперь, чтобы исследовать, почему... Очевидно, что что-то делать с барьерами памяти вызывает проблему - возможно, как-то заставляя метод MyButton_Click_Aux называть себя...

UPDATE

Хорошо, поэтому другие люди находят, что .Net 3.5 не проблема.

Я использую .Nt 4, так что эти комментарии относятся к этому:

Как я уже сказал, отключайте батарею, и она работает.

В равной степени, если вы снова включите volatile и удалите try/, то он также будет работать:

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 как изменчивым. Он работает без сбоев. Здесь 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!

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

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

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

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

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

Если вы посмотрите на ошибку MS Connect, которую обнаружил @Hasty G (ответьте дальше) - вы видите, что ошибка проявляется аналогичным образом, но с volatile bool в выводе.

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

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

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

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

Ответ 2

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

Ответ 3

Возвращает ли журнал Log? Это также вызовет SO.

Ответ 4

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

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