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


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

, когда

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

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

это специфическая платформа?

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

почему

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

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

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

насколько это портативно?

9 101

9 ответов:

краткое описание того, что компиляторы 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'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 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'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 */

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

это специфическая платформа?

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

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

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

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

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

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

насколько это портативно?

Написание Твердого Кода (и, возможно,Код) говорит о вещах, которые следует учитывать при выборе шаблонов заливки. Я упомянул некоторые из них здесь, и статья Википедии о магическое число (Программирование) также суммирует их. Некоторые из трюков зависят от специфики используемого процессора (например, требует ли он выровненных операций чтения и записи и какие значения карта с инструкциями, которые будут ловушки). Другие приемы, такие как использование больших значений и необычных значений, которые выделяются в дампе памяти, более переносимы.

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

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

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

private:
    SomeObject *_obj;
}

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

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

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

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

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

-Wc,'initauto(deadbeef,word)'

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