Quando e perché un compilatore inizializzerà la memoria su 0xCD, 0xDD, ecc. Su malloc / free / new / delete?


129

So che il compilatore a volte inizializza la memoria con determinati schemi come 0xCDe 0xDD. Quello che voglio sapere è quando e perché questo accade.

quando

Questo è specifico per il compilatore utilizzato?

Fare malloc/newe free/deletelavorare allo stesso modo in merito a questo?

È specifico per la piattaforma?

Si verificherà su altri sistemi operativi, come Linuxo VxWorks?

Perché

La mia comprensione è che ciò si verifica solo nella Win32configurazione di debug e viene utilizzato per rilevare sovraccarichi di memoria e per aiutare il compilatore a rilevare le eccezioni.

Puoi fare qualche esempio pratico su come questa inizializzazione sia utile?

Ricordo di aver letto qualcosa (forse in Code Complete 2) che diceva che è bene inizializzare la memoria su uno schema noto durante l'allocazione, e alcuni schemi attiveranno interruzioni in Win32cui compaiono eccezioni nel debugger.

Quanto è portatile?

Risposte:


191

Un breve riepilogo di ciò che i compilatori di Microsoft usano per vari bit di memoria non posseduta / non inizializzata quando compilati per la modalità debug (il supporto può variare in base alla versione del compilatore):

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(). 

Dichiarazione di non responsabilità: la tabella è tratta da alcune note che ho in giro - potrebbero non essere corrette al 100% (o coerenti).

Molti di questi valori sono definiti in 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 */

Ci sono anche alcune volte in cui il runtime di debug riempirà i buffer (o parti di buffer) con un valore noto, ad esempio lo spazio "lento" std::stringnell'allocazione o il buffer passato fread(). Questi casi usano un valore dato il nome _SECURECRT_FILL_BUFFER_PATTERN(definito in crtdefs.h). Non sono sicuro esattamente quando è stato introdotto, ma era in fase di debug almeno da VS 2005 (VC ++ 8).

Inizialmente, il valore usato per riempire questi buffer era 0xFD- lo stesso valore usato per la terra di nessuno. Tuttavia, in VS 2008 (VC ++ 9) il valore è stato modificato in 0xFE. Suppongo sia perché potrebbero esserci situazioni in cui l'operazione di riempimento verrebbe eseguita oltre la fine del buffer, ad esempio, se il chiamante ha passato una dimensione del buffer troppo grande per fread(). In tal caso, il valore 0xFDpotrebbe non innescare il rilevamento di questo sovraccarico poiché se la dimensione del buffer fosse troppo grande di uno solo, il valore di riempimento sarebbe lo stesso del valore di terra di nessuno utilizzato per inizializzare quel canarino. Nessun cambiamento nella terra di nessuno significa che il superamento non sarebbe stato notato.

Quindi il valore di riempimento è stato modificato in VS 2008 in modo tale che un caso del genere cambierebbe il canarino terrestre di nessuno, con conseguente rilevamento del problema da parte del runtime.

Come altri hanno notato, una delle proprietà chiave di questi valori è che se una variabile del puntatore con uno di questi valori viene de-referenziata, si tradurrà in una violazione di accesso, poiché su una configurazione standard di Windows a 32 bit, gli indirizzi della modalità utente non supererà 0x7fffffff.


1
Non so se sia su MSDN - l'ho messo insieme da qui e lì o forse l'ho preso da qualche altro sito web.
Michael Burr,

2
Oh sì, in parte proviene dalla fonte CRT in DbgHeap.c.
Michael Burr,

Alcuni di questi sono su MSDN ( msdn.microsoft.com/en-us/library/bebs9zyz.aspx ), ma non tutti. Buona lista.
sean e

3
@seane - Cordiali saluti, il tuo link sembra morto. Il nuovo (il testo è stato migliorato) è disponibile qui: msdn.microsoft.com/en-us/library/974tc9t1.aspx
Simon Mourier,

Qual è il nome di questi blocchi? Si tratta di barriera di memoria, membar, memoria di recinzione o istruzione di recinzione ( en.wikipedia.org/wiki/Memory_barrier )?
Kr85,

36

Una proprietà interessante del valore di riempimento 0xCCCCCCCC è che nell'assembly x86, il codice operativo 0xCC è il codice operativo int3 , ovvero l'interruzione del punto di interruzione del software. Quindi, se mai provi ad eseguire il codice nella memoria non inizializzata che è stata riempita con quel valore di riempimento, otterrai immediatamente un punto di interruzione e il sistema operativo ti permetterà di collegare un debugger (o uccidere il processo).


