¿Por qué una llamada recursiva causa StackOverflow a diferentes profundidades de pila?

Estaba intentando descubrir cómo el compilador de C # maneja las llamadas de cola.

(Responder:Ellos no están. Pero elJIT (s) de 64 bits HACEREMOS TCE (eliminación de la llamada de cola).Se aplican restricciones.)

Así que escribí una pequeña prueba utilizando una llamada recursiva que imprime cuántas veces se llama antes delStackOverflowException mata el proceso

class Program
{
    static void Main(string[] args)
    {
        Rec();
    }

    static int sz = 0;
    static Random r = new Random();
    static void Rec()
    {
        sz++;

        //uncomment for faster, more imprecise runs
        //if (sz % 100 == 0)
        {
            //some code to keep this method from being inlined
            var zz = r.Next();  
            Console.Write("{0} Random: {1}\r", sz, zz);
        }

        //uncommenting this stops TCE from happening
        //else
        //{
        //    Console.Write("{0}\r", sz);
        //}

        Rec();
    }

Justo en el momento justo, el programa termina con la excepción SO en cualquiera de:

'Optimizar compilación' OFF (ya sea debug o release)Objetivo: x86Objetivo: AnyCPU + "Preferir 32 bits" (esto es nuevo en VS 2012 y la primera vez que lo vi.Más aquí.)Alguna rama aparentemente inocua en el código (vea la rama comentada 'else').

A la inversa, al usar 'Optimizar compilación' ON + (Objetivo = x64 o AnyCPU con 'Preferir 32 bits' APAGADO (en una CPU de 64 bits)), el TCE sucede y el contador sigue girando para siempre (está bien, podría decirse que giraabajo cada vez que su valor se desborda).

Pero noté un comportamiento que no puedo explicar. en elStackOverflowException caso: nunca (?) sucede enexactamente la misma profundidad de pila. Aquí están las salidas de unas pocas ejecuciones de 32 bits, Versión de lanzamiento:

51600 Random: 1778264579
Process is terminated due to StackOverflowException.

51599 Random: 1515673450
Process is terminated due to StackOverflowException.

51602 Random: 1567871768
Process is terminated due to StackOverflowException.

51535 Random: 2760045665
Process is terminated due to StackOverflowException.

Y la construcción de depuración:

28641 Random: 4435795885
Process is terminated due to StackOverflowException.

28641 Random: 4873901326  //never say never
Process is terminated due to StackOverflowException.

28623 Random: 7255802746
Process is terminated due to StackOverflowException.

28669 Random: 1613806023
Process is terminated due to StackOverflowException.

El tamaño de pila es constante (por defecto a 1 MB). Los tamaños de los marcos de pila son constantes.

Entonces, ¿qué puede explicar la variación (a veces no trivial) de la profundidad de la pila cuando elStackOverflowException ¿golpes?

ACTUALIZAR

Hans Passant plantea el tema deConsole.WriteLine tocando P / Invocar, interoperabilidad y posiblemente bloqueo no determinístico.

Así que simplifiqué el código a esto:

class Program
{
    static void Main(string[] args)
    {
        Rec();
    }
    static int sz = 0;
    static void Rec()
    {
        sz++;
        Rec();
    }
}

Lo ejecuté en Release / 32bit / Optimization ON sin un depurador. Cuando el programa falla, adjunto el depurador y reviso el valor del contador.

Y esotodavía No es lo mismo en varias carreras. (O mi prueba es defectuosa.)

ACTUALIZACIÓN: Cierre

Según lo sugerido por fejesjoco, busqué en ASLR (aleatorización del diseño del espacio de direcciones).

Es una técnica de seguridad que dificulta que los ataques de desbordamiento de búfer encuentren la ubicación precisa de (por ejemplo,) llamadas específicas del sistema, aleatorizando varias cosas en el espacio de direcciones del proceso, incluida la posición de la pila y, aparentemente, su tamaño.

La teoría suena bien. ¡Vamos a ponerlo en práctica!

Para probar esto, usé una herramienta de Microsoft específica para la tarea:EMET o el kit de herramientas de experiencia de mitigación mejorada. Permite establecer el indicador ASLR (y mucho más) en un nivel de sistema o proceso.
(También hay unaEn todo el sistema, alternativa de hacking de registro. que no lo intenté)

Para verificar la efectividad de la herramienta, también descubrí queExplorador de procesos Informa debidamente el estado del indicador ASLR en la página 'Propiedades' del proceso. Nunca vi eso hasta hoy :)

Teóricamente, EMET puede (re) establecer el indicador ASLR para un solo proceso. En la práctica, no parece cambiar nada (ver la imagen de arriba).

Sin embargo, deshabilité ASLR para todo el sistema y (un reinicio más tarde) finalmente pude verificar que, de hecho, la excepción SO ahora siempre ocurre a la misma profundidad de pila.

PRIMA

Relacionado con ASLR, en noticias anteriores:Cómo Chrome consiguió pwned

Respuestas a la pregunta(2)

Su respuesta a la pregunta