Когда и почему ОС инициализирует память 0xCD, 0xDD и т.д. На malloc/free/new/delete?

Я знаю, что ОС иногда инициализирует память с помощью определенных шаблонов, таких как 0xCD и 0xDD. Я хочу знать, что , когда и почему это происходит.

<Н2 > ПриН2 >

Является ли это специфическим для используемого компилятора?

Делают ли malloc/new и free/delete работать таким же образом в отношении этого?

Является ли он специфичным для платформы?

Будет ли это происходить в других операционных системах, таких как Linux или VxWorks?

Почему

Я понимаю, что это происходит только в конфигурации отладки Win32 и используется для обнаружения переполнения памяти и для исключения исключения компилятора.

Можете ли вы привести практические примеры того, как эта инициализация полезна?

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

Насколько переносимым является это?

Ответ 1

Краткое описание того, что компиляторы 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. 
                         Used to detect writing through dangling pointers. 

0xED or  Aligned Fence   'No man 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 detect 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 reproducable).  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 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 land with this */
static unsigned char _bAlignLandFill  = 0xED;   /* fill no-man land for aligned routines */
static unsigned char _bDeadLandFill   = 0xDD;   /* fill free objects with this */
static unsigned char _bCleanLandFill  = 0xCD;   /* fill new objects with this */

Есть также несколько раз, когда время выполнения отладки будет заполнять буферы (или части буферов) известным значением, например, "slack" space в распределении std::string или буфером, переданным в fread(). В этих случаях используется значение с именем _SECURECRT_FILL_BUFFER_PATTERN (определено в crtdefs.h). Я не уверен точно, когда это было введено, но оно было в отладочной среде исполнения по крайней мере на VS 2005 (VС++ 8).

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

Таким образом, значение заливки было изменено в VS 2008, так что такой случай изменил бы ни одного человека наземного канала, что привело бы к обнаружению проблемы по времени выполнения.

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

Ответ 2

Одно приятное свойство значения 0xCCCCCCCC - это то, что в сборке x86 код операции 0xCC представляет собой код операции int3, который является прерыванием прерывания программного обеспечения, Итак, если вы когда-либо пытаетесь выполнить код в неинициализированной памяти, заполненный этим значением заполнения, вы сразу же попадете в точку останова, и операционная система позволит вам подключить отладчик (или убить процесс).

Ответ 3

Этот компилятор и конкретная ОС, Visual studio устанавливает разные типы памяти для разных значений, так что в отладчике вы можете легко увидеть, переполнен ли вы в malloced память, фиксированный массив или неинициализированный объект. Кто-то опубликует детали, пока я их буду искать...

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

Ответ 4

Это не ОС - это компилятор. Вы также можете изменить поведение - см. Нижнюю часть этого сообщения.

Microsoft Visual Studio генерирует (в режиме отладки) двоичный файл, который предварительно заполняет стек памяти 0xCC. Он также вставляет пробел между кадром стека, чтобы обнаружить переполнение буфера. Очень простой пример того, где это полезно здесь (на практике Visual Studio обнаружит эту проблему и выдает предупреждение):

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

Если Visual Studio не преминиализовало переменные к известному значению, тогда эта ошибка может быть трудно найти. С преинициализированными переменными (точнее, с предыализированной памятью стека) проблема воспроизводится при каждом запуске.

Однако есть небольшая проблема. Значение Visual Studio использует TRUE - все, кроме 0, будет. На самом деле вполне вероятно, что когда вы запускаете свой код в режиме деблокирования, то унифицированные переменные могут быть выделены для части памяти стека, которая содержит 0, что означает, что вы можете иметь унифицированную ошибку переменной, которая проявляется только в режиме деблокирования.

Это меня раздражало, поэтому я написал script, чтобы изменить значение pre-fill, непосредственно редактируя двоичный файл, позволяя мне найти неинициализированные переменные проблемы, которые появляются только тогда, когда стек содержит нуль. Этот script только модифицирует предварительную заливку стека; Я никогда не экспериментировал с предварительной загрузкой кучи, хотя это должно быть возможно. Может потребоваться редактирование DLL во время выполнения, возможно, нет.

