2011-02-02 12 views
29

me siento un StackOverflowException cuando corro el siguiente código:¿Cómo realizo un seguimiento de la causa de una StackOverflowException en .NET?

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 
} 

¿Cuál podría ser la causa de la StackOverflowException?

+34

"debe, por lo tanto, ser un error en .net" Probablemente no. – jason

+8

Por favor, publique el código para 'MyButton_Click_Aux' –

+7

Incluya el código en MyButton_Click_Aux. –

Respuesta

44

sé cómo evitar que suceda

Sólo que no sé por qué se hace que (todavía). Y parece que de hecho has encontrado un error en el .Net BCL o, más probablemente, en el JIT.

Acabo de comentar todas las líneas en el método MyButton_Click_Aux y luego comencé a traerlas de nuevo, una por una.

Quite el volatile de la int estática y ya no obtendrá un StackOverflowException.

ahora a la investigación por qué ... Es evidente que algo que ver con las barreras de memoria está causando un problema - quizás alguna manera forzar el método MyButton_Click_Aux a llamarse ...

ACTUALIZACIÓN

Bueno por lo que otras personas están descubriendo que .Net 3.5 no es un problema.

estoy usando .nt 4, así por lo que estos comentarios se refieren a lo siguiente:

Como ya he dicho, tarda el volátil y funciona.

Igualmente, si se pone la volatilidad de nuevo y quitar el try/finally también funciona:

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); 
    //} 
} 

También me preguntaba si era algo que ver con la no inicializado reportCount cuando el try/finally está en . Pero no importa si lo inicias a cero.

estoy mirando a la IL ahora - aunque podría requerir alguien con algunas grietas ASM para que se involucren ...

Informe final de actualización Como digo, esto realmente va a requerir un análisis de la Salida JIT para entender realmente lo que está sucediendo y, aunque me resulta divertido analizar el ensamblador, creo que probablemente sea un trabajo para alguien en Microsoft, por lo que este error puede ser confirmado y solucionado. Dicho eso, parece ser un conjunto bastante limitado de circunstancias.

He pasado a una versión de lanzamiento para deshacerme de todo el ruido de IL (nops, etc.) para el análisis.

Esto, sin embargo, ha tenido un impacto complicado en el diagnóstico. Pensé que lo tenía pero no lo hice, pero ahora sé lo que es.

yo probamos este código:

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

Con el int tan volátil. Funciona sin culpa. Aquí está la 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 

Entonces nos fijamos en el código mínimo necesario para obtener de nuevo el error:

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

Y es 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 

La diferencia? Bueno, hay dos que vi: el boxeo de la int volátil y una llamada virtual. Por lo tanto, estas dos clases de configuración:

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

y luego trató de cada una de estas cuatro versiones del método infractor:

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

que trabaja.

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

Cual también funciona.

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

Oops - StackOverflowException.

Y:

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

Vaya de nuevo! StackOverflowException!

Podríamos ir más lejos con esto, pero el problema es, creo, claramente causado por el boxeo del int volátil mientras dentro del último bloque de un try/catch ... puse el código dentro del try, y No hay problema. Agregué una cláusula catch (y puse el código allí), tampoco hay problema.

También podría aplicarse al boxeo de otros tipos de valores, supongo.

Por lo tanto, para resumir, en .Net 4.0, en compilaciones tanto de depuración como de liberación, el boxeo de un int volátil en un bloque finally parece causar que el JIT genere código que termina llenando la pila. El hecho de que el seguimiento de la pila simplemente muestre "código externo" también respalda esta proposición.

Incluso existe la posibilidad de que no siempre se pueda reproducir e incluso dependa de la disposición y el tamaño del código generado por try/finally. Está claro que tiene algo que ver con un error jmp o algo similar que se genera en la ubicación incorrecta que finalmente repite uno o más comandos push en la pila. ¡La idea de que eso sea causado por una operación de caja es, francamente, fascinante!

final Informe final de actualización

Si nos fijamos en el error de MS Conectar que @Hasty G encontró (responder más abajo) - que se ve allí que los manifiestos errores de una forma similar, pero con un bool volátil en una declaración catch.

Además, MS en cola una solución para esto después de obtenerlo para reproducir, pero aún no hay una revisión disponible después de 7 meses. Ya lo he registrado anteriormente como soporte de MS Connect, así que no diré nada más: ¡no creo que sea necesario!

actualización final final final (23/02/2011)

Se fija - pero aún no depurados. Cita del MS Team en el error MS Connect:

Sí, está solucionado. Estamos en el proceso de descubrir la mejor forma de enviar una solución. Ya está arreglado en 4.5, pero realmente nos gustaría arreglar un lote de errores de generación de código antes de la versión 4.5.

+0

@Andras Zoltan no he considerado la palabra clave volátil que anteriormente estaba en uso con muchos hilos, pero ya no tiene ningún propósito, así que puedo eliminar el problema muy extraño –

+0

@PRASHANT P - hay más. También eliminé el try \ finally de todo el bloque y dejé el volátil activado - y listo - no más StackOverflow ... –

+0

Sí, noté que al eliminar el try/finalmente me deshice de él. No lo sugerí, ya que supuse que había una razón por la que tenías eso allí desde el principio. – dotalchemy

12

El error está en su código. Presumiblemente, MyButton_Click_Aux() hace que se vuelva a ingresar algún método. Sin embargo, inexplicablemente omitiste ese código de tu pregunta y nadie puede comentarlo.

+0

@Jonathan Wood no tengo reingreso Sé de recursividad Tengo mucha experiencia en la codificación c Esta es la forma solo creo que debe ser un error –

+0

@PRASHANT: No, usted no ha establecido ese. Me alegro de que hayas actualizado el código en tu pregunta, pero ¿qué hay en tu bloque 'try' que no se muestra? ¿Entiendo por el comentario que el código faltante es lo que causa el desbordamiento? En cualquier caso, Visual Studio tiene un maravilloso depurador. Paso a paso por el código. Luego puede determinar con certeza si no hay reingreso, y si no puede ver exactamente dónde ocurre el error. –

+0

@Joathanathan Wood He hecho este código en el intento, no importa, ahora solo tengo simplemente formulario con 1 botón y el seguimiento de la pila solo tiene .net soldados –

0

¿Log vuelve a llamar a Log? Esto también causaría un SO.

+0

@Mark Avenius esto es obvio que tengo editar y estoy eliminando todo el código de Log, ni siquiera recibe la llamada –

+0

@Prashant: puede parecer obvio , pero es, sin embargo, un lugar donde podría ocurrir un SO, así que lo mencioné porque no puedo ver qué está haciendo 'Log'. Solo trato de ayudar ... –

+1

@Mark Avenius siento haber recibido muchas críticas sarcásticas en ninguna respuesta. Nunca pretendo insultar –

0

Cuando ocurre esa excepción, ¿por qué no verificar lo que se grabó en el panel de la pila de llamadas? La pila de llamadas puede decir mucho.

Además, la depuración de bajo nivel con SOS.dll y WinDbg también puede decirte mucho.

Cuestiones relacionadas