c++ - puntero - ¿Cuándo y por qué un SO inicializará la memoria a 0xCD, 0xDD, etc. en malloc/free/new/delete?
puntero a puntero (9)
¿Es esto específico para el compilador utilizado?
En realidad, casi siempre es una característica de la biblioteca de tiempo de ejecución (como la biblioteca C runtime). El tiempo de ejecución suele estar fuertemente correlacionado con el compilador, pero hay algunas combinaciones que puede intercambiar.
Creo que en Windows, el montón de depuración (HeapAlloc, etc.) también utiliza patrones de relleno especiales que son diferentes de los que provienen de las implementaciones malloc y libre en la biblioteca de tiempo de ejecución de C de depuración. Por lo tanto, también puede ser una característica del sistema operativo, pero la mayoría de las veces es solo la biblioteca de tiempo de ejecución del idioma.
¿Malloc / new y free / delete funcionan de la misma manera con respecto a esto?
La porción de administración de memoria de new y delete generalmente se implementan con malloc y libre, por lo que la memoria asignada con new y delete generalmente tiene las mismas características.
¿Es específico de la plataforma?
Los detalles son específicos del tiempo de ejecución. Los valores reales utilizados a menudo se eligen para que no solo se vean inusuales y obvios cuando se mira un volcado hexadecimal, sino que están diseñados para tener ciertas propiedades que pueden aprovechar las características del procesador. Por ejemplo, a menudo se usan valores impares, porque podrían causar un error de alineación. Se usan valores grandes (en oposición a 0), ya que causan demoras sorprendentes si realiza un bucle en un contador no inicializado. En x86, 0xCC es una instrucción int 3
, por lo que si ejecuta una memoria no inicializada, se bloqueará.
¿Ocurrirá en otros sistemas operativos, como Linux o VxWorks?
En su mayoría depende de la biblioteca de tiempo de ejecución que utilice.
¿Puedes dar ejemplos prácticos de cómo esta inicialización es útil?
Enumeré algunos arriba. Generalmente, los valores se eligen para aumentar las posibilidades de que ocurra algo inusual si se hace algo con porciones inválidas de la memoria: largas demoras, trampas, fallas de alineación, etc. Los gestores de montones también a veces usan valores de relleno especiales para las separaciones entre asignaciones. Si esos patrones cambian alguna vez, sabe que hubo una escritura incorrecta (como un desbordamiento del búfer) en alguna parte.
Recuerdo haber leído algo (tal vez en Code Complete 2) que es bueno inicializar la memoria a un patrón conocido al asignarlo, y ciertos patrones desencadenarán interrupciones en Win32 que darán lugar a excepciones en el depurador.
¿Qué tan portátil es esto?
Escribir Solid Code (y tal vez Code Complete ) habla sobre cosas que se deben tener en cuenta al elegir patrones de relleno. He mencionado algunos de ellos aquí, y el artículo de Wikipedia sobre Magic Number (programación) también los resume. Algunos de los trucos dependen de los detalles del procesador que está utilizando (como si requiere lecturas y escrituras alineadas y qué valores se asignan a las instrucciones que atraparán). Otros trucos, como el uso de valores grandes y valores inusuales que se destacan en un volcado de memoria, son más portátiles.
Sé que el SO algunas veces iniciará la memoria con ciertos patrones como 0xCD y 0xDD. Lo que quiero saber es cuándo y por qué sucede esto.
Cuando
¿Es esto específico para el compilador utilizado?
¿Malloc / new y free / delete funcionan de la misma manera con respecto a esto?
¿Es específico de la plataforma?
¿Ocurrirá en otros sistemas operativos, como Linux o VxWorks?
Por qué
Tengo entendido que esto solo ocurre en la configuración de depuración de Win32, y se usa para detectar sobrepases de memoria y para ayudar al compilador a detectar excepciones.
¿Puedes dar ejemplos prácticos de cómo esta inicialización es útil?
Recuerdo haber leído algo (tal vez en Code Complete 2) que es bueno inicializar la memoria a un patrón conocido al asignarlo, y ciertos patrones desencadenarán interrupciones en Win32 que darán lugar a excepciones en el depurador.
¿Qué tan portátil es esto?
El compilador IBM XLC tiene una opción "initauto" que asignará a las variables automáticas un valor que usted especifique. Usé lo siguiente para mis compilaciones de depuración:
-Wc,''initauto(deadbeef,word)''
Si miraba el almacenamiento de una variable no inicializada, se establecería en 0xdeadbeef
Es el compilador y el sistema operativo específico, Visual Studio establece diferentes tipos de memoria para diferentes valores para que en el depurador pueda ver fácilmente si ha sobrescrito en la memoria mal colocada, una matriz fija o un objeto no inicializado. Alguien publicará los detalles mientras los estoy buscando en Google ...
Es fácil ver que la memoria ha cambiado desde su valor de inicio inicial, generalmente durante la depuración, pero a veces también para el código de versión, ya que puede adjuntar depuradores al proceso mientras se está ejecutando.
Tampoco es solo memoria, muchos depuradores configurarán el contenido del registro en un valor de centinela cuando se inicie el proceso (algunas versiones de AIX establecerán algunos registros en 0xdeadbeef
que es ligeramente gracioso).
Este artículo describe patrones inusuales de bits de memoria y varias técnicas que puede usar si encuentra estos valores.
La razón obvia para el "por qué" es que supongamos que tiene una clase como esta:
class Foo
{
public:
void SomeFunction()
{
cout << _obj->value << endl;
}
private:
SomeObject *_obj;
}
Y luego crea una instancia de Foo
y llama SomeFunction
, se dará una violación de acceso tratando de leer 0xCDCDCDCD
. Esto significa que olvidaste inicializar algo. Ese es el "por qué parte". De lo contrario, el puntero podría haberse alineado con otra memoria y sería más difícil de depurar. Solo le informa el motivo por el que recibe una infracción de acceso. Tenga en cuenta que este caso fue bastante simple, pero en una clase más grande es fácil cometer ese error.
AFAIK, esto solo funciona en el compilador de Visual Studio cuando está en modo de depuración (a diferencia de la versión)
No es el sistema operativo, es el compilador. También puede modificar el comportamiento; consulte la parte inferior de esta publicación.
Microsoft Visual Studio genera (en modo Debug) un binario que rellena previamente la memoria de la pila con 0xCC. También inserta un espacio entre cada marco de pila para detectar desbordamientos de búfer. Un ejemplo muy simple de dónde esto es útil está aquí (en la práctica, Visual Studio detectaría este problema y emitiría una advertencia):
...
bool error; // uninitialised value
if(something)
{
error = true;
}
return error;
Si Visual Studio no preinicializó las variables a un valor conocido, entonces este error podría ser difícil de encontrar. Con las variables preinicializadas (o más bien, la memoria de pila preinicializada), el problema es reproducible en cada ejecución.
Sin embargo, hay un pequeño problema. El valor que Visual Studio usa es VERDADERO, cualquier cosa excepto 0 sería. En realidad, es bastante probable que cuando ejecute el código en modo Release, las variables unitarias puedan asignarse a una pieza de memoria de pila que contenga 0, lo que significa que puede tener una variable variable individualizada que solo se manifiesta en modo Release.
Eso me molestó, así que escribí un script para modificar el valor de pre-llenado editando directamente el binario, lo que me permitió encontrar problemas variables no inicializados que solo aparecen cuando la pila contiene un cero. Este script solo modifica el pre-llenado de la pila; Nunca experimenté con el relleno previo del montón, aunque debería ser posible. Puede implicar la edición de la DLL de tiempo de ejecución, puede que no.
Un resumen rápido de lo que los compiladores de Microsoft usan para varios bits de memoria sin titular / no inicializada cuando se compila para el modo de depuración (el soporte puede variar según la versión del compilador):
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().
Descargo de responsabilidad: la tabla proviene de algunas notas que tengo por ahí, es posible que no sean 100% correctas (o coherentes).
Muchos de estos valores se definen en 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 */
También hay unas pocas veces en que el tiempo de ejecución de depuración rellenará los búferes (o partes de los búferes) con un valor conocido, por ejemplo, el espacio ''slack'' en la asignación de std::string
o el búfer pasado a fread()
. Esos casos usan un valor dado el nombre _SECURECRT_FILL_BUFFER_PATTERN
(definido en crtdefs.h
). No estoy seguro exactamente cuando se introdujo, pero estaba en el tiempo de ejecución de depuración por al menos VS 2005 (VC ++ 8).
Inicialmente, el valor utilizado para llenar estos almacenamientos intermedios era 0xFD
, el mismo valor utilizado para la tierra de nadie. Sin embargo, en VS 2008 (VC ++ 9) el valor se cambió a 0xFE
. Supongo que se debe a que podría haber situaciones en las que la operación de relleno se ejecutaría más allá del final del búfer, por ejemplo, si la persona que llama pasó en un tamaño de búfer que era demasiado grande para fread()
. En ese caso, el valor 0xFD
podría no desencadenar la detección de este desbordamiento, ya que si el tamaño del búfer fuera demasiado grande en solo uno, el valor de relleno sería el mismo que el valor de tierra de nadie utilizado para inicializar ese canario. Ningún cambio en la tierra de nadie significa que no se notará el desbordamiento.
Entonces, el valor de relleno se modificó en VS 2008, por lo que ese caso cambiaría el canario de tierra de nadie, lo que daría como resultado la detección del problema por el tiempo de ejecución.
Como otros han notado, una de las propiedades clave de estos valores es que es una variable de puntero con uno de estos valores desreferenciados, resultará en una violación de acceso, ya que en una configuración estándar de Windows de 32 bits, las direcciones de modo de usuario no ir más alto que 0x7fffffff.
Una propiedad agradable sobre el valor de relleno 0xCCCCCCCC es que en el ensamblaje x86, el código de operación 0xCC es el int3 operación int3 , que es la interrupción del punto de interrupción del software. Entonces, si alguna vez intentas ejecutar código en la memoria no inicializada que se ha llenado con ese valor de relleno, inmediatamente alcanzarás un punto de interrupción, y el sistema operativo te permitirá conectar un depurador (o eliminar el proceso).