2009-06-18 18 views
143

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 potrebbe essere dovuta a una corruzione nel mucchio. Questi errori non arresteranno sempre immediatamente l'applicazione, anche se è probabile che si verifichi un brusco incidente.Come eseguire il debug degli errori di danneggiamento dell'heap?

Il grosso problema con questi errori è che vengono visualizzati solo dopo che il danneggiamento è effettivamente avvenuto, il che rende molto difficile il monitoraggio e il debug, specialmente su un'applicazione multithread.

  • Che tipo di cose possono causare questi errori?

  • Come si esegue il debug?

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

risposta

115

Application Verifier combinato con Debugging Tools for Windows è una configurazione straordinaria. È possibile ottenere entrambi come parte dello Windows Driver Kit or the lighter Windows SDK. (Ho trovato informazioni su Application Verifier durante la ricerca su un earlier question about a heap corruption issue.) Ho usato BoundsChecker e Insure ++ (menzionato in altre risposte) anche in passato, anche se sono rimasto sorpreso di quanta funzionalità ci 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 in * 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, è possibile fornire il proprio operatore globale nuovo/eliminazione e sovraccarichi di 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 potrebbe ripagare a lungo termine.La lista caratteristica desiderabile dovrebbe essere familiare da dmalloc e electricfence, e sorprendentemente eccellente libro Writing Solid Code:

  • sentinella valori: permettere un po 'più di spazio prima e dopo ogni alloc, rispettando il requisito massimo allineamento; riempire con numeri magici (aiuta a catturare buffer overflow e underflow e l'occasionale puntatore "wild")
  • alloc fill: riempire nuove allocazioni con un valore magico non 0 - Visual C++ lo farà già per te in Debug costruisce (aiuta l'uso cattura di Vars non inizializzati)
  • riempimento libero: compilare memoria liberata con un non-0 valore magico, progettato per innescare un segfault se è dereferenziato nella maggior parte dei casi (aiuta cattura puntatori penzoloni)
  • senza ritardo: non restituire la memoria liberata all'heap per un po ', tenerla libera, ma non disponibile (aiuta a catturare più puntatori che penzolano, cattura prossime doppio FreeS)
  • tracciamento: essere in grado di registrare in cui una ripartizione è stata fatta a volte può essere utile

Si noti che nel nostro sistema homebrew locale (per un target embedded) manteniamo il monitoraggio separato dal la maggior parte delle altre cose, perché l'overhead di runtime è molto più alto.


Se siete interessati a più motivi per sovraccaricare questi allocazione funzioni/operatori, dare un'occhiata a my answer to "Any reason to overload global operator new and delete?"; 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.

+3

Una piccola cosa da notare su Application Verifier: è necessario registrare i simboli di Application Verifier davanti ai simboli del server simbolo Microsoft nel percorso di ricerca dei simboli, se lo si usa ... Mi sono preso un po 'di ricerche per capire perché! Avrf wasn' trovare i simboli necessari. – leander

+0

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

+0

Application Verifier deve essere utilizzato con WinDbg o dovrebbe funzionare con il debugger di Visual Studio? Ho cercato di usarlo, ma non genera errori o apparentemente fare nulla quando eseguo il debug in VS2012. –

8

Che tipo di cose possono causare questi errori?

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

Come si esegue il debug?

Utilizzare uno strumento che aggiunge automatizzato limiti-controllo per l'eseguibile: cioè valgrind su Unix, o uno strumento come BoundsChecker (Wikipedia suggerisce anche purificare e Assicurare ++) su Windows.

Attenzione che questi rallenteranno la vostra applicazione, quindi potrebbero essere inutilizzabili se la vostra è un'applicazione soft-real-time.

Un altro strumento/strumento di debug può essere l'HeapAgent di MicroQuill.

+1

Ricostruire l'applicazione con debugging runtime (/ MDd o/MTd flag) sarebbe il mio primo passo. Questi eseguono controlli addizionali su malloc e free, e spesso si bloccano efficacemente nel restringere la posizione dei bug. –

+0

HeapAgent di MicroQuill: non c'è molto scritto o sentito a riguardo, ma per la corruzione dell'heap, dovrebbe essere incluso nell'elenco. –

+1

BoundsChecker funziona bene come un test del fumo, ma non pensate nemmeno di eseguire un programma al suo interno durante il tentativo di eseguire quel programma in produzione. Il rallentamento può variare da 60x a 300x, a seconda delle opzioni che si stanno utilizzando e se si sta utilizzando o meno la funzione di strumentazione del compilatore. Disclaimer: Sono uno dei ragazzi che gestisce il prodotto per Micro Focus. –

3

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

Si è scoperto 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.

+0

Sì: guarda le opzioni di compilazione/compilazione dell'applicazione e assicurati che sia stato creato per collegarsi a una versione "multi-thread" della libreria di runtime C. – ChrisW

+0

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

+0

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

1

Oltre a cercare strumenti, è consigliabile cercare 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 uno che non è sa che è stato eseguito in tale ambiente.

L'ultima volta che mi è successo, era un pacchetto nativo che era stato utilizzato con successo da 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.

5

Il miglior strumento che ho trovato utile e funzionante ogni volta è la revisione del codice (con revisori di codice validi).

Oltre alla revisione del codice, proverei prima lo Page Heap. Page Heap richiede pochi secondi per l'impostazione e con un po 'di fortuna potrebbe individuare il tuo problema.

Se non si è fortunati con Page Heap, scaricare Debugging Tools for Windows da Microsoft e imparare a utilizzare 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.

31

È possibile rilevare numerosi problemi di danneggiamento dell'heap abilitando Page Heap per l'applicazione. Per fare ciò devi usare gflags.exe come parte di Debugging Tools For Windows

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

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.

+0

+ 1, è un modo facile e migliore per limitare il problema! –

+0

sì ma una volta che ottengo questa chiamata di funzione nel mio dump del callstack (dopo l'arresto anomalo della memoria): wow64! Wow64NotifyDebugger, cosa posso fare? Non so ancora cosa sta andando storto nella mia applicazione – Guillaume07

+0

Ho appena provato gflags per eseguire il debug del danneggiamento dell'heap qui, piccolo strumento MOLTO utile, altamente raccomandato. Ho scoperto che stavo accedendo alla memoria liberata, che, una volta strumentato con gflag, si inserirà immediatamente nel debugger ... Handy! –

8

Una punta rapida, che ho avuto da Detecting access to freed memory è questo:

Se si desidera individuare l'errore rapidamente, senza controllare ogni affermazione che accede al blocco di memoria , è possibile impostare il puntatore di memoria ad 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 
3

Se questi gli 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.

4

Si consiglia inoltre di verificare se si sta eseguendo il collegamento con 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 si dovesse creare un oggetto 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 Stack Overflow  , Freeing memory allocated in a different DLL.

0

È possibile utilizzare VC CRT Mucchio-Check macro per _CrtSetDbgFlag: _CRTDBG_CHECK_ALWAYS_DF o _CRTDBG_CHECK_EVERY_16_DF .. _CRTDBG_CHECK_EVERY_1024_DF.

10

Per davvero le cose rallentare e eseguire un sacco di controllo runtime, prova ad aggiungere quanto segue nella parte superiore del vostro main() o equivalente in Microsoft Visual Studio C++

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_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 sono stati:

  • Rimozione di elementi da una collezione STL mentre l'iterazione su di esso (io ci credo sono flag di debug in Visual Studio per la cattura di queste cose, ho preso durante la revisione del codice)
  • Questo è più complesso, io divido in passi:
    • Da un thread nativo C++, richiamo in codice gestito
    • nel territorio gestito, chiamo Control.Invoke e smaltire un oggetto gestito che avvolge il nativo oggetto a cui appartiene la richiamata.
    • Poiché l'oggetto è ancora attivo all'interno del thread nativo (rimarrà bloccato nella chiamata di richiamata fino al termine Control.Invoke). Dovrei chiarire che uso boost::thread, quindi uso una funzione membro come funzione thread.
    • Soluzione: Usa Control.BeginInvoke (la mia GUI è fatta con WinForms) invece in modo che il thread nativo può finire prima che l'oggetto viene distrutto (lo scopo della richiamata è proprio informando che il filo si è conclusa e l'oggetto può essere distrutto).
0

ho avuto un problema simile - ed è spuntato 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 date:

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

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

Problemi correlati