Come eseguire il debug degli errori di corruzione dell'heap?


165

Sto eseguendo il debug di un'applicazione C ++ multi-thread (nativa) in Visual Studio 2008. In occasioni apparentemente casuali, ricevo un errore "Windows ha attivato un punto di interruzione ..." con una nota che ciò potrebbe essere dovuto a un danneggiamento nel mucchio. Questi errori non sempre arrestano immediatamente l'applicazione, anche se è probabile che si arresti in modo anomalo poco dopo.

Il grosso problema di questi errori è che vengono visualizzati solo dopo che la corruzione ha effettivamente avuto luogo, il che li rende molto difficili da rintracciare ed eseguire il debug, specialmente su un'applicazione multi-thread.

  • Che tipo di cose possono causare questi errori?

  • Come eseguo il debug?

Suggerimenti, strumenti, metodi, illuminazioni ... sono i benvenuti.

Risposte:


128

Application Verifier combinato con gli strumenti di debug per Windows è una configurazione straordinaria. Puoi ottenere entrambi come parte del Windows Driver Kit o del più leggero Windows SDK . (Ho scoperto Application Verifier durante la ricerca di una domanda precedente su un problema di corruzione dell'heap .) Ho usato anche BoundsChecker e Insure ++ (menzionato in altre risposte) in passato, anche se sono rimasto sorpreso dalla funzionalità di Application Verifier.

Electric Fence (alias "efence"), dmalloc , valgrind e così via meritano tutti una menzione, ma la maggior parte di questi sono molto più facili da usare con * nix di Windows. Valgrind è incredibilmente flessibile: ho eseguito il debug di software server di grandi dimensioni con molti problemi di heap che lo utilizzano.

Quando tutto il resto fallisce, puoi fornire al tuo operatore globale sovraccarichi new / delete e malloc / calloc / realloc - il modo per farlo varierà un po 'a seconda del compilatore e della piattaforma - e questo sarà un po' un investimento - ma potrebbe ripagare nel lungo periodo. L'elenco delle caratteristiche desiderabili dovrebbe apparire familiare da dmalloc ed electricfence, e il libro sorprendentemente eccellente Writing Solid Code :

  • valori di sentinella : lasciare un po 'più di spazio prima e dopo ogni allocazione, rispettando il requisito di allineamento massimo; riempire con numeri magici (aiuta a catturare overflow e underflow del buffer e il puntatore "selvaggio" occasionale)
  • alloc fill : riempire nuove allocazioni con un valore magico diverso da 0 - Visual C ++ lo farà già per te nelle build di debug (aiuta a catturare l'uso di variabili non inizializzate)
  • riempimento libero : riempie la memoria liberata con un valore magico diverso da 0, progettato per attivare un segfault se nella maggior parte dei casi è privo di riferimenti (aiuta a catturare i puntatori penzolanti)
  • ritardato libero : non rimandare la memoria liberata per un po 'nell'heap, mantenerlo libero pieno ma non disponibile (aiuta a catturare più puntatori penzolanti, cattura i doppi liberaggi vicini)
  • monitoraggio : essere in grado di registrare dove è stata effettuata un'allocazione a volte può essere utile

Si noti che nel nostro sistema homebrew locale (per un target incorporato) manteniamo il tracciamento separato dalla maggior parte delle altre cose, perché il sovraccarico di runtime è molto più alto.


Se sei interessato a più motivi per sovraccaricare queste funzioni / operatori di allocazione, dai un'occhiata alla mia risposta a "Qualsiasi motivo per sovraccaricare l'operatore globale nuovo ed eliminare?" ; autopromozione spudorata a parte, elenca altre tecniche che sono utili nel tenere traccia degli errori di corruzione dell'heap, così come altri strumenti applicabili.


Poiché continuo a trovare la mia risposta qui durante la ricerca dei valori alloc / free / fence utilizzati da MS, ecco un'altra risposta che copre i valori di riempimento di dbgheap di Microsoft .


3
Una piccola cosa degna di nota di Application Verifier: è necessario registrare i simboli di Application Verifier prima dei simboli di Microsoft Symbol Server nel percorso di ricerca dei simboli, se lo si utilizza ... Mi ci è voluto un po 'di ricerca per capire perché! Avrf non era trovare i simboli di cui aveva bisogno.
magro

Application Verifier è stato di grande aiuto e, combinato con alcune ipotesi, sono stato in grado di risolvere il problema! Grazie mille, e anche per tutti gli altri, per aver sollevato punti utili.

Application Verifier deve essere utilizzato con WinDbg o dovrebbe funzionare con il debugger di Visual Studio? Ho provato a usarlo, ma non genera alcun errore o apparentemente non fa nulla quando eseguo il debug in VS2012.
Nathan Reed,

@NathanReed: credo che funzioni anche con VS - vedi msdn.microsoft.com/en-us/library/ms220944(v=vs.90).aspx - anche se nota che questo link è per VS2008, non lo sono sicuro delle versioni successive. La memoria è un po 'sfocata, ma credo che quando ho avuto il problema nel collegamento "domanda precedente" ho appena eseguito Application Verifier e salvato le opzioni, eseguito il programma e quando si è bloccato ho scelto VS con cui eseguire il debug. AV ha appena fatto crash / asserire prima. Il comando! Avrf è specifico per WinDbg, per quanto ne so. Speriamo che altri possano fornire maggiori informazioni!
magro

Grazie. In realtà ho risolto il mio problema originale e alla fine non si è trattato di corruzione dell'heap, ma di qualcos'altro, quindi questo probabilmente spiega perché App Verifier non ha trovato nulla. :)
Nathan Reed,

35

Puoi rilevare molti problemi di corruzione dell'heap abilitando Page Heap per la tua applicazione. Per fare ciò è necessario utilizzare gflags.exe fornito come parte degli strumenti di debug per Windows

Esegui Gflags.exe e nelle opzioni del file immagine per il tuo eseguibile, seleziona l'opzione "Abilita heap di pagina".

Ora riavvia exe e connettiti a un debugger. Con Page Heap abilitato, l'applicazione si interromperà nel debugger ogni volta che si verifica un danneggiamento dell'heap.


sì, ma una volta ricevuta questa funzione nel dump del callstack (dopo l'arresto anomalo della corruzione della memoria): wow64! Wow64NotifyDebugger, cosa posso fare? Ancora non so che cosa non va nella mia domanda
Guillaume07,

Ho appena provato gflags per eseguire il debug della corruzione dell'heap qui, piccolo strumento MOLTO utile, altamente raccomandato. Ho scoperto che stavo accedendo alla memoria liberata che, quando dotata di gflags, si interrompe immediatamente nel debugger ... Pratico!
Dave F,

Ottimo strumento! Ho appena trovato un bug, che cercavo da giorni, perché Windows non dice l'indirizzo della corruzione, solo che "qualcosa" è sbagliato, il che non è davvero utile.
Devolus

Un po 'in ritardo alla festa, ma ho notato un aumento significativo dell'utilizzo della memoria nell'applicazione di cui sto eseguendo il debug quando ho attivato Page Heap. Sfortunatamente fino al punto in cui l'applicazione (32 bit) esaurisce la memoria prima che venga attivato il rilevamento della corruzione dell'heap. Qualche idea su come affrontare questo problema?
martedì

13

Per rallentare davvero le cose ed eseguire molti controlli di runtime, prova ad aggiungere quanto segue all'inizio main()o equivalente in Microsoft Visual Studio C ++

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF );


8

Che tipo di cose possono causare questi errori?

Fare cose cattive con la memoria, ad esempio scrivere dopo la fine di un buffer o scrivere su un buffer dopo che è stato liberato di nuovo nell'heap.

Come eseguo il debug?

Usa uno strumento che aggiunge il controllo automatico dei limiti al tuo eseguibile: vale a dire valgrind su Unix o uno strumento come BoundsChecker (Wikipedia suggerisce anche Purify e Insure ++) su Windows.

Fai attenzione che questi rallenteranno la tua applicazione, quindi potrebbero essere inutilizzabili se la tua è un'applicazione soft-real-time.

Un altro possibile strumento / aiuto per il debug potrebbe essere HeapAgent di MicroQuill.


1
Ricostruire l'applicazione con il runtime di debug (/ MDd o / MTd flag) sarebbe il mio primo passo. Questi eseguono controlli aggiuntivi su malloc e free, e spesso sono abbastanza efficaci nel restringere la posizione dei bug.
Impiegato russo il

HeapAgent di MicroQuill: non c'è molto scritto o sentito a riguardo, ma per corruzione dell'heap, dovrebbe essere nella tua lista.
Samrat Patil,

1
BoundsChecker funziona bene come test del fumo, ma non pensare nemmeno di eseguire un programma sotto di esso mentre prova a eseguirlo anche in produzione. Il rallentamento può variare da 60x a 300x, a seconda delle opzioni che stai usando e se stai usando o meno la funzione di strumentazione del compilatore. Disclaimer: sono uno dei ragazzi che mantiene il prodotto per Micro Focus.
Rick Papo,

8

Un rapido suggerimento che ho ottenuto dal rilevamento dell'accesso alla memoria liberata è questo:

Se si desidera individuare rapidamente l'errore, senza controllare ogni istruzione che accede al blocco di memoria, è possibile impostare il puntatore di memoria su un valore non valido dopo aver liberato il blocco:

#ifdef _DEBUG // detect the access to freed memory
#undef free
#define free(p) _free_dbg(p, _NORMAL_BLOCK); *(int*)&p = 0x666;
#endif

5

Lo strumento migliore che ho trovato utile e lavorato ogni volta è la revisione del codice (con buoni revisori del codice).

Oltre alla revisione del codice, proverei prima Heap di pagina . Page Heap richiede alcuni secondi per essere configurato e, per fortuna, potrebbe individuare il tuo problema.

Se non hai fortuna con Page Heap, scarica gli strumenti di debug per Windows da Microsoft e impara ad usare WinDbg. Spiacenti, non posso darti un aiuto più specifico, ma il debug della corruzione dell'heap multi-thread è più un'arte che una scienza. Google per "WinDbg heap corruzione" e dovresti trovare molti articoli sull'argomento.


4

Puoi anche verificare se stai collegando la libreria di runtime C dinamica o statica. Se i file DLL si collegano alla libreria di runtime C statica, i file DLL hanno heap separati.

Quindi, se dovessi creare un oggetto in una DLL e provare a liberarlo in un'altra DLL, otterrai lo stesso messaggio che vedi sopra. Questo problema è indicato in un'altra domanda di overflow dello stack, liberando memoria allocata in una DLL diversa .


3

Che tipo di funzioni di allocazione stai usando? Recentemente ho riscontrato un errore simile utilizzando le funzioni di allocazione dello stile Heap *.

Si è scoperto che stavo creando erroneamente l'heap con il HEAP_NO_SERIALIZE opzione. Questo essenzialmente fa funzionare le funzioni Heap senza sicurezza del thread. È un miglioramento delle prestazioni se usato correttamente ma non dovrebbe mai essere usato se si utilizza HeapAlloc in un programma multi-thread [1]. Lo dico solo perché il tuo post menziona che hai un'app multi-thread. Se stai usando HEAP_NO_SERIALIZE ovunque, eliminalo e probabilmente risolverà il tuo problema.

[1] Vi sono alcune situazioni in cui ciò è legale, ma richiede la serializzazione delle chiamate a Heap * e in genere non è il caso dei programmi multi-thread.


Sì: guarda le opzioni del compilatore / build dell'applicazione e assicurati che sia stato creato per il collegamento con una versione "multi-thread" della libreria di runtime C.
ChrisW,

@ChrisW per le API in stile HeapAlloc è diverso. In realtà è un parametro che può essere modificato al momento della creazione dell'heap, non al momento del collegamento.
JaredPar,

Oh. Non mi è venuto in mente che l'OP potesse parlare di quell'heap e non dell'heap nel CRT.
ChrisW,

@ChrisW, la domanda è piuttosto vaga, ma ho appena risolto il problema che ho dettagliato ~ 1 settimana fa, quindi è fresco nella mia mente.
JaredPar,

3

Se questi errori si verificano casualmente, è molto probabile che si siano verificate gare di dati. Per favore, controlla: modifichi i puntatori di memoria condivisa da thread diversi? Intel Thread Checker può aiutare a rilevare tali problemi in un programma multithread.


1

Oltre a cercare strumenti, considera la ricerca di un probabile colpevole. C'è qualche componente che stai utilizzando, forse non scritto da te, che potrebbe non essere stato progettato e testato per funzionare in un ambiente multithread? O semplicemente uno che non conosci è stato eseguito in tale ambiente.

L'ultima volta che mi è successo, era un pacchetto nativo che era stato usato con successo da lavori batch per anni. Ma era la prima volta in questa azienda che era stato utilizzato da un servizio Web .NET (che è multithread). Ecco fatto: avevano mentito sul fatto che il codice fosse sicuro per i thread.


1

Puoi utilizzare le macro Heap-Check di VC CRT per _CrtSetDbgFlag : _CRTDBG_CHECK_ALWAYS_DF o _CRTDBG_CHECK_EVERY_16_DF .. _CRTDBG_CHECK_EVERY_1024_DF .


0

Vorrei aggiungere la mia esperienza. Negli ultimi giorni, ho risolto un'istanza di questo errore nella mia applicazione. Nel mio caso particolare, gli errori nel codice erano:

  • Rimozione di elementi da una raccolta STL durante l'iterazione su di esso (credo che ci siano flag di debug in Visual Studio per rilevare queste cose; l'ho rilevato durante la revisione del codice)
  • Questo è più complesso, lo dividerò in passaggi:
    • Da un thread C ++ nativo, richiamare nel codice gestito
    • Nella terra gestita, chiamare Control.Invokee disporre un oggetto gestito che avvolge l'oggetto nativo a cui appartiene il callback.
    • Poiché l'oggetto è ancora attivo all'interno del thread nativo (rimarrà bloccato nella chiamata di richiamata fino al Control.Invoketermine). Dovrei chiarire che uso boost::thread, quindi uso una funzione membro come funzione thread.
    • Soluzione : utilizzare Control.BeginInvoke(la mia GUI è realizzata con Winforms) invece in modo che il thread nativo possa terminare prima che l'oggetto venga distrutto (lo scopo del callback sta notificando con precisione che il thread è terminato e l'oggetto può essere distrutto).

0

Ho avuto un problema simile - e si è presentato in modo abbastanza casuale. Forse qualcosa era corrotto nei file di build, ma ho finito per risolverlo pulendo prima il progetto e poi la ricostruzione.

Quindi oltre alle altre risposte fornite:

Che tipo di cose possono causare questi errori? Qualcosa di corrotto nel file di build.

Come eseguo il debug? Pulizia del progetto e ricostruzione. Se è stato risolto, questo era probabilmente il problema.

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.