Tecniche di debug CRT

Quando si esegue il debug di un programma che usa la libreria di runtime C, queste tecniche di debug potrebbero essere utili.

Uso della libreria di debug CRT

La libreria C Runtime (CRT) offre un ampio supporto per il debug. Per usare una delle librerie di debug CRT, è necessario collegare e compilare con /DEBUG/MDd, /MTdo ./LDd

Le definizioni e le macro principali per il debug CRT sono disponibili nel<crtdbg.h> file di intestazione.

Le funzioni delle librerie di debug CRT sono compilate con informazioni di debug /Z7, /Zd, /Zi, /ZI (Formato informazioni di debug) e senza ottimizzazione. Alcune funzioni contengono asserzioni per la verifica di parametri ricevuti e viene fornito il codice sorgente. Grazie a questo codice sorgente è possibile eseguire passo passo le funzioni CRT per accertarsi che funzionino correttamente e rilevare eventuali parametri o stati di memoria errati. Alcune tecnologie CRT sono proprietarie e non forniscono codice sorgente per la gestione delle eccezioni, la virgola mobile e altre routine.

Per ulteriori informazioni sulle varie librerie di runtime che è possibile utilizzare, vedere Librerie di runtime del linguaggio C.

Macro per la creazione di report

Per il debug, è possibile usare le _RPTn macro e _RPTFn , definite in<crtdbg.h>, per sostituire l'uso delle printf istruzioni. Non è necessario racchiuderli nelle #ifdef direttive, perché scompaiono automaticamente nella build di versione quando _DEBUG non sono definiti.

Macro Descrizione
_RPT0, _RPT1, _RPT2, _RPT3, _RPT4 Genera una stringa di messaggio e da zero a quattro argomenti. Per _RPT1 tramite _RPT4, la stringa del messaggio funge da stringa di formattazione in stile printf per gli argomenti.
_RPTF0, _RPTF1, _RPTF2, _RPTF3, _RPTF4 _RPTnCome , ma anche queste macro generano il nome del file e il numero di riga in cui si trova la macro.

Si consideri l'esempio seguente:

#ifdef _DEBUG
    if ( someVar > MAX_SOMEVAR )
        printf( "OVERFLOW! In NameOfThisFunc( ),
               someVar=%d, otherVar=%d.\n",
               someVar, otherVar );
#endif

Questo codice restituisce i valori di someVar e otherVar in stdout. È possibile utilizzare la chiamata di _RPTF2 che segue per restituire questi stessi valori nonché il nome file e il numero di riga:

if (someVar > MAX_SOMEVAR) _RPTF2(_CRT_WARN, "In NameOfThisFunc( ), someVar= %d, otherVar= %d\n", someVar, otherVar );

Alcune applicazioni potrebbero richiedere la creazione di report di debug che le macro fornite con la libreria di runtime C non forniscono. Per questi casi, è possibile scrivere una macro progettata in modo specifico per soddisfare i propri requisiti. In uno dei file di intestazione, ad esempio, è possibile includere codice simile al seguente per definire una macro denominata ALERT_IF2:

#ifndef _DEBUG                  /* For RELEASE builds */
#define  ALERT_IF2(expr, msg, arg1, arg2)  do {} while (0)
#else                           /* For DEBUG builds   */
#define  ALERT_IF2(expr, msg, arg1, arg2) \
    do { \
        if ((expr) && \
            (1 == _CrtDbgReport(_CRT_ERROR, \
                __FILE__, __LINE__, msg, arg1, arg2))) \
            _CrtDbgBreak( ); \
    } while (0)
#endif

Una chiamata a ALERT_IF2 potrebbe eseguire tutte le funzioni del printf codice:

ALERT_IF2(someVar > MAX_SOMEVAR, "OVERFLOW! In NameOfThisFunc( ),
someVar=%d, otherVar=%d.\n", someVar, otherVar );

È possibile modificare facilmente una macro personalizzata in modo da segnalare più o meno informazioni a destinazioni diverse. Questo approccio è utile man mano che i requisiti di debug si evolvono.

Scrittura di funzioni hook di debug

È possibile scrivere diversi tipi di funzioni hook di debug personalizzate che consentono di inserire il codice in alcuni punti predefiniti all'interno della normale elaborazione del debugger.

Funzioni hook del blocco client

Se si desidera convalidare o inserire in report il contenuto dei dati memorizzati in blocchi _CLIENT_BLOCK, sarà possibile scrivere una funzione specificamente per tale scopo. La funzione scritta deve avere un prototipo simile al seguente, come definito in<crtdbg.h>:

void YourClientDump(void *, size_t)

In altre parole, la funzione hook deve accettare un void puntatore all'inizio del blocco di allocazione, insieme a un size_t valore di tipo che indica le dimensioni dell'allocazione e restituire void. In caso contrario, il suo contenuto spetta all'utente.

Dopo aver installato la funzione hook usando _CrtSetDumpClient, verrà chiamata ogni volta che viene eseguito il dump di un _CLIENT_BLOCK blocco. Sarà quindi possibile utilizzare _CrtReportBlockType per ottenere informazioni sul tipo o sul sottotipo dei blocchi di cui è stato effettuato il dump.

Il puntatore alla funzione passato a _CrtSetDumpClient è di tipo _CRT_DUMP_CLIENT, come definito in<crtdbg.h>:

typedef void (__cdecl *_CRT_DUMP_CLIENT)
   (void *, size_t);

Funzioni hook di allocazione

Una funzione hook di allocazione, installata usando _CrtSetAllocHook, viene chiamata ogni volta che la memoria viene allocata, riallocata o liberata. È possibile usare questo tipo di hook per molti scopi diversi. Usarlo per testare il modo in cui un'applicazione gestisce situazioni di memoria insufficienti, ad esempio per esaminare i modelli di allocazione o le informazioni sull'allocazione dei log per un'analisi successiva.

Nota

Tenere presente la restrizione sull'uso delle funzioni della libreria di runtime C in una funzione hook di allocazione, descritta in Hook di allocazione e allocazioni di memoria crt.

Una funzione hook di allocazione deve avere un prototipo simile all'esempio seguente:

int YourAllocHook(int nAllocType, void *pvData,
        size_t nSize, int nBlockUse, long lRequest,
        const unsigned char * szFileName, int nLine )

Il puntatore passato a _CrtSetAllocHook è di tipo _CRT_ALLOC_HOOK, come definito in<crtdbg.h>:

typedef int (__cdecl * _CRT_ALLOC_HOOK)
    (int, void *, size_t, int, long, const unsigned char *, int);

Quando la libreria di runtime chiama l'hook, l'argomento nAllocType indica quale operazione di allocazione sta per essere eseguita (_HOOK_ALLOC, _HOOK_REALLOCo _HOOK_FREE). In una riallocazione gratuita o in una riallocazione, pvData ha un puntatore all'articolo dell'utente del blocco che sta per essere liberato. Tuttavia, per un'allocazione, questo puntatore è Null, perché l'allocazione non si è verificata. Gli argomenti rimanenti contengono le dimensioni dell'allocazione, il relativo tipo di blocco, un numero di richiesta sequenziale e un puntatore al nome del file. Se disponibile, gli argomenti includono anche il numero di riga in cui è stata effettuata l'allocazione. Dopo che la funzione hook esegue qualsiasi analisi e altre attività desiderate dall'autore, deve restituire TRUE, a indicare che l'operazione di allocazione può continuare o FALSE, a indicare che l'operazione deve avere esito negativo. Un semplice hook di questo tipo potrebbe controllare la quantità di memoria allocata finora e restituire FALSE se tale quantità supera un limite ridotto. L'applicazione riscontra quindi il tipo di errori di allocazione che normalmente si verificano solo quando la memoria disponibile era insufficiente. Funzioni hook più complesse possono tenere traccia degli schemi di allocazione, analizzare l'utilizzo della memoria o informare del verificarsi di determinate situazioni.

Hook di allocazione e allocazioni di memoria CRT

Una restrizione importante sulle funzioni hook di allocazione è che devono ignorare _CRT_BLOCK in modo esplicito i blocchi. Questi blocchi sono le allocazioni di memoria effettuate internamente dalle funzioni della libreria di runtime C se effettuano chiamate alle funzioni della libreria di runtime C che allocano memoria interna. È possibile ignorare _CRT_BLOCK i blocchi includendo il codice seguente all'inizio della funzione hook di allocazione:

if ( nBlockUse == _CRT_BLOCK )
    return( TRUE );

Se l'hook di allocazione non ignora _CRT_BLOCK i blocchi, qualsiasi funzione della libreria di runtime C chiamata nell'hook può intercettare il programma in un ciclo infinito. Ad esempio, printf crea un'allocazione interna. Se il codice hook chiama printf, l'allocazione risultante causerà la chiamata dell'hook di nuovo, che chiamerà printf di nuovo e così via, fino all'overflow dello stack. Se è necessario segnalare le operazioni di allocazione _CRT_BLOCK, un modo per ovviare a questa limitazione consiste nell'utilizzare funzioni API Windows, anziché funzioni di runtime del linguaggio C, per la formattazione e l'output. Poiché le API di Windows non usano l'heap della libreria di runtime C, non intercettare l'hook di allocazione in un ciclo infinito.

Se si esaminano i file di origine della libreria di runtime, si noterà che la funzione hook di allocazione predefinita , _CrtDefaultAllocHook che semplicemente restituisce TRUE, si trova in un file separato di propria proprietà, debug_heap_hook.cpp. Se si vuole chiamare l'hook di allocazione anche per le allocazioni effettuate dal codice di avvio di runtime eseguito prima della funzione dell'applicazione main , è possibile sostituire questa funzione predefinita con una delle proprie, anziché usare _CrtSetAllocHook.

Funzioni hook per la creazione di report

Una funzione hook del report, installata tramite _CrtSetReportHook, viene chiamata ogni volta che _CrtDbgReport genera un report di debug. È possibile utilizzare tale funzione, ad esempio, per filtrare i report in modo da concentrarsi su tipi specifici di allocazioni. Una funzione hook del report deve avere un prototipo simile all'esempio seguente:

int AppReportHook(int nRptType, char *szMsg, int *retVal);

Il puntatore passato a _CrtSetReportHook è di tipo _CRT_REPORT_HOOK, come definito in <crtdbg.h>:

typedef int (__cdecl *_CRT_REPORT_HOOK)(int, char *, int *);

Quando la libreria di runtime chiama la funzione hook, l'argomento nRptType contiene la categoria del report (_CRT_WARN, _CRT_ERRORo _CRT_ASSERT) szMsg contiene un puntatore a una stringa di messaggio di report completamente assemblata e retVal specifica se _CrtDbgReport deve continuare l'esecuzione normale dopo la generazione del report o l'avvio del debugger. Il retVal valore zero continua l'esecuzione, il valore 1 avvia il debugger.

Se l'hook gestisce completamente il messaggio in questione, in modo che non sia necessaria alcuna ulteriore segnalazione, deve restituire TRUE. Se restituisce FALSE, _CrtDbgReport in genere verrà restituito il messaggio.

Contenuto della sezione

  • Versioni di debug di funzioni di allocazione heap

    Vengono illustrate le versioni di debug speciali delle funzioni di allocazione dell'heap, tra cui: come viene chiamato il CRT, i vantaggi di chiamarli in modo esplicito, come evitare la conversione, tenere traccia dei tipi separati di allocazioni nei blocchi client e i risultati della mancata definizione _DEBUGdi .

  • Informazioni dettagliate sull'heap di debug CRT

    Descrive la gestione della memoria e l'heap di debug, i tipi di blocchi nell'heap di debug, le funzioni di creazione di report sullo stato dell'heap heap di debug e come tenere traccia delle richieste di allocazione.

  • Trovare perdite di memoria usando la libreria CRT

    Vengono illustrate le tecniche per rilevare e isolare le perdite di memoria utilizzando il debugger e la libreria di runtime del linguaggio C.

Vedi anche