Ответ 5

Является ли это специфическим для используемого компилятора?

Фактически, это почти всегда функция библиотеки времени выполнения (например, библиотека времени выполнения C). Время выполнения обычно сильно коррелирует с компилятором, но есть некоторые комбинации, которые вы можете обменять.

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

Делают ли malloc/new и free/delete работать таким же образом в отношении этого?

Часть управления памятью нового и удаления обычно реализуется с помощью malloc и бесплатно, поэтому память, выделенная новым и удаленным, обычно имеет те же функции.

Является ли он специфичным для платформы?

Детали являются специфичными для исполнения. Используемые фактические значения часто выбираются так, чтобы не только выглядеть необычно и очевидно при просмотре шестнадцатеричного дампа, но и иметь определенные свойства, которые могут использовать преимущества процессора. Например, часто используются нечетные значения, поскольку они могут вызвать ошибку выравнивания. Большие значения используются (в отличие от 0), поскольку они вызывают неожиданные задержки, если вы зацикливаетесь на неинициализированный счетчик. На x86 0xCC является инструкцией int 3, поэтому, если вы выполняете неинициализированную память, она будет ловушкой.

Будет ли это происходить в других операционных системах, таких как Linux или VxWorks?

В основном это зависит от используемой библиотеки времени выполнения.

Можете ли вы привести практические примеры того, как эта инициализация полезна?

Я перечислил некоторые выше. Значения, как правило, выбираются для увеличения шансов на то, что что-то необычное произойдет, если вы сделаете что-то с недопустимыми частями памяти: длительные задержки, ловушки, ошибки выравнивания и т.д. Администраторы кучи также иногда используют специальные значения заполнения для разрывов между распределениями. Если эти шаблоны когда-либо меняются, он знает, что где-то там была плохая запись (например, переполнение буфера).

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

Насколько переносимым является это?

Написание Solid Code (и, возможно, Code Complete) говорит о том, что нужно учитывать при выборе шаблонов заполнения. Я упомянул о некоторых из них здесь, а также в статье Wikipedia по Magic Number (программирование). Некоторые из трюков зависят от специфики используемого процессора (например, требуется ли для него согласованные чтения и записи и какие значения соответствуют инструкциям, которые будут ловушки). Другие трюки, такие как использование больших значений и необычных значений, выделяющихся в дампе памяти, более переносимы.

Ответ 6

Очевидная причина "почему" заключается в том, что у вас есть такой класс:

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

private:
    SomeObject *_obj;
}

И затем вы создаете экземпляр a Foo и вызываете SomeFunction, это приведет к нарушению доступа, пытаясь прочитать 0xCDCDCDCD. Это означает, что вы забыли инициализировать что-то. Это "почему-то". Если нет, то указатель, возможно, выстроился в другую память, и было бы сложнее отладить. Это просто позволяет вам узнать причину, по которой вы получаете нарушение доступа. Обратите внимание, что этот случай был довольно прост, но в более крупном классе легко сделать эту ошибку.

AFAIK, это работает только в компиляторе Visual Studio в режиме отладки (в отличие от выпуска)

Ответ 7

Легко видеть, что память изменилась с начального начального значения, как правило, во время отладки, но иногда и для кода выпуска, поскольку вы можете прикреплять отладчики к процессу во время его работы.

Это не просто память, многие отладчики будут устанавливать содержимое регистра на значение дозорного элемента при запуске процесса (некоторые версии AIX будут устанавливать некоторые регистры на 0xdeadbeef, который будет слегка юмористическим).

Ответ 8

В этой статье описываются необычные шаблоны бит памяти и различные методы, которые вы можете использовать, если вы сталкиваетесь с этими значениями.

Ответ 9

Компилятор IBM XLC имеет параметр "initauto", который присваивает автоматическим переменным значение, которое вы указываете. Я использовал следующие для своих отладочных сборников:

-Wc,'initauto(deadbeef,word)'

Если бы я посмотрел на хранилище неинициализированной переменной, он был бы установлен в 0xdeadbeef