Коли і чому компілятор ініціалізує пам'ять до 0xCD, 0xDD і т.д. на malloc / free / new / delete?


129

Я знаю, що компілятор іноді ініціалізує пам'ять з певними шаблонами, такими як 0xCDі 0xDD. Я хочу знати, коли і чому це відбувається.

Коли

Чи використовується ця специфіка для компілятора?

Є malloc/newі free/deleteроботу таким же чином , щодо цього?

Це конкретна платформа?

Чи відбудеться це в інших операційних системах, таких як Linuxабо VxWorks?

Чому?

Я розумію, що це відбувається лише в Win32конфігурації налагодження, і він використовується для виявлення перевитрат пам'яті та допомоги компілятору в винятку винятків.

Чи можете ви навести якісь практичні приклади, як корисна ця ініціалізація?

Я пам’ятаю, що читав щось (можливо, у Code Complete 2), кажучи про те, що при розподілі пам’яті добре ініціалізувати пам’ять до відомого шаблону, і певні шаблони спричинить переривання, в Win32результаті яких у відладчику з’являться винятки.

Наскільки це портативно?

Відповіді:


191

Короткий підсумок того, що використовують компілятори Microsoft для різних бітів невідомої / неініціалізованої пам'яті під час компіляції в режимі налагодження (підтримка може відрізнятися від версії компілятора):

Value     Name           Description 
------   --------        -------------------------
0xCD     Clean Memory    Allocated memory via malloc or new but never 
                         written by the application. 

0xDD     Dead Memory     Memory that has been released with delete or free. 
                         It is used to detect writing through dangling pointers. 

0xED or  Aligned Fence   'No man's land' for aligned allocations. Using a 
0xBD                     different value here than 0xFD allows the runtime
                         to detect not only writing outside the allocation,
                         but to also identify mixing alignment-specific
                         allocation/deallocation routines with the regular
                         ones.

0xFD     Fence Memory    Also known as "no mans land." This is used to wrap 
                         the allocated memory (surrounding it with a fence) 
                         and is used to detect indexing arrays out of 
                         bounds or other accesses (especially writes) past
                         the end (or start) of an allocated block.

0xFD or  Buffer slack    Used to fill slack space in some memory buffers 
0xFE                     (unused parts of `std::string` or the user buffer 
                         passed to `fread()`). 0xFD is used in VS 2005 (maybe 
                         some prior versions, too), 0xFE is used in VS 2008 
                         and later.

0xCC                     When the code is compiled with the /GZ option,
                         uninitialized variables are automatically assigned 
                         to this value (at byte level). 


// the following magic values are done by the OS, not the C runtime:

0xAB  (Allocated Block?) Memory allocated by LocalAlloc(). 

0xBAADF00D Bad Food      Memory allocated by LocalAlloc() with LMEM_FIXED,but 
                         not yet written to. 

0xFEEEFEEE               OS fill heap memory, which was marked for usage, 
                         but wasn't allocated by HeapAlloc() or LocalAlloc(). 
                         Or that memory just has been freed by HeapFree(). 

Відмова: таблиця складається з якихось нотаток, які я лежу навколо - вони можуть бути не на 100% правильними (або узгодженими).

Багато з цих значень визначені в vc / crt / src / dbgheap.c:

/*
 * The following values are non-zero, constant, odd, large, and atypical
 *      Non-zero values help find bugs assuming zero filled data.
 *      Constant values are good, so that memory filling is deterministic
 *          (to help make bugs reproducible).  Of course, it is bad if
 *          the constant filling of weird values masks a bug.
 *      Mathematically odd numbers are good for finding bugs assuming a cleared
 *          lower bit.
 *      Large numbers (byte values at least) are less typical and are good
 *          at finding bad addresses.
 *      Atypical values (i.e. not too often) are good since they typically
 *          cause early detection in code.
 *      For the case of no man's land and free blocks, if you store to any
 *          of these locations, the memory integrity checker will detect it.
 *
 *      _bAlignLandFill has been changed from 0xBD to 0xED, to ensure that
 *      4 bytes of that (0xEDEDEDED) would give an inaccessible address under 3gb.
 */

