Condividi tramite


Esempio 12: Uso della verifica heap pagina per trovare un bug

La serie di comandi seguente illustra come usare le funzionalità di verifica dell'heap della pagina di GFlags e del debugger NTSD per rilevare un errore nell'uso della memoria heap. In questo esempio, il programmatore sospetta che un'applicazione fittizia, pheap-buggy.exe, abbia un errore heap e procede attraverso una serie di test per identificare l'errore.

Per informazioni dettagliate su NTSD, vedere Debug con CDB e NTSD.

Passaggio 1: Abilitare la verifica dell'heap della pagina standard

Il comando seguente abilita la verifica dell'heap della pagina standard per pheap-buggy.exe:

gflags /p /enable pheap-buggy.exe

Passaggio 2: Verificare che l'heap di pagina sia abilitato

Il comando seguente elenca i file di immagine per cui è abilitata la verifica dell'heap della pagina:

gflags /p

In risposta, GFlags visualizza l'elenco seguente di programmi. In questa visualizzazione le tracce indicano la verifica dell'heap della pagina standard e le tracce complete indicano la verifica dell'heap di pagina completa. In questo caso, pheap-buggy.exe è elencato con tracce, indicando che la verifica heap della pagina standard è abilitata, come previsto.

pheap-buggy.exe: page heap enabled with flags (traces )

Passaggio 3: Eseguire il debugger

Il comando seguente esegue la funzione CorruptAfterEnd di pheap-buggy.exe in NTSD con i parametri -g (ignorare il punto di interruzione iniziale) e -x (set second-chance break on access violation exceptions) parametri:

ntsd -g -x pheap-buggy CorruptAfterEnd

Quando l'applicazione ha esito negativo, NTSD genera la visualizzazione seguente, che indica che ha rilevato un errore in pheap-buggy.exe:

===========================================================
VERIFIER STOP 00000008: pid 0xAA0: corrupted suffix pattern

        00C81000 : Heap handle 
        00D81EB0 : Heap block 
        00000100 : Block size 
#         00000000 :
===========================================================

Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00d81eb0 ecx=77f7e257 edx=0006fa18 esi=00000008 edi=00c81000
eip=77f7e098 esp=0006fc48 ebp=0006fc5c iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
ntdll!DbgBreakPoint:
77f7e098 cc               int     3

Le informazioni sull'intestazione includono l'indirizzo dell'heap con il blocco danneggiato (00C81000 : handle Heap), l'indirizzo del blocco danneggiato (00D81EB0 : blocco Heap) e le dimensioni dell'allocazione (00000100 : dimensioni del blocco).

Il messaggio "modello di suffisso danneggiato" indica che l'applicazione violava il modello di integrità dei dati inserito dopo la fine dell'allocazione dell'heap pheap-buggy.exe.

Passaggio 4: Visualizzare lo stack di chiamate

Nel passaggio successivo usare gli indirizzi segnalati da NTSD per individuare la funzione che ha causato l'errore. I due comandi successivi attivano il dump del numero di riga nel debugger e visualizzano lo stack di chiamate con numeri di riga.

C:\>.lines

Line number information will be loaded 

C:\>kb

ChildEBP RetAddr  Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
0006fc5c 77f9e6dd 00000008 77f9e3e8 00c81000 ntdll!DbgBreakPoint
0006fcd8 77f9f3c8 00c81000 00000004 00d81eb0 ntdll!RtlpNtEnumerateSubKey+0x2879
0006fcfc 77f9f5bb 00c81000 01001002 00000010 ntdll!RtlpNtEnumerateSubKey+0x3564
0006fd4c 77fa261e 00c80000 01001002 00d81eb0 ntdll!RtlpNtEnumerateSubKey+0x3757
0006fdc0 77fc0dc2 00c80000 01001002 00d81eb0 ntdll!RtlpNtEnumerateSubKey+0x67ba
0006fe78 77fbd87b 00c80000 01001002 00d81eb0 ntdll!RtlSizeHeap+0x16a8
0006ff24 010013a4 00c80000 01001002 00d81eb0 ntdll!RtlFreeHeap+0x69
0006ff3c 01001450 00000000 00000001 0006ffc0 pheap-buggy!TestCorruptAfterEnd+0x2b [d:\nttest\base\testsrc\kernel\rtl\pageheap\pheap-buggy.cxx @ 185]
0006ff4c 0100157f 00000002 00c65a68 00c631d8 pheap-buggy!main+0xa9 [d:\nttest\base\testsrc\kernel\rtl\pageheap\pheap-buggy.cxx @ 69]
0006ffc0 77de43fe 00000000 00000001 7ffdf000 pheap-buggy!mainCRTStartup+0xe3 [crtexe.c @ 349]
0006fff0 00000000 0100149c 00000000 78746341 kernel32!DosPathToSessionPathA+0x204

