次の方法で共有


例 12: ページ ヒープ検証を使用してバグを発見する

次の一連のコマンドは、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: デバッガーを実行します

次のコマンドは、-g (最初のブレークポイントを無視) パラメーターと -x (アクセス違反例外にセカンドチャンスの中断を設定) パラメーターを付けて NTSD で pheap-buggy.exe の CorruptAfterEnd 関数を実行します。

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: ブロック サイズ) が含まれています。

「破損したサフィックス パターン」メッセージは、アプリケーションが pheap-buggy.exe のヒープ割り当ての後に GFlags によって挿入されたデータ整合性パターンに違反したことを示します。

手順 4: 呼び出し履歴を表示します

次の手順では、NTSD が報告したアドレスを使用して、エラーの原因となった関数を特定します。 次の 2 つのコマンドは、デバッガーで行番号のダンプ出力を有効にし、行番号を含む呼び出し履歴を表示します。

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 関数が RtlFreeHeap へのリダイレクト HeapFree を呼び出して 0x00c80000 の割り当てを解放しようとしたときにエラーが発生したことが示されています。

このエラーの原因として、プログラムがこの関数で割り当てたバッファーの末尾を超えて書き込んだことが考えられます。

手順 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: もう一度デバッガーを実行します

次のコマンドは、-g (最初のブレークポイントを無視) パラメーターと -x (アクセス違反例外にセカンドチャンスの中断を設定) パラメーターを付けて NTSD デバッガーで pheap-buggy.exe の CorruptAfterEnd 関数を実行します。

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

スタック トレースでは、pheap-buggy.exe の 184 行目で問題が発生したことが示されています。 完全ページ ヒープ検証が有効になっているため、呼び出し履歴はシステム DLL ではなくプログラム コードで起動します。 その結果、ヒープ ブロックが解放されたときではなく、違反が発生した場所が取得されました。

手順 8: コードのエラーを特定します

簡単な検査で問題の原因が明らかになりました。プログラムが 256 バイト (0x100) バッファーで 257 番目のバイト (0x101) に書き込もうとしています。一般的な off-by-one エラーです。

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