6
E 0xCD è l' intistruzione, quindi l'esecuzione di 0xCD 0xCD genererà un int CD, che anche trap.
Tad Marshall,

2
Nel mondo di oggi, Data Execution Prevention non consente nemmeno alla CPU di recuperare un'istruzione dall'heap. Questa risposta è obsoleta da XP SP2.
MSalters il

2
@MSalters: Sì, è vero che per impostazione predefinita, la memoria appena allocata non sarà eseguibile, ma qualcuno potrebbe facilmente utilizzare VirtualProtect()o mprotect()rendere eseguibile la memoria.
Adam Rosenfield,

Non è possibile eseguire il codice da un blocco dati. MAI. Indovina di nuovo.
Dan,

9

È specifico per il compilatore e il sistema operativo, Visual Studio imposta diversi tipi di memoria su valori diversi in modo che nel debugger sia possibile vedere facilmente se si è passati alla memoria malloced, a un array fisso o a un oggetto non inizializzato. Qualcuno pubblicherà i dettagli mentre li sto cercando su Google ...

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


La mia ipotesi è che viene utilizzato per verificare se si dimentica di terminare correttamente anche le stringhe (poiché vengono stampati quegli 0xCD o 0xDD).
Strager

0xCC = variabile locale (stack) non inizializzata 0xCD = variabile non inizializzata (heap?) Variabile 0xDD = variabile cancellata
FryGuy

@FryGuy C'è un motivo pratico che determina (alcuni di) questi valori, come spiego qui .
Glenn Slayden,

4

Non è il sistema operativo, è il compilatore. Puoi anche modificare il comportamento: vedi in fondo a questo post.

Microsoft Visual Studio genera (in modalità Debug) un binario che pre-riempie la memoria dello stack con 0xCC. Inserisce inoltre uno spazio tra ogni frame dello stack per rilevare gli overflow del buffer. Un esempio molto semplice di dove questo è utile è qui (in pratica Visual Studio individuerebbe questo problema ed emettere un avviso):

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

Se Visual Studio non preinizializza le variabili su un valore noto, questo errore potrebbe essere difficile da trovare. Con variabili preinizializzate (o meglio, memoria di stack preinizializzate), il problema è riproducibile ad ogni corsa.

Tuttavia, c'è un leggero problema. Il valore utilizzato da Visual Studio è TRUE, qualunque cosa tranne 0 sarebbe. In realtà è molto probabile che quando si esegue il codice in modalità Rilascio, le variabili unitizzate possano essere allocate a un pezzo di memoria dello stack che contiene 0, il che significa che è possibile avere un bug variabile unitizzato che si manifesta solo in modalità Rilascio.

Questo mi ha infastidito, quindi ho scritto uno script per modificare il valore di pre-riempimento modificando direttamente il binario, permettendomi di trovare problemi variabili non inizializzati che si presentano solo quando lo stack contiene uno zero. Questo script modifica solo il pre-riempimento dello stack; Non ho mai sperimentato il pre-riempimento dell'heap, anche se dovrebbe essere possibile. Potrebbe comportare la modifica della DLL di runtime, potrebbe non farlo.


1
VS non emette un avviso quando si utilizza un valore prima che venga inizializzato, come GCC?
Strager

3
Sì, ma non sempre, perché dipende dall'analisi statica. Di conseguenza è abbastanza facile confonderlo con l'aritmetica del puntatore.
Airsource Ltd,

3
"Non è il sistema operativo, è il compilatore." In realtà, non è il compilatore, è la libreria di runtime.
Adrian McCarthy,

Durante il debug, il debugger di Visual Studio mostrerà il valore di un valore bool se non 0 o 1 con qualcosa come true (204) . Quindi è relativamente facile vedere quel tipo di bug se si traccia il codice.
Phil1970,

4

Questo è specifico per il compilatore utilizzato?

In realtà, è quasi sempre una caratteristica della libreria di runtime (come la libreria di runtime C). Il runtime di solito è fortemente correlato con il compilatore, ma ci sono alcune combinazioni che è possibile scambiare.

Credo su Windows, l'heap di debug (HeapAlloc, ecc.) Utilizza anche schemi di riempimento speciali che sono diversi da quelli che provengono dal malloc e dalle implementazioni gratuite nella libreria di runtime C di debug. Quindi potrebbe anche essere una funzionalità del sistema operativo, ma la maggior parte delle volte è solo la libreria di runtime della lingua.

Malloc / new e free / delete funzionano allo stesso modo al riguardo?