static unsigned char _bNoMansLandFill = 0xFD;   /* fill no-man's land with this */
static unsigned char _bAlignLandFill  = 0xED;   /* fill no-man's land for aligned routines */
static unsigned char _bDeadLandFill   = 0xDD;   /* fill free objects with this */
static unsigned char _bCleanLandFill  = 0xCD;   /* fill new objects with this */

Також є кілька разів, коли час налагодження буде відтворювати буфери (або частини буферів) з відомим значенням, наприклад, "слабкий" простір у std::stringвиділенні або буфер переданий fread(). У цих випадках використовується значення, задане ім'ям _SECURECRT_FILL_BUFFER_PATTERN(визначеним у crtdefs.h). Я не впевнений, коли саме він був представлений, але це було в режимі налагодження принаймні VS 2005 (VC ++ 8).

Спочатку значення, яке використовували для заповнення цих буферів, було 0xFD- те саме значення, яке використовується для нічиїх земель. Однак у VS 2008 (VC ++ 9) значення було змінено на 0xFE. Я припускаю, що це тому, що можуть виникнути ситуації, коли операція заповнення буде проходити повз кінець буфера, наприклад, якщо б абонент передав розмір буфера, який був занадто великим fread(). У цьому випадку значення0xFD може не викликати виявлення цього перевиконання, оскільки якби розмір буфера був занадто великим лише на один, значення заповнення було б таким самим, як і значення землі для людини, яке використовується для ініціалізації цього каналу. Ніяка зміна в нічиїй землі не означає, що перевитрату не помітять.

Таким чином, значення заливки було змінено у VS 2008, щоб такий випадок змінив нічийну земельну канарку, що призвело до виявлення проблеми під час виконання.

Як зазначали інші, одне з ключових властивостей цих значень полягає в тому, що якщо змінна вказівник з одним із цих значень буде відкликана, це призведе до порушення доступу, оскільки в стандартній 32-розрядної конфігурації Windows адреси користувальницького режиму не перевищуватиме 0x7fffffff.


1
Я не знаю, чи є він на MSDN - я з'єднав його звідси-небудь або, можливо, отримав його з якогось іншого веб-сайту.
Майкл Берр

2
О так - дещо з джерела CRT в DbgHeap.c.
Майкл Берр

Деякі з них є на MSDN ( msdn.microsoft.com/en-us/library/bebs9zyz.aspx ), але не всі. Хороший список.
sean e

3
@seane - FYI ваше посилання здається мертвим. Новий (текст удосконалений) доступний тут: msdn.microsoft.com/en-us/library/974tc9t1.aspx
Simon Mourier

Як називається ці блоки? Це бар'єр пам’яті, мембрана, огорожа пам’яті чи інструкція щодо забору ( en.wikipedia.org/wiki/Memory_barrier )?
kr85

36

Одним із приємних властивостей щодо значення заповнення 0xCCCCCCCC є те, що в складі x86 опкод 0xCC є кодом int3 , який є програмним переривом точки переривання. Отже, якщо ви коли-небудь спробуєте виконати код у неініціалізованій пам'яті, заповненій цим значенням заповнення, ви негайно натиснете точку розриву, і операційна система дозволить вам приєднати налагоджувач (або вбити процес).


6
І 0xCD - це intінструкція, тож виконання 0xCD 0xCD генерує an int CD, який також потрапить у пастку.
Тад Маршалл

2
У сучасному світі запобігання виконанню даних навіть не дозволяє процесору отримувати інструкції з купи. Ця відповідь застаріла після XP SP2.
MSalters

2
@MSalters: Так, це правда, що за замовчуванням щойно виділена пам’ять буде невиконаною, але хтось може легко використовувати VirtualProtect()або mprotect()зробити пам'ять виконуваною.
Адам Розенфілд

Ви не можете виконати код з блоку даних. ВСЕ. Знову вгадайте.
Dan

9

Це компілятор і ОС, Visual studio встановлює різні види пам'яті на різні значення, так що в налагоджувальнику ви зможете легко побачити, чи потрапили ви в заблоковану пам'ять, фіксований масив або неініціалізований об'єкт. Хтось опублікує деталі, поки я їх гуглю ...

