Я намагався з'ясувати, як обробляються зворотні виклики компілятором C #.
(Відповідь: це не так. Але 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 на будь-якому з:
- `` Оптимізувати збірку '' ВИМКНЕНО (налагодження або випуск)
- Ціль: x86
- Ціль: AnyCPU + "Віддати перевагу 32 біту" (це нове у VS 2012, і я вперше це бачив. Докладніше тут .)
- Деякі, здавалося б, нешкідливі гілки в коді (див. Коментар "else" гілки).
З іншого боку , з допомогою «Оптимізувати збірки» ON + (Target = x64 або AnyCPU з «Воліють 32bit» OFF (на 64 - бітних CPU)), TCE відбувається , і лічильник продовжує обертатися назавжди (ок, це , можливо , закручує вниз кожен раз , коли його значення переповнюється ).
Але я помітив , що поведінка я не можу пояснити , в StackOverflowException
разі: (?) Він ніколи не відбувається в точно ту ж саму глибину стеки. Ось результати кількох 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 без налагоджувача. Коли програма аварійно завершує роботу, я підключаю налагоджувач і перевіряю значення лічильника.
І це все одно не однаково на декількох прогонах. (Або мій тест є хибним.)
ОНОВЛЕННЯ: Закриття
Як запропонував fejesjoco, я вивчив ASLR (рандомізація макета адресного простору).
Це техніка безпеки, яка заважає атакам переповнення буфера знаходити точне розташування (наприклад) конкретних системних викликів шляхом рандомізації різних речей в адресному просторі процесу, включаючи позицію стека та, мабуть, його розмір.
Теорія звучить добре. Давайте застосуємо це на практиці!
Для того, щоб перевірити це, я використав спеціальний для завдання інструмент Microsoft: EMET або The Enhanced Mitigation Experience Toolkit . Це дозволяє встановити прапор ASLR (і багато іншого) на рівні системи або процесу.
(Існує також загальносистемна альтернатива злому реєстру, яку я не пробував)
Для того, щоб перевірити ефективність інструменту, я також виявив, що Провідник процесів належним чином повідомляє статус прапора ASLR на сторінці "Властивості" процесу. Ніколи цього не бачив до сьогодні :)
Теоретично EMET може (повторно) встановити прапор ASLR для одного процесу. На практиці це, здається, нічого не змінило (див. Зображення вище).
Однак я вимкнув ASLR для всієї системи, і (одна перезавантаження пізніше), нарешті, міг переконатися, що справді виняток SO тепер завжди відбувається на одній глибині стека.
БОНУС
Пов’язані з ASLR, у попередніх новинах: Як Chrome отримав pwned