Quando e perché un sistema operativo invierà la memoria a 0xCD, 0xDD, ecc. Su malloc / free / new / delete?

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

quando

Questo è specifico per il compilatore utilizzato?

In questo modo, malloc / new e free / delete funzionano allo stesso modo?

È specifico per piattaforma?

Si verificherà su altri sistemi operativi, come Linux o VxWorks?

Perché

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

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

Ricordo di aver letto qualcosa (forse in Code Complete 2) che è opportuno inizializzare la memoria su un pattern conosciuto quando lo si assegna, e alcuni pattern generano interrupt in Win32 che generano eccezioni nel debugger.

Quanto è portatile questo?

Un breve riepilogo di ciò che i compilatori di Microsoft utilizzano per vari bit di memoria non condivisa / non inizializzata quando compilato per la modalità di debug (il supporto può variare a seconda della 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. 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(). 

Disclaimer: il tavolo è tratto da alcune note che ho trovato – 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 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 (ie 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 riempie i buffer (o parti di buffer) con un valore noto, ad esempio lo spazio “allentato” nell’allocazione di std::string o il buffer passato a fread() . Questi casi usano un valore dato il nome _SECURECRT_FILL_BUFFER_PATTERN (definito in crtdefs.h ). Non sono sicuro di quando è stato introdotto, ma è stato nel runtime di debug almeno da VS 2005 (VC ++ 8).

Inizialmente il valore utilizzato per riempire questi buffer era 0xFD , lo stesso valore utilizzato per la terra di nessuno. Tuttavia, in VS 2008 (VC ++ 9) il valore è stato modificato in 0xFE . Presumo che ciò sia dovuto al fatto che potrebbero verificarsi situazioni in cui l’operazione di riempimento verrebbe eseguita oltre la fine del buffer, ad esempio se il chiamante ha superato una dimensione del buffer troppo grande per essere fread() . In tal caso, il valore 0xFD potrebbe non triggersre il rilevamento di questo sovraccarico poiché se la dimensione del buffer fosse troppo grande per uno solo, il valore di riempimento sarebbe lo stesso del valore di terra di nessun uomo utilizzato per inizializzare quel canarino. Nessun cambiamento nella terra di nessuno significa che il superamento non verrà notato.

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

Come altri hanno notato, una delle proprietà chiave di questi valori è che una variabile puntatore con uno di questi valori è dereferenziata, provocherà una violazione di accesso, poiché su una configurazione standard di Windows a 32 bit, gli indirizzi della modalità utente non andare più in alto di 0x7fffffff.

Una bella proprietà sul valore di riempimento 0xCCCCCCCC è che nell’assembly x86, l’opcode 0xCC è l’opcode int3 , che è l’interruzione del breakpoint del software. Quindi, se provate a eseguire codice nella memoria non inizializzata che è stata riempita con quel valore di riempimento, otterrete immediatamente un punto di interruzione, e il sistema operativo vi permetterà di colbind un debugger (o uccidere il processo).

Compilatore e sistema operativo specifico, Visual Studio imposta diversi tipi di memoria su valori diversi, in modo che nel debugger si possa facilmente vedere se ci si trova in una memoria malloced, un array fisso o un object non inizializzato. Qualcuno pubblicherà i dettagli mentre li sto cercando su google …

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

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 file binario che pre-riempie la memoria dello stack con 0xCC. Inserisce anche uno spazio tra ogni frame dello stack per rilevare overflow del buffer. Un esempio molto semplice di dove questo è utile è qui (in pratica Visual Studio individuerebbe questo problema ed emetterebbe un avvertimento):

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

Se Visual Studio non ha preinitializzato le variabili su un valore noto, allora questo bug potrebbe essere difficile da trovare. Con le variabili preinizializzate (o meglio, la memoria dello stack pre-inizializzata), il problema è riproducibile ad ogni esecuzione.

Tuttavia, c’è un piccolo problema. Il valore utilizzato da Visual Studio è TRUE – qualsiasi cosa eccetto 0. In realtà è molto probabile che quando si esegue il codice in modalità Release le variabili unitializzate possano essere allocate a un frammento di memoria che contiene 0, il che significa che si può avere un bug variabile unitializzato che si manifesta solo in modalità Release.

Questo mi ha infastidito, così ho scritto uno script per modificare il valore di pre-riempimento modificando direttamente il file binario, permettendomi di trovare problemi variabili non inizializzati che si manifestano 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 ansible. Potrebbe comportare la modifica della DLL run-time, potrebbe non.

Questo è specifico per il compilatore utilizzato?

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

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

In questo modo, malloc / new e free / delete funzionano allo stesso modo?

La parte di gestione della memoria di new e delete viene solitamente implementata con malloc e free, quindi la memoria allocata con new e delete di solito hanno le stesse caratteristiche.

È specifico per piattaforma?

I dettagli sono specifici del runtime. I valori effettivi usati sono spesso scelti non solo per sembrare inusuali ed evidenti quando si guarda un dump esadecimale, ma sono progettati per avere certe proprietà che possono trarre vantaggio dalle caratteristiche del processore. Ad esempio, vengono spesso utilizzati valori dispari, poiché potrebbero causare un errore di allineamento. Vengono utilizzati valori grandi (anziché 0), perché causano ritardi sorprendenti se si esegue il ciclo su un contatore non inizializzato. Su x86, 0xCC è un’istruzione int 3 , quindi se si esegue una memoria non inizializzata, verrà intrappolata.

Si verificherà su altri sistemi operativi, come Linux o VxWorks?

Dipende principalmente dalla libreria di runtime che si usa.

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

Ho elencato alcuni sopra. I valori vengono generalmente scelti per aumentare le probabilità che si verifichi qualcosa di insolito se si fa qualcosa con porzioni di memoria non valide: lunghi ritardi, trappole, errori di allineamento, ecc. I gestori di heap talvolta utilizzano anche valori di riempimento speciali per gli intervalli tra le allocazioni. Se questi modelli cambiano, sa che c’è stata una brutta scrittura (come un sovraccarico del buffer) da qualche parte.

Ricordo di aver letto qualcosa (forse in Code Complete 2) che è opportuno inizializzare la memoria su un pattern conosciuto quando lo si assegna, e alcuni pattern generano interrupt in Win32 che generano eccezioni nel debugger.

Quanto è portatile questo?

Scrivere codice solido (e magari codice completo ) parla delle cose da considerare quando si scelgono i modelli di riempimento. Ne ho menzionato alcuni qui e l’articolo di Wikipedia su Magic Number (programmazione) li riassume anche. Alcuni trucchi dipendono dalle specifiche del processore che stai utilizzando (ad esempio se richiede letture e scritture allineate e quali valori mappano alle istruzioni che verranno intercettate). Altri trucchi, come l’utilizzo di valori grandi e valori insoliti che si distinguono in un dump della memoria sono più portabili.

Questo articolo descrive modelli di bit di memoria insoliti e varie tecniche che è ansible utilizzare se si verificano questi valori.

La ragione ovvia del “perché” è che supponiamo di avere una class come questa:

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

E quindi istanziate uno a Foo e chiamate SomeFunction , vi darà una violazione di accesso cercando di leggere 0xCDCDCDCD . Ciò significa che hai dimenticato di inizializzare qualcosa. Questo è il "perché parte". In caso contrario, il puntatore potrebbe essere 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. Nota che questo caso era abbastanza semplice, ma in una class più grande è facile commettere quell'errore.

AFAIK, funziona solo sul compilatore di Visual Studio quando si trova in modalità di debug (al contrario di rilascio)

È facile vedere che la memoria è cambiata dal suo valore iniziale iniziale, generalmente durante il debug ma a volte anche per il codice di rilascio, dal momento che è ansible colbind i debugger al processo mentre è in esecuzione.

Inoltre, molti debugger imposteranno i contenuti del registro su un valore sentinella all’avvio del processo (alcune versioni di AIX imposteranno i registri su 0xdeadbeef che è leggermente divertente).

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

-Wc,'initauto(deadbeef,word)'

Se osservassi la memorizzazione di una variabile non inizializzata, sarebbe impostata su 0xdeadbeef