Почему рекурсивный вызов вызывает StackOverflow на разных глубинах стека?
Я пытался выяснить, как хвостовые вызовы обрабатываются компилятором C#.
(ответ: это не так. но 64bit JIT (s) будет делать 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();
}
прямо по сигналу, программа заканчивается с таким исключением на любом оф:
- 'оптимизировать сборку' выкл (отладка или выпуск)
- цель: x86
- Target: AnyCPU + "Prefer 32 bit" (это новое в VS 2012 и в первый раз, когда я его увидел. здесь.)
- некоторая, казалось бы, безобидная ветвь в коде (см. прокомментированную ветвь "else").
и наоборот, используя "оптимизировать сборку" на + (Target = x64 или AnyCPU с "Prefer 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 без отладчика. Когда программа выходит из строя, я подключаю отладчик и проверяю значение счетчика.
и еще не то же самое на несколько рейсов. (Или мой тест ошибочен.)
обновление: закрытие
как предложил fejesjoco, я заглянул в ASLR (рандомизация макета адресного пространства).
это метод безопасности, который затрудняет атаки переполнения буфера, чтобы найти точное местоположение (например) конкретных системных вызовов, путем рандомизации различных вещей в адресном пространстве процесса, включая позицию стека и, видимо, его размер.
теория звучит хорошо. Давайте воплотим это в жизнь!
чтобы проверить это, я использовал инструмент Microsoft конкретные задачи: EMET или расширенный набор инструментов для смягчения последствий. Это позволяет установить флаг ASLR (и многое другое) на уровне системы или процесса.
(Существует также общесистемная альтернатива взлома реестра, которую я не пробовал)
для проверки эффективность инструмента, я также обнаружил, что Process Explorer должным образом сообщает о состоянии флага ASLR на странице "свойства" процесса. Никогда не видел этого до сегодняшнего дня:)
теоретически EMET может (re)установить флаг ASLR для одного процесса. На практике это, похоже, ничего не изменило (см. изображение выше).
тем не менее, я отключил ASLR для всей системы и (одна перезагрузка позже) я мог бы, наконец, проверить, что действительно так исключение теперь всегда происходит на той же глубине стека.
бонус
ASLR-related, в старых новостях:как хром получил pwned
2 ответа:
Я думаю, что это может быть ASLR на работе. Вы можете отключить DEP, чтобы проверить эту теорию.
смотрите здесь для служебного класса C#, чтобы проверить информацию о памяти:https://stackoverflow.com/a/8716410/552139
кстати, с помощью этого инструмента, я обнаружил, что разница между максимальным и минимальным размером стека составляет около 2 Кб, который полстраницы. Это странно.
обновление: хорошо, теперь я знаю, что я прав. Я проследил за теорией полстраницы, и нашел этот документ, который рассматривает реализацию ASLR в Windows: http://www.symantec.com/avcenter/reference/Address_Space_Layout_Randomization.pdf
цитата:
Как только стек был помещен, начальный указатель стека далее рандомизированная случайной декрементной величиной. Начальное смещение равно некоторые до половины страницы (2048 байт)
и это ответ на ваш вопрос. ASLR забирает между 0 и 2048 байт вашего начального стека случайным образом.
изменить
r.Next()
доr.Next(10)
.StackOverflowException
s должно происходить на той же глубине.генерируемые строки должны потреблять одинаковую память, потому что они имеют одинаковый размер.
r.Next(10).ToString().Length == 1
всегда.r.Next().ToString().Length
переменной.то же самое применяется, если вы используете
r.Next(100, 1000)