Di conseguenza, il debugger visualizza lo stack di chiamate per pheap-buggy.exe con numeri di riga. Lo stack di chiamate mostra che si è verificato l'errore quando la funzione TestCorruptAfterEnd in pheap-buggy.exe ha tentato di liberare un'allocazione in 0x00c80000 chiamando HeapFree, un reindirizzamento a RtlFreeHeap.

La causa più probabile di questo errore è che il programma ha scritto oltre la fine del buffer allocato in questa funzione.

Passaggio 5: Abilitare la verifica dell'heap a pagina completa

A differenza della verifica heap di pagina standard, la verifica dell'heap a pagina completa può rilevare l'uso improprio di questo buffer heap non appena si verifica. Il comando seguente abilita la verifica dell'heap a pagina completa per pheap-buggy.exe:

gflags /p /enable pheap-buggy.exe /full

Passaggio 6: Verificare che l'heap a pagina completa sia abilitato

Il comando seguente elenca i programmi per cui è abilitata la verifica dell'heap della pagina:

gflags /p

In risposta, GFlags visualizza l'elenco seguente di programmi. In questa visualizzazione le tracce indicano la verifica dell'heap della pagina standard e le tracce complete indicano la verifica dell'heap di pagina completa. In questo caso, pheap-buggy.exe è elencato con tracce complete, indicando che la verifica dell'heap a pagina completa è abilitata, come previsto.

pheap-buggy.exe: page heap enabled with flags (full traces )

Passaggio 7: Eseguire di nuovo il debugger

Il comando seguente esegue la funzione CorruptAfterEnd di pheap-buggy.exe nel debugger NTSD con il parametro -g (ignora il punto di interruzione iniziale) e -x (impostare le eccezioni di seconda probabilità per le eccezioni di violazione di accesso):

ntsd -g -x pheap-buggy CorruptAfterEnd

Quando l'applicazione ha esito negativo, NTSD genera la visualizzazione seguente, che indica che ha rilevato un errore in pheap-buggy.exe:

Page heap: process 0x5BC created heap @ 00880000 (00980000, flags 0x3)
ModLoad: 77db0000 77e8c000   kernel32.dll
ModLoad: 78000000 78046000   MSVCRT.dll
Page heap: process 0x5BC created heap @ 00B60000 (00C60000, flags 0x3)
Page heap: process 0x5BC created heap @ 00C80000 (00D80000, flags 0x3)
Access violation - code c0000005 (first chance)
Access violation - code c0000005 (!!! second chance !!!)
eax=00c86f00 ebx=00000000 ecx=77fbd80f edx=00c85000 esi=00c80000 edi=00c16fd0
eip=01001398 esp=0006ff2c ebp=0006ff4c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000206
pheap-buggy!TestCorruptAfterEnd+1f:
01001398 889801010000     mov     [eax+0x101],bl          ds:0023:00c87001=??

Con la verifica dell'heap a pagina completa abilitata, il debugger interrompe una violazione di accesso. Per trovare la posizione precisa della violazione di accesso, attivare il dump del numero di riga e visualizzare la traccia dello stack di chiamate.

La traccia dello stack di chiamate numerate viene visualizzata come segue:

ChildEBP RetAddr  Args to Child
0006ff3c 01001450 00000000 00000001 0006ffc0 pheap-buggy!TestCorruptAfterEnd+0x1f [d:\nttest\base\testsrc\kernel\rtl\pageheap\pheap-buggy.cxx @ 184]
0006ff4c 0100157f 00000002 00c16fd0 00b70eb0 pheap-buggy!main+0xa9 [d:\nttest\base\testsrc\kernel\rtl\pageheap\pheap-buggy.cxx @ 69]
0006ffc0 77de43fe 00000000 00000001 7ffdf000 pheap-buggy!mainCRTStartup+0xe3 [crtexe.c @ 349]
WARNING: Stack unwind information not available. Following frames may be wrong.
0006fff0 00000000 0100149c 00000000 78746341 kernel32!DosPathToSessionPathA+0x204

La traccia dello stack mostra che il problema si verifica nella riga 184 di pheap-buggy.exe. Poiché la verifica heap a pagina completa è abilitata, lo stack di chiamate viene avviato nel codice del programma, non in una DLL di sistema. Di conseguenza, la violazione è stata rilevata dove è successo, anziché quando il blocco heap è stato liberato.

Passaggio 8: Individuare l'errore nel codice

Un'ispezione rapida rivela la causa del problema: il programma tenta di scrivere nel 257° byte (0x101) di un buffer da 256 byte (0x100), un errore di off-by-one comune.

*((PCHAR)Block + 0x100) = 0;