Почему рекурсивный вызов вызывает StackOverflow на разных глубинах стека?

Я пытался понять, как хвостовые вызовы обрабатываются компилятором C #.

(Ответ:Oни'нет. Но64-битный JIT БУДЕТ делать TCE (устранение хвостовых вызовов).Ограничения применяются.)

Поэтому я написал небольшой тест с использованием рекурсивного вызова, который печатает, сколько раз он вызывается доStackOverflowException убивает процесс.

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

Прямо по сигналу, программа заканчивается SO Exception для любого из: '

Оптимизировать сборку ВЫКЛ (отладка или выпуск)Цель: x86Цель: AnyCPU + "Предпочитаю 32 бит (это новое в VS 2012, и я впервые это увидел.Больше здесь.)Некоторая, казалось бы, безобидная ветвь в коде (см.еще» ветка).

И наоборот, используяОптимизировать сборку ON + (Target = x64 или AnyCPU с 'Предпочитаю 32bit OFF (на 64-битном процессоре)), происходит TCE, и счетчик продолжает вращаться вечно (хорошо, это возможно вращаетсявниз каждый раз, когда его значение переполняется).

Но я заметил, что могуобъяснить вStackOverflowException case: это никогда (?) не происходит вименно так та же глубина стека. Вот результаты нескольких 32-разрядных прогонов, Release build:

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.

И отладка сборки:

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.

Размер стека постоянен (по умолчанию 1 МБ). Стек кадров размеры постоянны.

Итак, что может объяснить (иногда нетривиальное) изменение глубины стека, когдаStackOverflowException хиты?

ОБНОВИТЬ

Ханс Пассант поднимает вопрос оConsole.WriteLine касание P / Invoke, взаимодействия и, возможно, недетерминированной блокировки.

Поэтому я упростил код до этого:

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

Я запустил его в Release / 32bit / Optimization ON без отладчика. Когда программа падает, я присоединяю отладчик и проверяю значение счетчика.

И этоеще ISN»То же самое на нескольких трассах. (Или мой тест некорректен.)

ОБНОВЛЕНИЕ: Закрытие

По предложению fejesjoco, я изучил ASLR (рандомизация расположения адресного пространства).

Это'Это техника безопасности, которая затрудняет атакам переполнения буфера точное определение местоположения (например, определенных системных вызовов) путем случайного выбора различных вещей в адресном пространстве процесса, включая позицию стека и, по-видимому, его размер.

Теория звучит хорошо. Позволять'с практикой!

Чтобы проверить это, я использовал инструмент Microsoft, специфичный для этой задачи:EMET или расширенный инструментарий по смягчению последствий, Это позволяет установить флаг ASLR (и многое другое) на уровне системы или процесса.

(Существует такжеобщесистемная альтернатива взлому реестра что я непопробую)

Чтобы проверить эффективность инструмента, я также обнаружил, чтоProcess Explorer должным образом сообщает о состоянии флага ASLR вСвойства страница процесса. Никогда такого не видел до сегодняшнего дня :)

Теоретически, EMET может (повторно) установить флаг ASLR для одного процесса. На практике это неКажется, что-то изменить (см. изображение выше).

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

БОНУС

Связанные с ASLR, в более старых новостях:Как Chrome получил pwned

Ответы на вопрос(2)

Ваш ответ на вопрос