Domanda

Sto lavorando su a multithread Applicazione C++ che sta danneggiando l'heap.I soliti strumenti per individuare questa corruzione sembrano essere inapplicabili.Le vecchie build (18 mesi) del codice sorgente mostrano lo stesso comportamento della versione più recente, quindi esiste da molto tempo e semplicemente non è stato notato;lo svantaggio è che i delta di origine non possono essere utilizzati per identificare quando è stato introdotto il bug: esistono molto delle modifiche al codice nel repository.

La richiesta di un comportamento anomalo è quella di generare throughput in questo sistema: trasferimento tramite socket di dati che vengono inseriti in una rappresentazione interna.Ho una serie di dati di test che periodicamente causeranno un'eccezione all'app (vari luoghi, varie cause, incluso il fallimento dell'allocazione heap, quindi:corruzione dell'heap).

Il comportamento sembra correlato alla potenza della CPU o alla larghezza di banda della memoria;maggiore è la quantità di ciascuno di essi, più facile sarà il crash.La disabilitazione di un core hyper-threading o dual-core riduce il tasso di corruzione (ma non elimina).Ciò suggerisce un problema legato alla tempistica.

Ora ecco il problema:
Quando viene eseguito in un ambiente di debug leggero (ad esempio Visual Studio 98 / AKA MSVC6) la corruzione dell'heap è ragionevolmente facile da riprodurre: passano dieci o quindici minuti prima che qualcosa fallisca in modo orrendo ed eccezioni, come un alloc; quando si esegue in un ambiente di debug sofisticato (Rational Purify, VS2008/MSVC9 o anche Microsoft Application Verifier) ​​il sistema diventa limitato alla velocità della memoria e non si arresta in modo anomalo (limitato alla memoria:La CPU non sta andando oltre 50%, la luce del disco non è accesa, il programma sta andando il più velocemente possibile, consumando scatola 1.3G di 2G di RAM).COSÌ, Posso scegliere tra essere in grado di riprodurre il problema (ma non identificare la causa) o essere in grado di identificare la causa o un problema che non riesco a riprodurre.

