Come eseguire il debug degli errori di danneggiamento dell’heap?

Sto eseguendo il debug di un’applicazione C ++ multi-thread (nativa) in Visual Studio 2008. In occasioni apparentemente casuali, ricevo un errore “Windows ha triggersto un punto di interruzione …” con una nota che potrebbe essere dovuta a un danneggiamento nel mucchio. Questi errori non arresteranno sempre immediatamente l’applicazione, anche se è probabile che si verifichi un brusco incidente.

Il grosso problema con questi errori è che compaiono solo dopo che il danneggiamento è effettivamente avvenuto, il che rende molto difficile rintracciare e eseguire il debug, specialmente su un’applicazione multi-thread.

  • Che tipo di cose possono causare questi errori?

  • Come faccio a eseguirne il debug?

Suggerimenti, strumenti, metodi, chiarimenti … sono i benvenuti.

Application Verifier in combinazione con Debugging Tools per Windows è un’installazione straordinaria. È ansible ottenere entrambi come parte del Windows Driver Kit o del più leggero SDK di Windows . (Ho trovato informazioni su Application Verifier durante la ricerca di una precedente domanda relativa a un problema di corruzione dell’heap .) Ho utilizzato BoundsChecker e Insure ++ (menzionato in altre risposte) anche in passato, sebbene mi sia stato sorpreso quanta funzionalità fosse in Application Verifier.

Electric Fence (aka “efence”), dmalloc , valgrind e così via sono tutti degni di nota, ma la maggior parte di questi è molto più facile da eseguire sotto * nix di Windows. Valgrind è ridicolmente flessibile: ho eseguito il debug di un grande software server con molti problemi di heap che lo utilizzano.

Quando tutto il resto fallisce, puoi fornire al tuo operatore globale nuovi / delete e sovraccarichi malloc / calloc / realloc – il modo in cui farlo varia leggermente a seconda del compilatore e della piattaforma – e questo sarà un po ‘un investimento – ma può pagare a lungo termine. L’elenco delle caratteristiche desiderabile dovrebbe sembrare familiare a dmalloc e electricfence e al libro sorprendentemente eccellente Writing Solid Code :

  • valori sentinella : consente un po ‘più di spazio prima e dopo ogni allocazione, rispettando il requisito di allineamento massimo; riempire con numeri magici (aiuta a catturare buffer overflow e underflow e l’occasionale puntatore “wild”)
  • alloc fill : riempi le nuove allocazioni con un valore non 0 magico – Visual C ++ lo farà già per te nei build di Debug (aiuta a prendere l’uso di vars non inizializzati)
  • riempimento libero : riempire la memoria liberata con un valore magico non 0, progettato per innescare un segfault se è stato deferenziato nella maggior parte dei casi (aiuta a prendere i puntatori penzolanti)
  • ritardato libero : non restituire la memoria liberata all’heap per un po ‘, tenerla libera piena ma non disponibile (aiuta a catturare più puntatori penzolanti, cattura le doppie libere vicine)
  • tracking : essere in grado di registrare dove è stata fatta un’assegnazione può a volte essere utile

Si noti che nel nostro sistema homebrew locale (per un target incorporato) manteniamo il tracciamento separato dalla maggior parte degli altri elementi, poiché l’overhead 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 “Qualche motivo per sovraccaricare l’operatore globale e cancellarlo?” ; a parte l’auto-promozione spudorata, elenca le altre tecniche che sono utili nel tracciare gli errori di danneggiamento dell’heap, così come altri strumenti applicabili.

È ansible rilevare numerosi problemi di danneggiamento dell’heap abilitando Page Heap per l’applicazione. Per fare questo è necessario utilizzare gflags.exe che viene 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 accumulo di pagine”.

Ora riavvia il tuo exe e collegalo a un debugger. Con Page Heap abilitato, l’applicazione si interromperà in debugger ogni volta che si verifica un danneggiamento dell’heap.

Un articolo molto pertinente è il debug di Heap corruzione con Application Verifier e Debugdiag .

Per rallentare davvero le cose ed eseguire molti controlli di runtime, prova ad aggiungere quanto segue nella parte superiore del tuo main() o equivalente in Microsoft Visual Studio C ++

 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF ); 

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 nuovamente nell’heap.

Come faccio a eseguirne il debug?

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