http://msdn.microsoft.com/en-us/library/974tc9t1.aspx


Я гадаю, що він використовується для перевірки, чи ви забули належним чином завершити свої рядки (оскільки надруковані ці 0xCD або 0xDD).
страгер

0xCC = неініціалізована локальна (стек) змінна 0xCD = неініціалізована змінна клас (heap?) 0xDD = видалена змінна
FryGuy

@FryGuy Існує практична причина, яка диктує (деякі) ці значення, як я пояснюю тут .
Гленн Слейден

4

Це не ОС - це компілятор. Ви також можете змінити поведінку - дивіться внизу цієї публікації.

Microsoft Visual Studio генерує (в режимі налагодження) двійковий файл, який попередньо заповнює стек пам'яті 0xCC. Він також вставляє простір між кожним фреймом стека, щоб виявити переповнення буфера. Тут дуже простий приклад того, де це корисно (на практиці Visual Studio виявив би цю проблему та видав би попередження):

...
   bool error; // uninitialised value
   if(something)
   {
      error = true;
   }
   return error;

Якщо Visual Studio не встановив попередні зміни змінних за відомим значенням, то потенційно важко знайти цю помилку. За допомогою преініціалізованих змінних (а точніше, преініціалізованої пам'яті стека) проблема може бути відтворена на кожному запуску.

Однак є невелика проблема. Значення, яке використовує Visual Studio, - ІСТИНА - все, крім 0, було б. Насправді цілком ймовірно, що при запуску коду в режимі випуску неітіалізовані змінні можуть бути виділені на частину пам'яті стека, яка, як правило, містить 0, а це означає, що ви можете мати неітіалізовану помилку змінної, яка проявляється лише в режимі випуску.

Це мене дратувало, тому я написав сценарій для зміни значення попереднього заповнення шляхом прямого редагування двійкових, що дозволило мені знайти неініталізовані проблеми змінної, які з'являються лише тоді, коли стек містить нуль. Цей сценарій змінює лише попереднє заповнення стека; Я ніколи не експериментував із попередньою заливкою купи, хоча це повинно бути можливим. Можливо, це стосуватиметься редагування DLL, що працює під час роботи, можливо, не буде.


1
Чи не видає VS попередження при використанні значення перед його ініціалізацією, як GCC?
страгер

3
Так, але не завжди, тому що це залежить від статичного аналізу. Отже, його досить легко переплутати з арифметикою вказівника.
Airsource Ltd

3
"Це не ОС - це компілятор." Насправді, це не компілятор - це бібліотека часу виконання.
Адріан Маккарті

Під час налагодження налагоджувач Visual Studio покаже значення bool, якщо не 0 або 1, з чимось на зразок true (204) . Тож порівняно легко побачити таку помилку, якщо простежити код.
Phil1970

4

Чи використовується ця специфіка для компілятора?

Насправді це майже завжди особливість бібліотеки часу виконання (як, наприклад, бібліотека часу виконання C). Час виконання зазвичай сильно співвідноситься з компілятором, але є деякі комбінації, які можна замінити.

Я вважаю, що в Windows купі налагодження (HeapAlloc тощо) також використовуються спеціальні шаблони заповнення, які відрізняються від тих, що надходять від malloc та вільних реалізацій в бібліотеці виконання налагодження C. Тож це може бути і функцією ОС, але в більшості випадків це лише бібліотека часу виконання мови.

Чи працює так, як malloc / new та free / delete однаково щодо цього?

Частина керування пам'яттю нових та видалених зазвичай реалізується з malloc та вільною, тому пам'ять, що виділяється новою та видаленою, зазвичай має однакові функції.

Це конкретна платформа?

Деталі конкретні для виконання. Фактичні значення, які використовуються, часто вибираються таким чином, щоб не лише виглядати незвично і очевидно, коли дивляться на шестигранний дамп, але розроблені так, щоб мати певні властивості, які можуть скористатись функціями процесора. Наприклад, непарні значення часто використовуються, оскільки вони можуть спричинити помилку вирівнювання. Великі значення використовуються (на відміну від 0), оскільки вони викликають дивовижні затримки, якщо ви переходите до неініціалізованого лічильника. На x86 0xCC - int 3інструкція, тому якщо ви виконаєте неініціалізовану пам'ять, вона потрапить у пастку.

Чи відбудеться це в інших операційних системах, таких як Linux або VxWorks?

Це здебільшого залежить від бібліотеки виконання, яку ви використовуєте.

Чи можете ви навести якісь практичні приклади, як ця ініціалізація корисна?

Я перерахував деякі вище. Ці значення, як правило, вибираються, щоб збільшити шанси на те, що трапиться щось незвичне, якщо ви робите щось з недійсними частинами пам'яті: тривалі затримки, пастки, помилки вирівнювання тощо. Менеджери купи також іноді використовують спеціальні значення заповнення для пропусків між розподілами. Якщо ці шаблони колись змінюються, він знає, що десь була погана запис (як перекриття буфера).

Я пам’ятаю, що читав щось (можливо, у Code Complete 2), що при розподілі пам'яті добре ініціалізувати пам'ять до відомої схеми, і певні шаблони спричинить переривання в Win32, що призведе до винятків, що відображаються у відладчику.

Наскільки це портативно?

Написання твердого коду (а може бути і повного коду ) розповідає про речі, які слід враховувати при виборі шаблонів заповнення. Деякі з них я згадав тут, а стаття Вікіпедії про Магічне число (програмування) також їх підсумовує. Деякі хитрощі залежать від специфіки процесора, який ви використовуєте (наприклад, потрібен він для вирівнювання читання і запису, а також значення, які відображаються в інструкціях, які потраплять у пастку). Інші хитрощі, як-от використання великих значень та незвичайних значень, які виділяються на дампі пам'яті, є більш портативними.



2

Очевидна причина "чому" полягає в тому, що припустимо, у вас такий клас:

class Foo
{
public:
    void SomeFunction()
    {
        cout << _obj->value << endl;
    }

private:
    SomeObject *_obj;
}

І тоді ви створили одне ім'я Fooта зателефонували SomeFunction, воно дасть порушення доступу, намагаючись прочитати0xCDCDCDCD . Це означає, що ви забули щось ініціалізувати. Ось "чому частина". Якщо ні, то вказівник, можливо, вишикувався якоюсь іншою пам'яттю, і це буде важче налагодити. Це просто повідомити вам причину порушення доступу. Зауважте, що цей випадок був досить простим, але у великому класі легко помилитися.

AFAIK, це працює лише на компіляторі Visual Studio лише в режимі налагодження (на відміну від випуску)


Ваше пояснення не випливає, оскільки ви також отримаєте порушення доступу, намагаючись прочитати 0x00000000, що було б так само корисно (або більше, як погана адреса). Як я зазначив в іншому коментарі на цій сторінці, справжньою причиною 0xCD0xCC) є те, що вони є інтерпретованими кодами x86, які викликають переривання програмного забезпечення, і це дозволяє витончене відновлення до налагоджувача лише в одній конкретній і рідкісній помилки а саме, коли ЦП помилково намагається виконувати байти в некодовій області. Окрім цього функціонального використання, значення заповнення є лише рекомендаційними підказками.
Гленн Слейден

2

Легко помітити, що пам’ять змінилася від її початкового початкового значення, як правило, під час налагодження, але іноді і для коду випуску, оскільки ви можете додавати налагоджувачі до процесу, поки він працює.

Це не лише пам'ять, багато налагоджувачів встановлюватимуть вміст регістра на дозорне значення при запуску процесу (деякі версії AIX встановлюють деякі регістри, на 0xdeadbeefякі є м'яко жартівливими).


1

Компілятор IBM XLC має опцію "initauto", яка призначить автоматичним змінним значення, яке ви вказали. Я використовував наступні для своїх налагоджень:

-Wc,'initauto(deadbeef,word)'

Якби я подивився на сховище неініціалізованої змінної, було б встановлено значення 0xdeadbeef

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.