c++ - borland - tutorial delphi 7 pdf




Corruzione dell'heap in Win32; come individuare? (10)

È in condizioni di memoria insufficiente? In tal caso potrebbe essere che new stia restituendo NULL invece di lanciare std :: bad_alloc. I compilatori VC++ non lo implementavano correttamente. C'è un articolo sugli errori di allocazione della memoria legacy che bloccano le app STL create con VC6 .

Sto lavorando su un'applicazione C ++ multithread 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 questo è in circolazione da molto tempo e non è stato notato; sul lato negativo, i delta di origine non possono essere utilizzati per identificare quando è stato introdotto il bug - ci sono molte modifiche al codice nel repository.

Il prompt per il comportamento anomalo è quello di generare throughput in questo sistema - trasferimento socket dei dati che viene trasformato in una rappresentazione interna. Ho una serie di dati di test che causano periodicamente un'eccezione dell'app (vari luoghi, varie cause - incluso l'allocazione dell'heap non riuscita, quindi: corruzione dell'heap).

Il comportamento sembra correlato alla potenza della CPU o alla larghezza di banda della memoria; più ogni macchina ha, più è facile che si blocchi. La disabilitazione di un core hyper-threading o di un core dual core riduce il tasso di corruzione (ma non elimina). Ciò suggerisce un problema relativo alla tempistica.

Ora ecco il punto:
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 orribile ed eccezioni, come alloc; quando viene eseguito in un ambiente di debug sofisticato (Rational Purify, VS2008/MSVC9 o persino Microsoft Application Verifier) ​​il sistema diventa associato alla velocità della memoria e non si VS2008/MSVC9 modo anomalo (associato alla memoria: la CPU non supera il 50% , la luce del disco non è accesa , il programma sta andando il più velocemente possibile, consumando 1.3G G di 2 G di RAM). Quindi, ho una scelta 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.

Le mie migliori ipotesi su dove andare al prossimo sono:

  1. Ottieni una scatola follemente grintosa (per sostituire l'attuale scatola di sviluppo: 2 GB di RAM in un E6550 Core2 Duo ); ciò consentirà di riproporre l'arresto anomalo provocando comportamenti errati durante l'esecuzione in un ambiente di debug potente; o
  2. Riscrivi gli operatori new ed delete per utilizzare VirtualAlloc e VirtualProtect per contrassegnare la memoria come sola lettura non appena terminata. Esegui con MSVC6 e MSVC6 in MSVC6 che il sistema operativo catturi il cattivo che sta scrivendo per liberare memoria. Sì, questo è un segno di disperazione: chi diavolo riscrive new ed 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 "Overflow dello stack? Stiamo ricevendo overflow dello stack adesso?!?"

E ora, la domanda: come posso individuare il corruttore di heap?

Aggiornamento: bilanciare new[] ed delete[] sembra aver fatto molta strada per risolvere il problema. Invece di 15 minuti, l'app ora dura circa due ore prima di andare in crash. Non ancora. Ulteriori suggerimenti? La corruzione dell'heap persiste.

Aggiornamento: una versione di rilascio in Visual Studio 2008 sembra notevolmente migliore; l'attuale sospetto poggia sull'implementazione STL fornita con VS98 .

  1. Riprodurre il problema. Dr Watson produrrà una discarica che potrebbe essere utile per ulteriori analisi.

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

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

Ho capito che al momento, di nuovo: non c'è molto aiuto fino a quando qualcosa non va storto. Voglio catturare il vandalo nell'atto.

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

Non ho molte speranze, ma tempi disperati richiedono ...

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

No, non lo farò e domani passerò un paio d'ore a esaminare l'area di lavoro (58 progetti in esso) e verificare che siano tutti compilati e collegati con le bandiere appropriate.

Aggiornamento: ci sono voluti 30 secondi. Seleziona tutti i progetti nella finestra di dialogo Settings , deseleziona fino a trovare i progetti che non hanno le impostazioni giuste (avevano tutte le impostazioni giuste).


Abbiamo avuto abbastanza fortuna scrivendo le nostre funzioni malloc e gratuite. In produzione, chiamano semplicemente lo standard malloc e sono gratuiti, ma nel debug possono fare quello che vuoi. Abbiamo anche una semplice classe base che non fa altro che sovrascrivere i nuovi operatori ed eliminare gli operatori per utilizzare queste funzioni, quindi qualsiasi classe che scrivi può semplicemente ereditare da quella classe. Se hai un sacco di codice, potrebbe essere un grosso lavoro sostituire le chiamate a malloc e gratuitamente al nuovo malloc e gratuitamente (non dimenticare realloc!), Ma a lungo termine è molto utile.

Nel libro di Steve Maguire Writing Solid Code (altamente raccomandato), ci sono esempi di cose di debug che puoi fare in queste routine, come:

  • Tieni traccia delle allocazioni per trovare le perdite
  • Alloca più memoria del necessario e metti i marcatori all'inizio e alla fine della memoria - durante la routine libera, puoi assicurarti che questi marcatori siano ancora lì
  • memset la memoria con un marcatore sull'allocazione (per trovare l'uso della memoria non inizializzata) e su libero (per trovare l'uso della memoria libera)

Un'altra buona idea è di non usare mai 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 questi hanno colto molti problemi.


Esegui l'applicazione originale con ADplus -crash -pn appnename.exe Quando viene visualizzato il problema di memoria, otterrai un bel dump grande.

È possibile analizzare il dump per capire quale posizione della memoria è stata danneggiata. Se sei fortunato, la memoria di sovrascrittura è una stringa unica che puoi capire da dove proviene. Se non sei fortunato, dovrai scavare nell'heap win32 e capire quali erano le caratteristiche della memoria originale. (heap -x potrebbe aiutare)

Dopo aver appreso che cosa è stato incasinato, è possibile restringere l'utilizzo dell'appverifier con impostazioni heap speciali. cioè puoi specificare quale DLL monitorare o quale dimensione di allocazione monitorare.

Speriamo 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 trascorso molto tempo ad analizzare i dump di arresto anomalo e le fonti di navigazione.

PS: è possibile utilizzare DebugDiag per analizzare i dump. Può indicare la DLL possiede l'heap danneggiato e fornirti altri dettagli utili.


Hai provato vecchie build, ma c'è un motivo per cui non puoi continuare a tornare 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 sono in perdita di ciò che potresti voler registrare.

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


Il poco tempo che ho dovuto risolvere un problema simile. Se il problema persiste, ti suggerisco di farlo: Monitora tutte le chiamate a new / delete e malloc / calloc / realloc / free. Faccio una singola DLL esportando una funzione per registrare tutte le chiamate. Questa funzione riceve il parametro per identificare l'origine del codice, il puntatore all'area allocata e il tipo di chiamata salvando queste informazioni in una tabella. Tutta la coppia allocata / liberata viene eliminata. Alla fine o dopo è necessario effettuare una chiamata a un'altra funzione per creare un rapporto per i dati a sinistra. Con questo è possibile identificare chiamate errate (nuove / gratuite o malloc / elimina) o mancanti. Se nel tuo codice viene sovrascritto un caso di buffer, le informazioni salvate possono essere errate ma ogni test può rilevare / scoprire / includere una soluzione di errore identificata. Molte corse per aiutare a identificare gli errori. In bocca al lupo.


Il suggerimento di Graeme di malloc / free personalizzato è una buona idea. Vedi se riesci a caratterizzare alcuni schemi sulla corruzione per darti una leva da sfruttare.

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

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


La mia prima azione sarebbe la seguente:

  1. Costruisci i binari nella versione "Release" ma creando il file di informazioni di debug (troverai questa possibilità nelle impostazioni del progetto).
  2. Utilizzare Dr Watson come debugger predefinito (DrWtsn32 -I) su un computer su cui si desidera riprodurre il problema.
  3. Riproduzione del problema. Il dott. Watson produrrà una discarica che potrebbe essere utile per ulteriori analisi.

Un altro tentativo potrebbe essere l'utilizzo di 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 di runtime (scheda C / C ++, categoria Generazione di codice nelle impostazioni del progetto VS 6.0)?


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

Riscrivere nuovo ed eliminare potrebbe essere utile, ma ciò non cattura gli allocati impegnati dal codice di livello inferiore. Se questo è ciò che desideri, è meglio low-level alloc API utilizzando Microsoft Detours.

Anche controlli di integrità come: verifica la corrispondenza delle librerie di runtime (release vs debug, multi-thread vs single-thread, dll vs. lib statica), cerca le eliminazioni errate (ad esempio, elimina dove dovrebbe essere stato eliminato [] usato), assicurati di non mescolare e abbinare i tuoi alloc.

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?


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

  • Utilizzo errato dell'heap, ad es. Doppia libertà, lettura dopo libera, scrittura dopo libera, impostazione del flag HEAP_NO_SERIALIZE con allocazioni e liberazioni da più thread sullo stesso heap
  • Fuori dalla memoria
  • Codice errato (es. Buffer overflow, buffer underflow, ecc.)
  • Problemi di "tempismo"

Se sono tutti i primi due, ma non l'ultimo, dovresti averlo già scoperto 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 usare la semantica di acquisizione / rilascio per sincronizzare l'accesso alla memoria condivisa con un flag, non usare i blocchi in modo appropriato, ecc.

Come minimo, sarebbe utile essere in grado di tracciare le allocazioni in qualche modo, come è stato suggerito in precedenza. Almeno allora puoi vedere cosa è realmente accaduto fino alla corruzione dell'heap e tentare di diagnosticare da ciò.

Inoltre, se è possibile reindirizzare facilmente le allocazioni a più heap, è possibile provare a vedere se ciò risolve il problema o si traduce in un comportamento buggy più riproducibile.

Durante il test con VS2008, hai eseguito con HeapVerifier con Conserve Memory impostato su Sì? Ciò potrebbe ridurre l'impatto sulle prestazioni dell'allocatore di heap. (Inoltre, devi eseguirlo Debug-> Inizia 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 / cancellare, l'ho fatto e ho un semplice codice sorgente su:

Ciò rileva perdite di memoria e inserisce anche i dati di guardia prima e dopo il blocco di memoria per catturare il danneggiamento dell'heap. Puoi semplicemente integrarlo inserendo #include "debug.h" nella parte superiore di ogni file CPP e definendo DEBUG e DEBUG_MEM.







memory