La mia attuale migliore ipotesi su dove andare dopo è:

  1. Ottieni una scatola follemente grugnita (per sostituire l'attuale scatola di sviluppo:2 GB di RAM in un E6550 Core2 Duo);ciò renderà possibile riprodurre il crash che causa comportamenti anomali durante l'esecuzione in un potente ambiente di debug;O
  2. Riscrivere gli operatori new E delete usare VirtualAlloc E VirtualProtect per contrassegnare la memoria come di sola lettura non appena terminata.Corri sotto MSVC6 e fare in modo che il sistema operativo catturi il malintenzionato che sta scrivendo nella memoria liberata.Sì, questo è un segno di disperazione:chi diavolo riscrive new E delete?!Mi chiedo se questo lo renderà lento come in Purify et al.

E no:La spedizione con la strumentazione Purify integrata non è un'opzione.

Un collega è appena passato e ha chiesto "Stack Overflow?Stiamo ricevendo stack overflow adesso?!?"

E ora la domanda: Come posso individuare il corruttore dell'heap?


Aggiornamento:bilanciamento new[] E delete[] sembra aver fatto molta strada verso la soluzione del problema.Invece di 15 minuti, l'app ora dura circa due ore prima di bloccarsi.Non ancora arrivato.Ulteriori suggerimenti?La corruzione dell'heap persiste.

Aggiornamento:una build di rilascio sotto Visual Studio 2008 sembra notevolmente migliore;il sospetto attuale poggia sul STL implementazione fornita con VS98.


  1. Riprodurre il problema. Dr Watson produrrà un dump che potrebbe essere utile in ulteriori analisi.

Ne prenderò nota, ma temo che il dottor Watson possa inciampare solo dopo il fatto, non quando il mucchio verrà calpestato.

Un altro tentativo potrebbe essere l'utilizzo WinDebug come strumento di debug abbastanza potente e allo stesso tempo leggero.

Fatto questo al momento, di nuovo:non è di grande aiuto finché qualcosa non va storto.Voglio cogliere il vandalo sul fatto.

Forse questi strumenti ti permetteranno almeno di restringere il problema a determinati componenti.

Non nutro molte speranze, ma i tempi disperati richiedono...

E sei sicuro che tutti i componenti del progetto abbiano le impostazioni corrette della libreria runtime (C/C++ tab, categoria Generazione codice nelle impostazioni del progetto VS 6.0)?

No, non lo sono e domani trascorrerò un paio d'ore esaminando lo spazio di lavoro (58 progetti al suo interno) e controllando che siano tutti compilati e collegati con i flag appropriati.


Aggiornamento:Ci sono voluti 30 secondi.Seleziona tutti i progetti in Settings finestra di dialogo, deseleziona finché non trovi i progetti che non hanno le impostazioni giuste (avevano tutti le impostazioni giuste).

È stato utile?

Soluzione

La mia prima scelta sarebbe uno strumento heap dedicato come pageheap.exe.

Riscrivere new ed delete potrebbe essere utile, ma ciò non rileva le allocazioni impegnate dal codice di livello inferiore.Se questo è quello che vuoi, meglio Deviare il low-level alloc APIs utilizzando Microsoft Detours.

Inoltre controlli di integrità come:verificare che le librerie di runtime corrispondano (versione vs.debug, multi-thread vs.thread singolo, dll vs.static lib), cerca eliminazioni errate (ad esempio, elimina dove avrebbe dovuto essere utilizzato delete []), assicurati di non mescolare e abbinare le tue allocazioni.

Prova anche a disattivare selettivamente i thread e vedi quando/se il problema scompare.

Che aspetto ha lo stack di chiamate, ecc. al momento della prima eccezione?

Altri suggerimenti

Ho gli stessi problemi nel mio lavoro (usiamo anche VC6 A volte).E non esiste una soluzione semplice per questo.Ho solo alcuni suggerimenti:

  • Prova con i crash dump automatici sulla macchina di produzione (vedi Dumper di processo).La mia esperienza dice il Dott.Watson lo è non perfetto per lo scarico.
  • Rimuovi tutto presa(...) dal tuo codiceSpesso nascondono gravi eccezioni di memoria.
  • Controllo Debug avanzato di Windows - Ci sono molti ottimi consigli per problemi come il tuo.Lo consiglio con tutto il cuore.
  • Se usi STL Tentativo STLPort e build controllate.L'iteratore non valido è un inferno.

Buona fortuna.Ci vogliono mesi per risolvere problemi come il tuo.Sii pronto per questo...

Esegui l'applicazione originale con ADplus -crash -pn appnename.exeQuando viene visualizzato il problema della memoria, otterrai un bel dump.

Puoi analizzare il dump per capire quale posizione di memoria è stata danneggiata.Se sei fortunato, la memoria di sovrascrittura è una stringa univoca da cui puoi capire da dove proviene.Se non sei fortunato, dovrai approfondire win32 accumulare e capire quali erano le caratteristiche della memoria originale.(heap -x potrebbe aiutare)

Dopo aver scoperto cosa non funzionava, puoi restringere l'utilizzo dell'appverifier con impostazioni heap speciali.cioè.puoi specificare cosa DLL monitorare o quale dimensione di allocazione monitorare.

Si spera che questo acceleri il monitoraggio abbastanza da catturare il colpevole.

Nella mia esperienza, non ho mai avuto bisogno della modalità di verifica dell'heap completo, ma ho passato molto tempo ad analizzare i dump degli arresti anomali e a sfogliare le fonti.

PS:Puoi usare DebugDiag per analizzare le discariche.Può evidenziare il DLL possedere l'heap danneggiato e fornirti altri dettagli utili.

Abbiamo avuto molta fortuna scrivendo il nostro malloc e le funzioni libere.In produzione, chiamano semplicemente malloc standard e sono gratuiti, ma in debug possono fare quello che vuoi.Abbiamo anche una semplice classe base che non fa altro che sovrascrivere gli operatori new ed delete per utilizzare queste funzioni, quindi qualsiasi classe che scrivi può semplicemente ereditare da quella classe.Se hai molto codice, potrebbe essere un grosso lavoro sostituire le chiamate a malloc e free con le nuove malloc e free (non dimenticare realloc!), ma alla lunga è molto utile.

Nel libro di Steve Maguire Scrittura di codice solido (altamente raccomandato), ci sono esempi di cose di debug che puoi fare in queste routine, come:

  • Tieni traccia delle allocazioni per trovare perdite
  • Assegna più memoria del necessario e metti dei marcatori all'inizio e alla fine della memoria: durante la routine libera, puoi assicurarti che questi marcatori siano ancora lì
  • memimpostare la memoria con un indicatore sull'allocazione (per trovare l'utilizzo della memoria non inizializzata) e su libera (per trovare l'utilizzo della memoria liberata)

Un'altra buona idea è quella di Mai usa cose come strcpy, strcat, O sprintf - usa sempre strncpy, strncat, E snprintf.Abbiamo scritto anche le nostre versioni di questi, per assicurarci di non cancellare la fine di un buffer, e anche queste hanno riscontrato molti problemi.

Dovresti affrontare questo problema sia con l'analisi runtime che statica.

Per l'analisi statica considerare la compilazione con PREfast (cl.exe /analyze).Rileva non corrispondenti delete E delete[], sovraccarico del buffer e una serie di altri problemi.Preparati, tuttavia, a superare molti kilobyte di avvisi L6, soprattutto se il tuo progetto ne è ancora dotato L4 non riparato.

PREfast è disponibile con Visual Studio Team System e, apparentemente, come parte di Windows SDK.

L'apparente casualità della corruzione della memoria ricorda molto un problema di sincronizzazione dei thread: viene riprodotto un bug a seconda della velocità della macchina.Se gli oggetti (blocchi di memoria) sono condivisi tra thread e le primitive di sincronizzazione (sezione critica, mutex, semaforo, altro) non sono su base per classe (per oggetto, per classe), allora è possibile arrivare a una situazione dove la classe (blocco di memoria) viene eliminata/liberata durante l'uso o utilizzata dopo essere stata eliminata/liberata.

Come test, potresti aggiungere primitive di sincronizzazione a ciascuna classe e metodo.Ciò renderà il tuo codice più lento perché molti oggetti dovranno attendere l'uno dall'altro, ma se questo elimina la corruzione dell'heap, il tuo problema di corruzione dell'heap diventerà un problema di ottimizzazione del codice.

È in condizioni di memoria insufficiente?Se è così, potrebbe essere che il nuovo stia tornando NULL invece di lanciare std::bad_alloc.Più vecchio VC++ i compilatori non lo hanno implementato correttamente.C'è un articolo su Errori di allocazione della memoria legacy schiantarsi STL app create con VC6.

Hai provato vecchie build, ma c'è un motivo per cui non puoi andare più indietro nella cronologia del repository e vedere esattamente quando è stato introdotto il bug?

Altrimenti, suggerirei di aggiungere una semplice registrazione di qualche tipo per aiutare a rintracciare il problema, anche se non so esattamente cosa potresti voler registrare.

Se riesci a scoprire cosa PUÒ causare esattamente questo problema, tramite Google e la documentazione delle eccezioni che stai ricevendo, forse ciò fornirà ulteriori informazioni su cosa cercare nel codice.

La mia prima azione sarebbe la seguente:

  1. Costruisci i binari nella versione "Release" ma creando un file di informazioni di debug (troverai questa possibilità nelle impostazioni del progetto).
  2. Utilizza Dr Watson come debugger predefinito (DrWtsn32 -I) su una macchina su cui desideri riprodurre il problema.
  3. Riprodurre il problema.Il dottor Watson produrrà un documento che potrebbe essere utile per ulteriori analisi.

Un altro tentativo potrebbe essere quello di utilizzare WinDebug come strumento di debug che è abbastanza potente e allo stesso tempo leggero.

Forse questi strumenti ti permetteranno almeno di restringere il problema a determinati componenti.

E sei sicuro che tutti i componenti del progetto abbiano le impostazioni corrette della libreria runtime (scheda C/C++, categoria Generazione codice nelle impostazioni del progetto VS 6.0)?

Quindi, dalle informazioni limitate di cui disponi, questa può essere una combinazione di una o più cose:

  • Utilizzo errato dell'heap, ovvero doppie libere, lettura dopo libera, scrittura dopo libera, impostazione del flag HEAP_NO_SERIALIZE con allocs e liberazioni da più thread sullo stesso heap
  • Fuori dalla memoria
  • Codice errato (ad esempio, buffer overflow, buffer underflow, ecc.)
  • Problemi di "tempistica".

Se sono i primi due ma non l'ultimo, dovresti averlo già rilevato con pageheap.exe.

Il che molto probabilmente significa che è dovuto al modo in cui il codice accede alla memoria condivisa.Sfortunatamente, rintracciarlo sarà piuttosto doloroso.L'accesso non sincronizzato alla memoria condivisa si manifesta spesso come strani problemi di "tempistica".Cose come non utilizzare la semantica di acquisizione/rilascio per sincronizzare l'accesso alla memoria condivisa con un flag, non utilizzare i blocchi in modo appropriato, ecc.

Per lo meno, aiuterebbe essere in grado di monitorare in qualche modo le allocazioni, come suggerito in precedenza.Almeno allora puoi vedere cosa è realmente successo fino alla corruzione dell'heap e tentare di diagnosticarlo.

Inoltre, se puoi reindirizzare facilmente le allocazioni su più heap, potresti provarlo per vedere se questo risolve il problema o si traduce in un comportamento bacato più riproducibile.

Durante il test con VS2008, hai eseguito HeapVerifier con Conserva memoria impostato su Sì?Ciò potrebbe ridurre l'impatto sulle prestazioni dell'allocatore di heap.(Inoltre, devi eseguirlo Debug->Avvia con Application Verifier, ma potresti già saperlo.)

Puoi anche provare a eseguire il debug con Windbg e vari usi del comando !heap.

MSN

Se scegli di riscrivere nuovo/eliminare, l'ho fatto e ho un semplice codice sorgente su:

http://gandolf.homelinux.org/~smhanov/blog/?id=10

Questo rileva le perdite di memoria e inserisce anche i dati di protezione prima e dopo il blocco di memoria per catturare il danneggiamento dell'heap.Puoi semplicemente integrarlo inserendo #include "debug.h" all'inizio di ogni file CPP e definendo DEBUG e DEBUG_MEM.

Il suggerimento di Graeme di malloc/free personalizzato è una buona idea.Vedi se riesci a caratterizzare qualche schema sulla corruzione per darti un mezzo per sfruttare.

Ad esempio, se si trova sempre in un blocco della stessa dimensione (diciamo 64 byte), modifica la coppia malloc/free per allocare sempre blocchi da 64 byte nella propria pagina.Quando liberi un blocco da 64 byte, imposta i bit di protezione della memoria su quella pagina per impedire letture e wite (utilizzando VirtualQuery).Quindi chiunque tenti di accedere a questa memoria genererà un'eccezione anziché corrompere l'heap.

Ciò presuppone che il numero di blocchi da 64 byte in sospeso sia solo moderato o che tu abbia molta memoria da masterizzare nella scatola!

Il poco tempo che ho avuto per risolvere un problema simile.Se il problema persiste ti consiglio di fare così:Monitora tutte le chiamate a new/delete e malloc/calloc/realloc/free.Realizzo una singola DLL esportando una funzione per registrare tutte le chiamate.Questa funzione riceve parametri per identificare la sorgente del codice, il puntatore all'area assegnata e il tipo di chiamata salvando queste informazioni in una tabella.Tutte le coppie allocate/liberate vengono eliminate.Alla fine o dopo aver effettuato la chiamata ad un'altra funzione per creare un report per i dati rimasti.Con questo puoi identificare le chiamate errate (nuove/libere o malloc/elimina) o mancanti.Se nel codice viene sovrascritto un buffer, le informazioni salvate possono essere errate ma ogni test può rilevare/scoprire/includere una soluzione di errore identificato.Molte corse per aiutare a identificare gli errori.Buona fortuna.

Pensi che questa sia una condizione di gara?Più thread condividono un heap?Puoi dare a ogni thread un heap privato con HeapCreate, quindi possono funzionare velocemente con HEAP_NO_SERIALIZE.Altrimenti, un heap dovrebbe essere thread-safe, se stai utilizzando la versione multi-thread delle librerie di sistema.

Un paio di suggerimenti.Hai menzionato i numerosi avvisi in W4: suggerirei di prendersi il tempo per correggere il codice in modo che venga compilato in modo pulito al livello di avviso 4: questo contribuirà notevolmente a prevenire bug sottili e difficili da trovare.

In secondo luogo, per l'opzione /analyze, genera effettivamente numerosi avvisi.Per utilizzare questa opzione nel mio progetto, quello che ho fatto è stato creare un nuovo file di intestazione che utilizzasse l'avviso #pragma per disattivare tutti gli avvisi aggiuntivi generati da /analyze.Poi, più in basso nel file, attivo solo gli avvisi che mi interessano.Quindi utilizzare l'opzione del compilatore /FI per forzare l'inclusione di questo file di intestazione per primo in tutte le unità di compilazione.Ciò dovrebbe consentire di utilizzare l'opzione /analyze mentre si controlla l'output

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top