Share via


Beispiel 12: Verwenden der Seitenheapüberprüfung zum Suchen eines Fehlers

Die folgende Reihe von Befehlen veranschaulicht, wie Sie die Seitenheapüberprüfungsfeatures von GFlags und den NTSD-Debugger verwenden, um einen Fehler bei der Heapspeichernutzung zu erkennen. In diesem Beispiel vermutet der Programmierer, dass eine fiktive Anwendung, pheap-buggy.exe, einen Heapfehler aufweist, und führt eine Reihe von Tests durch, um den Fehler zu identifizieren.

Ausführliche Informationen zu NTSD finden Sie unter Debuggen mit CDB und NTSD.

Schritt 1: Aktivieren der Standardmäßigen Seitenheapüberprüfung

Der folgende Befehl aktiviert die Standardmäßige Seitenheapüberprüfung für pheap-buggy.exe:

gflags /p /enable pheap-buggy.exe

Schritt 2: Überprüfen, ob der Seitenheap aktiviert ist

Der folgende Befehl listet die Bilddateien auf, für die die Seitenheapüberprüfung aktiviert ist:

gflags /p

Als Antwort zeigt GFlags die folgende Liste der Programme an. In dieser Anzeige geben Ablaufverfolgungen die Standardmäßige Seitenheapüberprüfung an, und vollständige Ablaufverfolgungen geben die vollständige Seitenheapüberprüfung an. In diesem Fall wird pheap-buggy.exe mit Ablaufverfolgungen aufgeführt, was angibt, dass die Standardmäßige Seitenheapüberprüfung wie beabsichtigt aktiviert ist.

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

Schritt 3: Ausführen des Debuggers

Der folgende Befehl führt die CorruptAfterEnd-Funktion von pheap-buggy.exe in NTSD mit den Parametern -g (anfänglichen Haltepunkt ignorieren) und -x (zweite Chance für Zugriffsverletzungsausnahmen festlegen) aus:

ntsd -g -x pheap-buggy CorruptAfterEnd

Wenn die Anwendung fehlschlägt, generiert NTSD die folgende Anzeige, die angibt, dass ein Fehler in pheap-buggy.exe erkannt wurde:

===========================================================
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

Die Headerinformationen enthalten die Adresse des Heaps mit dem beschädigten Block (00C81000 : Heaphandle), die Adresse des beschädigten Blocks (00D81EB0 : Heapblock) und die Größe der Zuordnung (00000100 : Blockgröße).

Die Meldung "Beschädigtes Suffixmuster" gibt an, dass die Anwendung gegen das Datenintegritätsmuster verstoßen hat, das GFlags nach dem Ende der pheap-buggy.exe Heapzuordnung eingefügt hat.

Schritt 4: Anzeigen der Aufrufliste

Verwenden Sie im nächsten Schritt die Von NTSD gemeldeten Adressen, um die Funktion zu suchen, die den Fehler verursacht hat. Die nächsten beiden Befehle aktivieren das Zeilennummerndumping im Debugger und zeigen die Aufrufliste mit Zeilennummern an.

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

Daher zeigt der Debugger die Aufrufliste für pheap-buggy.exe mit Zeilennummern an. Die Aufrufliste zeigt, dass der Fehler aufgetreten ist, wenn die TestCorruptAfterEnd-Funktion in pheap-buggy.exe versucht hat, eine Zuordnung bei 0x00c80000 durch Aufrufen von HeapFree, einer Umleitung zu RtlFreeHeap, frei zu geben.

Die wahrscheinlichste Ursache für diesen Fehler ist, dass das Programm über das Ende des Puffers hinaus geschrieben hat, den es in dieser Funktion zugeordnet hat.

Schritt 5: Aktivieren der vollständigen Heapüberprüfung

Im Gegensatz zur standardmäßigen Seitenheapüberprüfung kann die vollständige Seitenheapüberprüfung den Missbrauch dieses Heappuffers abfangen, sobald sie auftritt. Der folgende Befehl aktiviert die vollständige Heapüberprüfung für pheap-buggy.exe:

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

Schritt 6: Überprüfen, ob der ganzseitige Heap aktiviert ist

Der folgende Befehl listet die Programme auf, für die die Seitenheapüberprüfung aktiviert ist:

gflags /p

Als Antwort zeigt GFlags die folgende Liste der Programme an. In dieser Anzeige geben Ablaufverfolgungen die Standardmäßige Seitenheapüberprüfung an, und vollständige Ablaufverfolgungen geben die vollständige Seitenheapüberprüfung an. In diesem Fall wird pheap-buggy.exe mit vollständigen Ablaufverfolgungen aufgelistet, was angibt, dass die vollständige Heapüberprüfung wie beabsichtigt aktiviert ist.

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

Schritt 7: Erneutes Ausführen des Debuggers

Der folgende Befehl führt die CorruptAfterEnd-Funktion von pheap-buggy.exe im NTSD-Debugger mit den Parametern -g (anfänglichen Haltepunkt ignorieren) und -x (zweite Chance für Unterbrechungen bei Zugriffsverletzungsausnahmen festlegen) aus:

ntsd -g -x pheap-buggy CorruptAfterEnd

Wenn die Anwendung fehlschlägt, generiert NTSD die folgende Anzeige, die angibt, dass ein Fehler in pheap-buggy.exe erkannt wurde:

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=??

Wenn die vollständige Seitenheapüberprüfung aktiviert ist, bricht der Debugger bei einer Zugriffsverletzung. Um die genaue Position der Zugriffsverletzung zu ermitteln, aktivieren Sie das Zeilennummern-Dumping, und zeigen Sie die Aufrufstapelüberwachung an.

Die Nummerierte Aufrufliste-Ablaufverfolgung wird wie folgt angezeigt:

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

Die Stapelüberwachung zeigt, dass das Problem in Zeile 184 der pheap-buggy.exe auftritt. Da die vollständige Heapüberprüfung aktiviert ist, beginnt die Aufrufliste im Programmcode und nicht in einer System-DLL. Infolgedessen wurde der Verstoß an dem Ort erfasst, an dem er aufgetreten ist, anstatt zu dem Zeitpunkt, an dem der Heapblock freigegeben wurde.

Schritt 8: Suchen des Fehlers im Code

Eine schnelle Überprüfung zeigt die Ursache des Problems: Das Programm versucht, in das 257. Byte (0x101) eines 256-Byte-Puffers (0x100) zu schreiben, ein häufiger Off-by-One-Fehler.

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