La parte di gestione della memoria di new ed delete è di solito implementata con malloc e free, quindi la memoria allocata con new ed delete di solito hanno le stesse funzionalità.

È specifico per la piattaforma?

I dettagli sono specifici del runtime. I valori effettivi utilizzati sono spesso scelti non solo per apparire insoliti e ovvi quando si guarda a un dump esadecimale, ma sono progettati per avere determinate proprietà che possono trarre vantaggio dalle funzionalità del processore. Ad esempio, vengono spesso utilizzati valori dispari, poiché potrebbero causare un errore di allineamento. Vengono utilizzati valori di grandi dimensioni (anziché 0), poiché causano ritardi sorprendenti se si esegue il loop su un contatore non inizializzato. Su x86, 0xCC è int 3un'istruzione, quindi se si esegue una memoria non inizializzata, verrà intercettata.

Accadrà su altri sistemi operativi, come Linux o VxWorks?

Dipende principalmente dalla libreria di runtime in uso.

Puoi fare qualche esempio pratico su come questa inizializzazione sia utile?

Ne ho elencato alcuni sopra. I valori sono generalmente scelti per aumentare le possibilità che accada qualcosa di insolito se si fa qualcosa con porzioni di memoria non valide: lunghi ritardi, trappole, errori di allineamento, ecc. I gestori di heap usano talvolta anche valori di riempimento speciali per gli spazi tra le allocazioni. Se questi schemi cambiano mai, sa che c'è stata una cattiva scrittura (come un buffer sovraccarico) da qualche parte.

Ricordo di aver letto qualcosa (forse in Code Complete 2) che è bene inizializzare la memoria su uno schema noto durante l'allocazione, e alcuni schemi attiveranno interruzioni in Win32 che comporteranno eccezioni mostrate nel debugger.

Quanto è portatile?

Scrivere Solid Code (e forse Code Complete ) parla delle cose da considerare quando si scelgono i pattern di riempimento. Ne ho citati alcuni qui e anche l'articolo di Wikipedia su Magic Number (programmazione) li riassume. Alcuni trucchi dipendono dalle specifiche del processore che stai utilizzando (come se richiede letture e scritture allineate e quali valori associano alle istruzioni che intrappoleranno). Altri trucchi, come l'utilizzo di valori di grandi dimensioni e valori insoliti che si distinguono in un dump della memoria, sono più portabili.



2

La ragione ovvia del "perché" è che supponi di avere una classe come questa:

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

private:
    SomeObject *_obj;
}

E poi si crea un'istanza Fooe si chiama SomeFunction, si darà una violazione di accesso cercando di leggere0xCDCDCDCD . Ciò significa che hai dimenticato di inizializzare qualcosa. Questo è il "perché parte". In caso contrario, il puntatore potrebbe essersi allineato con qualche altra memoria e sarebbe più difficile eseguire il debug. Ti sta solo facendo sapere il motivo per cui ricevi una violazione di accesso. Si noti che questo caso è stato piuttosto semplice, ma in una classe più ampia è facile commettere questo errore.

AFAIK, questo funziona solo sul compilatore di Visual Studio quando è in modalità debug (al contrario della versione)


La tua spiegazione non segue, dal momento che potresti anche provare a leggere una violazione di accesso 0x00000000, che sarebbe altrettanto utile (o più, di un indirizzo errato). Come ho sottolineato in un altro commento in questa pagina, la vera ragione di 0xCD(e 0xCC) è che sono codici operativi x86 interpretabili che innescano un interruzione del software, e ciò consente un recupero grazioso nel debugger in un solo singolo specifico e raro tipo di errore , vale a dire, quando la CPU tenta erroneamente di eseguire byte in una regione non di codice. Oltre a questo uso funzionale, i valori di riempimento sono solo suggerimenti di consulenza, come si nota.
Glenn Slayden,

2

È facile vedere che la memoria è cambiata rispetto al suo valore iniziale iniziale, generalmente durante il debug ma a volte anche per il codice di rilascio, poiché è possibile collegare i debugger al processo mentre è in esecuzione.

Non è solo memoria, molti debugger imposteranno i contenuti dei registri su un valore sentinella all'avvio del processo (alcune versioni di AIX imposteranno alcuni registri per i 0xdeadbeefquali è leggermente umoristico).


1

Il compilatore XLC IBM ha un'opzione "initauto" che assegnerà alle variabili automatiche un valore specificato. Ho usato quanto segue per le mie build di debug:

-Wc,'initauto(deadbeef,word)'

Se osservassi l'archiviazione di una variabile non inizializzata, sarebbe impostata su 0xdeadbeef

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.