Fate attenzione che questi rallenteranno la vostra applicazione, quindi potrebbero essere inutilizzabili se la vostra è un’applicazione soft-real-time.

Un altro ansible strumento / strumento di debug potrebbe essere l’HeapAgent di MicroQuill.

Un suggerimento rapido, che ho ricevuto da Rilevare l’accesso alla memoria liberata, è questo:

Se si desidera individuare l’errore rapidamente, senza verificare ogni istruzione che accede al blocco di memoria, è ansible 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 

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

A parte la revisione del codice, proverei innanzitutto Page Heap . Page Heap richiede pochi secondi per l’impostazione e con un po ‘di fortuna potrebbe individuare il tuo problema.

Se non hai fortuna con Page Heap, scarica Debugging Tools per Windows da Microsoft e impara a usare WinDbg. Scusa non ho potuto fornire una guida più specifica, ma il debug della corruzione dell’heap multi-thread è più un’arte che una scienza. Google per “WinDbg heap corruption” e dovresti trovare molti articoli sull’argomento.

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

Quindi, se si dovesse creare un object in una DLL e provare a liberarlo in un’altra DLL, si otterrebbe lo stesso messaggio che si sta visualizzando sopra. Questo problema viene fatto riferimento in un’altra domanda di overflow dello stack, liberando la memoria allocata in una DLL diversa .

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

È risultato che stavo erroneamente creando l’heap con l’opzione HEAP_NO_SERIALIZE . Ciò essenzialmente fa funzionare le funzioni di heap senza sicurezza di thread. È un miglioramento delle prestazioni se usato correttamente ma non dovrebbe mai essere usato se si sta usando HeapAlloc in un programma multi-thread [1]. Lo dico solo perché il tuo post parla di un’app multi-thread. Se utilizzi HEAP_NO_SERIALIZE ovunque, eliminalo e probabilmente risolverà il tuo problema.

[1] Ci sono alcune situazioni in cui questo è legale, ma richiede la serializzazione delle chiamate a Heap * e in genere non è il caso per i programmi multi-thread.

Se questi errori si verificano casualmente, c’è un’alta probabilità che tu abbia incontrato gare di dati. Per favore, controlla: modifichi i puntatori della memoria condivisa da thread diversi? Intel Thread Checker può aiutare a rilevare tali problemi nel programma con multithreading.

Oltre a cercare strumenti, considera la ricerca di un probabile colpevole. C’è qualche componente che stai usando, magari non scritto da te, che potrebbe non essere stato progettato e testato per essere eseguito in un ambiente multithread? O semplicemente quello che non sai è stato eseguito in un tale ambiente.

L’ultima volta che mi è successo, era un pacchetto nativo che era stato utilizzato con successo dai lavori batch per anni. Ma è stata la prima volta in questa azienda che è stata utilizzata da un servizio Web .NET (che è multithreading). Era così – avevano mentito sul fatto che il codice fosse sicuro per il thread.

È ansible utilizzare macro VC CRT Heap-Check per _CrtSetDbgFlag : _CRTDBG_CHECK_ALWAYS_DF o _CRTDBG_CHECK_EVERY_16_DF .. _CRTDBG_CHECK_EVERY_1024_DF .

Mi piacerebbe 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 mentre si esegue l’iterazione su di esso (credo ci siano dei flag di debug in Visual Studio per catturare queste cose; l’ho preso durante la revisione del codice)
  • Questo è più complesso, lo dividerò in passaggi:
    • Da un thread C ++ nativo, richiamare nel codice gestito
    • In terra gestita, chiama Control.Invoke e disponi un object gestito che racchiude l’object nativo a cui appartiene la richiamata.
    • Poiché l’object è ancora vivo all’interno del thread nativo (rimarrà bloccato nella chiamata di callback fino a che Control.Invoke termina). Dovrei chiarire che uso boost::thread , quindi uso una funzione membro come funzione thread.
    • Soluzione : utilizzare Control.BeginInvoke (la mia GUI è realizzata con Winforms), in modo che il thread nativo possa terminare prima che l’object venga distrutto (lo scopo del callback è di notificare precisamente che il thread è terminato e che l’object può essere distrutto).

Ho avuto un problema simile – e è apparso in modo abbastanza casuale. Forse qualcosa era corrotto nei file di build, ma alla fine ho risolto il problema pulendo il progetto prima di ricostruirlo.

Quindi oltre alle altre risposte fornite:

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

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