範例 12:使用頁面堆積驗證來尋找 Bug

下列一系列的命令示範如何使用 GFlags 的頁面堆積驗證功能,以及 NTSD 偵錯工具來偵測堆積記憶體使用量中的錯誤。 在此範例中,程式設計人員懷疑虛構的應用程式pheap-buggy.exe發生堆積錯誤,並繼續進行一系列測試來識別錯誤。

如需 NTSD 的詳細資訊,請參閱 使用 CDB 和 NTSD 進行偵錯

步驟 1:啟用標準頁面堆積驗證

下列命令會啟用pheap-buggy.exe的標準頁面堆積驗證:

gflags /p /enable pheap-buggy.exe

步驟 2:確認頁面堆積已啟用

下列命令會列出啟用頁面堆積驗證的映射檔:

gflags /p

為了回應,GFlags 會顯示下列程式清單。 在此顯示中, 追蹤 表示標準頁面堆積驗證,而 完整追蹤 表示完整頁面堆積驗證。 在此情況下,pheap-buggy.exe會列出 追蹤,指出已如預期般啟用標準頁面堆積驗證。

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

步驟 3:執行偵錯工具

下列命令會在 NTSD 中執行 pheap-buggy.exe的 CorruptAfterEnd 函式,其中 -g (忽略初始中斷點) , 而 -x (設定存取違規例外狀況的第二次中斷) 參數:

ntsd -g -x pheap-buggy CorruptAfterEnd

當應用程式失敗時,NTSD 會產生下列顯示,指出它在 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

標頭資訊包括堆積的位址,其中包含損毀的區塊 (00C81000 :堆積控制碼) 、損毀區塊的位址 (00D81EB0 :堆積區塊) ,以及配置大小 (00000100:區塊大小) 。

「損毀尾碼模式」訊息表示應用程式違反 GFlags 在pheap-buggy.exe堆積配置結尾之後插入的資料完整性模式。

步驟 4:顯示呼叫堆疊

在下一個步驟中,使用 NTSD 回報的位址來找出造成錯誤的函式。 接下來的兩個命令會在偵錯工具中開啟行號傾印,並顯示具有行號的呼叫堆疊。

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

因此,偵錯工具會顯示具有行號pheap-buggy.exe的呼叫堆疊。 呼叫堆疊顯示顯示,當 pheap-buggy.exe 中的 TestCorruptAfterEnd 函式嘗試藉由呼叫 HeapFree來釋放0x00c80000配置時發生錯誤,這是重新導向至 RtlFreeHeap

這個錯誤最可能的原因是程式在這段函式中配置的緩衝區結尾之後寫入。

步驟 5:啟用完整頁面堆積驗證

不同于標準頁面堆積驗證,完整頁面堆積驗證可以在發生時立即攔截此堆積緩衝區的誤用。 下列命令會啟用pheap-buggy.exe的完整頁面堆積驗證:

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

步驟 6:確認已啟用完整頁面堆積

下列命令會列出啟用頁面堆積驗證的程式:

gflags /p

為了回應,GFlags 會顯示下列程式清單。 在此顯示中, 追蹤 表示標準頁面堆積驗證,而 完整追蹤 表示完整頁面堆積驗證。 在此情況下,pheap-buggy.exe會列出 完整追蹤,指出完整頁面堆積驗證已如預期般啟用。

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

步驟 7:再次執行偵錯工具

下列命令會在 NTSD 偵錯工具中執行pheap-buggy.exe的 CorruptAfterEnd 函式,其中 -g (忽略初始中斷點) ,而 -x (設定存取違規例外狀況的第二個機率中斷) 參數:

ntsd -g -x pheap-buggy CorruptAfterEnd

當應用程式失敗時,NTSD 會產生下列顯示,指出它在 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=??

啟用完整頁面堆積驗證後,偵錯工具會在存取違規時中斷。 若要尋找存取違規的精確位置,請開啟行號傾印並顯示呼叫堆疊追蹤。

編號的呼叫堆疊追蹤如下所示:

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

堆疊追蹤顯示問題發生在第 184 行的 pheap-buggy.exe。 因為已啟用完整頁面堆積驗證,所以呼叫堆疊會在程式碼中啟動,而不是在系統 DLL 中啟動。 如此一來,違規就會攔截到發生的位置,而不是釋放堆積區塊時。

步驟 8:在程式碼中找出錯誤

快速檢查會顯示問題的原因:程式會嘗試寫入 256 位元組 (0x101) 256 位元組 (0x100) 緩衝區的第 257 個位元組,這是一個常見的異動錯誤。

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