PageHeap ユーザー ガイド
はじめに
ページ ヒープの使用
ページ ヒープが検証するインターフェイス
ページ ヒープで見つかったバグのクラス
ページ ヒープ機能
ページ ヒープ機能
ページ ヒープは、ヒープ関連のバグや破損を見つけるための強力なツールであり、また Microsoft Windows システム上で実行されているアプリケーションのリークもある程度見つけることができます。ページ ヒープでは、アプリケーションとシステムの間にソフトウェア検証層を導入し、すべての動的メモリ処理 (割り当て、解放、その他のヒープ処理) を検証します。この文書では、ページ ヒープの使用方法を解説します。Windows 2000 Service Pack 1 および Windows XP から利用可能になるページ ヒープ機能が対象となります。
ページ ヒープはオペレーティング システムの一部であり、個別にインストールする必要はありません。その動作は、Gflags.exe および Pageheap.exe の 2 つのツールで制御します。
ほとんどのヒープ ブロックの破損は、次のどちらかの方法で発見できます。
全ページ ヒープ - 割り当ての末尾にアクセス不可能なページを置きます。
標準ページ ヒープ - ブロックが解放される時にフィル パターンを確認します。
最初の方法の利点は、プロセスが障害の地点でアクセス違反 (AV) を起こすため、デバッグが簡単になることです。欠点は、すべての割り当ては、コミットされたメモリを少なくとも 1 ページは使用するため、メモリを大量に使用するプロセスの場合、システム リソースをすぐに使い果たしてしまうことです。
2 番目のフィル パターンを使用する方法は、メモリの制限によってページ ヒープが使用できなくなる状況に対応するために考えられました。この方法は、メモリの消費量を削減します。ただし、ブロックが解放されたときしか破損が検出されません。
全ページ ヒープと標準ページ ヒープのどちらに割り当てるかは、設定可能ないくつかのページ ヒープ フラグの値に応じて決定します。
以下に、ほとんどの一般的なページ ヒープの使用例を説明します。すべて、gflags.exe および pageheap.exe の 2 つのツールを使用しています。これらのツールは、ページ ヒープのフラグを設定するツールであり、アプリケーションは起動されません。ページ ヒープの設定はレジストリに保存されるため、再起動しても、また複数のセッションでも有効です。
一般的な使用手順は、まず特定のページ ヒープ フラグを設定し、次にテストするアプリケーションを起動します。Gflags ツールは、システム全体でページ ヒープを有効にするため、再起動が必要です。プロセスのページ ヒープ設定は、プロセスがすでに実行中になっていると適用されません。このため、winlogon、lsass、services といった、再開できない "不死の" プロセスにページ ヒープ検証を設定する際にも再起動が必要です。
システム全体のページ ヒープ
システム上で実行されるすべてのアプリケーションにページ ヒープを設定するには、コマンド プロンプトから、以下のパラメータを付けて Gflags を実行します。
gflags -r +hpa
"-r" コマンドにより、Gflags は、カーネルや特定のイメージ ファイルでなく、システム レジストリ 設定上で動作します。"+hpa" コマンドにより、ページの末尾にヒープ割り当てが置かれます。これらの設定を有効にするには、再起動します。メモリの制限はありません (以前のバージョンでは、512 MB 以上のページ ファイルが必要でした)。
プロセスごとのページ ヒープ
1 つのプロセスに対してだけページ ヒープを有効化することで、そのプロセスだけを検証することができます。たとえば、Internet Explorer (iexplore.exe) をテストする場合、ページ ヒープを有効化するコマンドとして、次の 2 つのいずれかを使用できます。
pageheap /enable iexplore.exe
pageheap /enable iexplore.exe /full
最初のコマンドでは、標準ページ ヒープが有効化されます。/full オプションを使用すると、全ページ ヒープが有効化されます。全ページ ヒープのほうがバグが早く検出されますが、非常に多くのメモリを消費します。winlogon.exe、lsass.exe、services.exe など、いくつかのプロセスで /full オプションを使用するときは、大量のメモリ消費に備えて、ページ ファイルを 512 MB 以上にする必要があります。
DLL ごとのページ ヒープ
全ページ ヒープ バージョンのページ ヒープを実行する利点については後で紹介します。テストするプロセスのメモリ要求が高い場合は、いくつかの指定された DLL で実行される割り当てについてのみ、全ページ ヒープが使用できます。それ以外は標準ページ ヒープによる割り当てです。
たとえば、ole32.dll、mshtml.dll、および jscript.dll の 3 つの DLL で実行されるヒープ処理のみを完全にチェックするには、次のコマンドを使用します。
pageheap /enable iexplore.exe /dlls ole32.dll mshtml.dll jscript.dll
ページ ヒープの無効化
テストの終了後、ページ ヒープ検証を無効化することができます。サンプルの iexplore.exe に対するページ ヒープを無効化するには、次のコマンドを使用します。
pageheap /disable iexplore.exe
Gflags を使用してシステム全体でページ ヒープが有効化されている場合は、次のコマンドを使用して無効化します ("+" でなく "-" を使うことに注意してください)。
gflags -r -hpa
その他のヒープ フラグについて
ページ ヒープを有効化する際、Gflags と一緒にほかのグローバル フラグを設定することはできません。最大レベルのヒープ検証を実現しようとして、Gfalgs ユーザー インターフェイス内で "ヒープ" という言葉が含まれたチェックボックスをすべてチェックすることは避けてください。全ページ ヒープが有効化されている場合は、ほかのヒープ フラグはすべて無意味です。ページ ヒープ マネージャと、余計なヒープ フラグの衝突のため、誤ったページ ヒープ バグが表示されてしまいます。
カスタム割り当て/解放関数が後で NT ヒープ管理インターフェイス (RtlAllocateHeap、RtlFreeHeap など) に呼び出しを行う場合に限り、ページ ヒープは、C++ 形式の割り当てである "new " および "delete" を含め、すべてのメモリ割り当てプロセスで有効です。以下の関数は動作が確認されています。
HeapAlloc、HeapFree、HeapReAlloc など。これらの関数は kernel32.dll によってエクスポートされ、NT ヒープ インターフェイスに直接呼び出しを行います。
GlobalAlloc、GlobalFree、GlobalReAlloc など。これらの関数は kernel32.dll によってエクスポートされ、NT ヒープ インターフェイスに必ず呼び出しを行います。
LocalAlloc、LocalFree、LocalReAlloc など。これらの関数は kernel32.dll によってエクスポートされ、NT ヒープ インターフェイスに必ず呼び出しを行います。
malloc、free、realloc、msize、expand。これらの関数は msvcrt.dll によってエクスポートされ、NT ヒープに必ず呼び出しを行います。以前はそうではありませんでした。C ランタイムの以前のヒープ実装は、まったく異なるものでした。現在の C ランタイムは NT ヒープに直接呼び出しを行います。
Operators new、delete、new[ ]、delete[ ]。これらの関数は msvcrt.dll によってエクスポートされ、NT ヒープに必ず呼び出しを行います。
その他の割り当て/解放関数セットはおそらくカスタム スキームであり、直接的または間接的に NT ヒープに呼び出しを行うことは保証されません。実際の実装を調べるには、ソース コードを検査するか、デバッガで実行するしかありません。
静的リンクの使用は避けてください。アプリケーションによっては、旧バージョンの C ランタイムと静的にリンクされています。これら旧バージョンは、NT ヒープ API を呼び出さず、割り当ての検証にページ ヒープを使用することはできません。動的リンクを使用すれば、最新の C ランタイム ライブラリ (msvcrt.dll) を取得できます。
ページ ヒープはほとんどのヒープ関連のバグを検出します。ただし重点が置かれているのはヒープ破損であり、リークではありません。ページ ヒープには、ヒープ リークの検出機能もありますが、あまり優秀ではありません。
ページ ヒープの利点の 1 つは、多くのエラーが、発生時に検出できることです。たとえば、動的に割り当てられたバッファの末尾で 1 バイトのずれがあると、すぐにアクセス違反が発生します。発生時に検出できないエラーの種類はわずかです。その場合、ブロックが解放されるまでエラーレポートが遅延されます。
Invalid heap pointer ( 無効なヒープ ポインタ ) - すべての Win32 および NT レベルのヒープ インターフェイスは、処理が実行されるべき場所のヒープへのポインタを最初のパラメータに取ります。ページ ヒープ マネージャは、呼び出しが行われたときに、無効なヒープ ポインタを検出します。
Invalid heap block pointer ( 無効なヒープ ブロック ポインタ ) - 割り当てられた後のブロックは、いくつかのヒープ インターフェイスのパラメータとして使用できます。特に free() クラスのインターフェイスで使用されます。ページ ヒープ マネージャは、無効なヒープ ブロック ポインタを即座に検出します。無効なアドレスが、数バイトのずれなのか、または完全な不正 (ガーベジ) なのかを判断する方法については、デバッグ テクニックのセクションを参照してください。
Multithreaded unsynchronized access to the heap ( ヒープへの、同期されていないマルチスレッド アクセス ) -アプリケーションによっては、複数のスレッドからヒープに呼び出しを行うものがあります。この場合は、ユーザーが、ヒープ ロックの取得をトリガするフラグを設定しなければなりません。ページ ヒープ マネージャは、2 つのスレッドが同時にヒープに呼び出しを行おうとしたときに、このような違反を検出します。
Assumptions about reallocation of a block at the same address ( ブロックの再割り当てで同じアドレスを想定 )- 再割り当てを実行しても、同じアドレスが返されるとは限りません。再割り当てによってブロックサイズが減少する場合はなおさらです。アプリケーションの中には、再割り当て時に同じアドレスが返されると想定しているものがあります。ページ ヒープ マネージャは、再割り当ての際、常に新しいブロックを割り当てて、古いブロックを解放します。解放されているブロックは読み書きアクセス保護されているため、アクセスしようとすると、アクセス違反が発生します。
Double free ( 二重解放 ) - このバグは、同じヒープ ブロックが複数回解放されるバグで、アプリケーションによっては頻繁に発生します。2 番目の解放ではブロックは正しいプレフィックス ヘッダを持たず、割り当て済みブロックの中に見つからないため、ページ ヒープ マネージャはこのバグを即座に検出することができます。最初の解放処理のスタックトレースを分析する方法については、デバッグ テクニックのセクションを参照してください。アプリケーションがブロックのアドレスであると見なしているものを解放したとき、そのブロックはすでに再割り当ての一環として解放されているため、このエラーは再割り当てエラーの 1 つであるとも考えられます。
Access of block after free ( 解放後のブロックへのアクセス ) - 解放されたメモリ ブロックは、ページ ヒープ マネージャによって、保護メモリのプール内に短期間保管されます。それらのブロックにアクセスしようとすると、アクセス違反が発生します。"発生地" の原則に基き、解放されている保護プールが十分大きければ、ほとんどの問題を検出することができるはずです。解放されたブロックが保護プール内にある場合は、バグはすぐに検出されます。メモリが再利用された場合は、バグやその原因となったコードを検出できる可能性は非常に少なくなります。
Access after the end of allocated block ( 割り当て済みブロックの末尾以降へのアクセス ) - ページ ヒープ マネージャは、割り当てたブロックのすぐ後にアクセス不可能なページを置きます。ブロックの末尾以降にアクセスしようとすると、アクセス違反が発生します。Internet Explorer を含むいくつかのアプリケーションは、割り当てが 8 バイトで整列されていると想定しています。これは、NT 3.5 ヒープ マネージャ以降サポートされている機能です。8 バイトで整列されていない要求サイズは、8 バイト整列アドレスを取得するため、ブロックの末尾以降にアクセス可能なバイトがいくつか残ります。アプリケーションがこれら数バイトでのみ破損している場合は、ブロックが解放されるときにブロック サフィックス パターンを確認しない限りエラーが検出されません。
Access before the start of allocated block ( 割り当て済みブロックの先頭以前へのアクセス ) - 設定可能なフラグを使用することで、ページ ヒープ マネージャがブロックの末尾でなく先頭にアクセス不可能なページを置くように指定できます。ブロックの先頭以前にアクセスしようとすると、アクセス違反が発生します。
エラー |
標準ページ ヒープ |
全ページ ヒープ |
---|---|---|
Invalid heap pointer (無効なヒープ ポインタ) |
即座に検出 |
即座に検出 |
Invalid heap block pointer (無効なヒープ ブロック ポインタ) |
即座に検出 |
即座に検出 |
Unsynchronized access (同期されていないアクセス) |
即座に検出 |
即座に検出 |
Assumption about reallocation address (再割り当てアドレスに関する想定) |
実際の解放までに 90% |
90% は即座に検出 |
Double free (二重解放) |
90% は即座に検出 |
90% は即座に検出 |
Reuse after free (解放後の再利用) |
実際の解放までに 90% |
90% は即座に検出 |
Access after end of block (ブロック末尾以降へのアクセス) |
解放時に検出 |
即座に検出 |
Access before start of block (ブロック先頭以前へのアクセス) |
解放時に検出 |
即座に検出 (特殊なフラグ) |
このセクションでは、Windows 2000 Service Pack 1 で利用可能なすべてのページ ヒープ機能について説明します。
標準ページ ヒープ
重大な問題を解決するために、全ページ ヒープおよび標準ページ ヒープの 2 つの方法が用意されています。全ページ ヒープ テストでは膨大なメモリが消費されるため、大規模プロセスではページ ヒープが役立ちません。しかし、大規模プロセスでは、信頼性が最も重要です。標準ページ ヒープでは大規模プロセスのテストを実行できます。ただしブロックが解放されるまで問題を検出できません。このためエラーのデバッグが難しくなりますが、ページ ヒープ バグ検出をまったく使用しないよりは良い方法であると言えます。
一般に、最初の大規模プロセス テストでは標準ページ ヒープを使用します。問題が検出されたら、それらのプロセス内の限定されたクラスの割り当て (特定のサイズ範囲または特定のライブラリなど) に対して全ページ ヒープを有効化します。詳細は後述のデバッグのセクションで解説します。
標準ページ ヒープは、すべてのプロセスに対して、システム全体で安全に有効化できます。これは、コンポーネントに焦点を当てたテストでなく、システム全般の検査を実行する場合に非常に役立ちます。
標準ページ ヒープをシステム全体で有効化するには、次の Gflags コマンドを使用します。
gflags -r +hpa
コマンドを有効化するには、再起動する必要があります。システム全体の標準ページ ヒープを無効化するには、次のコマンドを実行します。
gflags -r -hpa
単一のプロセスに対して標準ページ ヒープを有効化するには、次のコマンドを使用します。
pageheap /enable imagename
imagename パラメータは、実行イメージを含むバイナリファイルの名前で、ファイルのパス名は含みません (notepad.exe、iexplore.exe など)。
全ページ ヒープ
全ページ ヒープは、個々のプロセスに対してのみ有効化できます。全ページ ヒープでは、必要となるページ ファイル サイズを正しく評価することが困難であるため、システム全体で有効化することはできません。システム全体の全ページ ヒープで、小さすぎるページ ファイルを使用すると、システムが起動できなくなります。単一のプロセスに対して全ページ ヒープを有効化するには、次のコマンドを使用します。
pageheap /enable imagename /full
imagename パラメータは、実行イメージを含むバイナリファイルの名前で、ファイルのパス名は含みません (notepad.exe、iexplore.exe など)。
DLL ごとのページ ヒープ割り当て
ページ ヒープ オーバーヘッドに必要なメモリの量を減らす 1 つの方法は、特定の DLL から要求が送られた場合のみ全ページ ヒープに割り当てることです。これにより、その DLL に対するすべてのインポートがページ ヒープ関数にリダイレクトされます。リダイレクトされるインポート関数は以下のとおりです。
HeapAlloc/Free
GlobalAlloc/Free
LocalAlloc/Free
malloc/free
operator new/delete
operator new[ ]/delete[ ]
DLL ごとのヒープ割り当てを有効化するには、次のコマンドを使用します。
pageheap /enable imagename /dlls dllimage dllimage ・・・
このコマンドでは、1 つまたは複数の DLL イメージ名を使用できます。複数の場合はスペースで区切ります。dllimage は DLL ライブラリの名前です。拡張子は含みますが、パス名は含みません。プロセス空間にロードされるすべてのイメージの末尾が ".dll" であるとは限らないため、拡張子は特に重要です。
サイズ範囲ページ ヒープ割り当て
ページ ヒープ オーバーヘッドに必要なメモリの量を減らすもう 1 つの方法は、特定のサイズ範囲内のブロックのみを全ページ ヒープに割り当てることです。サイズ範囲によるヒープ割り当てを有効化するには、次のコマンドを使用します。
pageheap /enable imagename /full /size start end
start および end パラメータには、ページ ヒープに割り当てられるブロックの、閉じたサイズ インターバルをバイト (十進数) で指定します。
アドレス範囲ページ ヒープ割り当て
この機能を使用すると、割り当ての時点でキャプチャされたスタックトレース内に含まれるブロックにのみ、またそのアドレスが特定の範囲内にある場合のみ、ページ ヒープを割り当てることができます。この機能は、一般的には使用されません。x86 アーキテクチャでしか動作せず、また基盤となるスタックトレースのキャプチャ アルゴリズムの問題により、100% 信頼できるものではないためです。アドレス範囲による割り当てを有効化するには、次のコマンドを使用します。
pageheap /enable imagename /full /address start end
start および end パラメータには、すべての割り当てスタックトレース上で検索するアドレス範囲を指定します。アドレスは、"C" 構文で 16 進数で指定します (0xAABBCCDDなど)。
ランダム ページ ヒープ割り当て
このオプションを使用すると、割り当てを全ページ ヒープで実行するか標準ページ ヒープで実行するかがランダムに決定されます。ランダム割り当てを有効化するには、次のコマンドを使用します。
pageheap /enable I/full /random probability
probability パラメータは 0 ~ 00 までの十進数整数で、割り当てを全ページ ヒープで実行する確率を表します。この値を 100 にすると、次のコマンドと同じ働きをします。
pageheap /enable imagename /full
この値を 0 にすると、次のコマンドと同じ働きをします。
pageheap /enable imagename.
逆方向オーバーランの検出
ページ ヒープが検出するバグのほとんどは "バッファ末尾のオーバーラン" です。しかし時として、バッファ先頭のオーバーランもあります。全ページ ヒープでは、アクセス不可能なページを、割り当ての末尾でなく先頭に置くこともあります。逆方向オーバーランの検出を有効化するには、次のコマンドを使用します。
pageheap /enable /full /backwards
整列されていない割り当て
すべてのバージョンの Windows ヒープ マネージャで、ヒープ割り当ての開始アドレスは 8 バイトで整列されていると保証されていました (64 ビット プラットフォームでは 16 バイト整列)。ページ ヒープ マネージャでも同じことが保証されます。しかし、割り当ての末尾をページの末尾と揃えたい場合は、これが不可能になります。ページ末尾に揃える割り当てが必要になるのは、1 バイトずれのエラーがあるときに、アクセス不可能なページへの読み書き引き起こし、即座にエラーを発生させたい場合です。
ブロック サイズとしてユーザーが要求した値が 8 バイトに整列されていない場合、ページ ヒープは、"開始アドレスの 8 バイト整列" 制限と "終了アドレスのページ整列" 制限の両方を満たすことができません。この場合、最初の制限を満たして、ブロックの開始アドレスを 8 バイトに整列するという解決策がとられます。その後、ブロックの末尾とアクセス不可能なページの先頭の間に小さなフィル パターンを使用します。このフィル パターンは、32 ビット アーキテクチャでは、0 ~ 7 バイトの長さになります。このフィル パターンは、解放時にチェックされます。
エラーを検出しないと末尾にフィル パターンが設定される、このような割り当てについて、即座にエラーを検出する必要がある場合は、次のコマンドを使用します。
pageheap /enable imagename /full /unaligned
これにより、ページ ヒープ マネージャは 8 バイト整列の規則を無視し、割り当ての末尾を常にページの境界に揃えます。
注意 : アプリケーションによっては、8 バイト整列を前提としているものがあり、その場合は /unaligned フラグを使用すると正常に動作しません。Internet Explorer はその一例です。
全ページ ヒープ割り当てのコミットされないページ
中核となる全ページ ヒープの実装では、1 ページより小さな割り当てに対して、2 つのページがコミットされます。1 つのページはユーザー割り当てに使用され、もう 1 つのページは、バッファの末尾でアクセス不可能にされます。
バッファ末尾のオーバーランは、コミットされたアクセス不可能なページではなく、予約された仮想空間のゾーンを使用して検出できます。アクセス違反例外は、プロセスがその予約仮想空間に触れたときに発生します。この方法で、メモリ消費を半分に削減することができます。現在の計画では、Microsoft が徹底的に評価を行った後、この方法を古い方法と置き換えようとしています。ヒープ ユーザーは、変更を意識する必要はまったくありません。予約仮想空間機能を使用するには、次のコマンドを使用します。
pageheap /enable imagename /full /decommit
アプリケーション開始時に自動的にデバッガをアタッチ
アプリケーションの中には、コマンド プロンプトからの起動が難しいものや、ほかのプロセスから起動されるものがあります。そのようなアプリケーションの場合は、起動時に必ずデバッガが自動的にアタッチされるように設定できます。これは、そのプロセスでページ ヒープが有効化されていて、ヒープのエラーが発生した場合に便利です。プロセスの開始時にデバッガを自動的にアタッチするには、次のコマンドを使用します。
pageheap /enable imagename /debug
ページ ヒープがサポートする複数のオプション
ページ ヒープ マネージャは、以下のように、複数のオプションを同時にサポートします。
pageheap /enable iexplore.exe /dlls jscript.dll vbscript.dll /size 40 56 /random 3
このコマンドは、jscript.dll または vbscript.dll で iexplore.exe によって実行された割り当て、および 40 ~ 50 バイトの範囲内にあるすべてのブロック、さらにその他すべての割り当てを 3% の確率で、全ページ ヒープに割り当てます。
ページ ヒープの無効化
すべての種類のページ ヒープ検証を無効化するには、次のコマンドを使用します。
pageheap /disable iexplore.exe
システム全体のページ ヒープ検証を無効化するには、次のコマンドを使用します。
gflags -r -hpa
ページ ヒープ エラーのデバッグ
ページ ヒープによって検出可能なバグのタイプについて、先のセクションで説明しました。このセクションでは、デバッグを簡単にするための、内部データ構造およびデバッガ拡張機能について説明します。また、よくあるデバッグのシナリオも紹介します。
ページ ヒープ ブロックの構造
バッファ末尾のオーバーラン バグの検出に全ページ ヒープが有効化されている場合、アプリケーションがページ末尾以降のメモリにアクセスしようとするとアクセス違反が発生します。このようなエラーでは、現在のスタック トレースがエラーを起こしたコードを直接ポイントしているため、デバッグが簡単です。標準ページ ヒープが使用されている場合、または整列の理由によりバッファ末尾の小さなフィル パターン内で破損が発生している場合は、ブロックが解放されたときにだけ破損が検出されます。このような場合、問題を特定するには、さらに別のデバッグが必要となります。全ページ ヒープでも標準ページ ヒープでも、ページ ヒープ マネージャはすべての割り当ての先頭にヘッダを配置します。このヘッダには、所有ヒープ、ユーザー要求サイズ、および場合によっては割り当てのスタックトレースといった、貴重な情報が含まれています。以下の図に、全ページ ヒープ ブロックおよび標準ページ ヒープ ブロックの構造を示します。
情報ブロックの構造は以下のとおりです。
DPH_BLOCK_INFORMATION |
|
---|---|
ULONG |
StartStamp; |
PVOID |
Heap; |
SIZE_T |
RequestedSize; |
SIZE_T |
ActualSize; |
LIST_ENTRY |
FreeQueue; |
PVOID |
StackTrace; |
ULONG |
EndStamp; |
Heap フィールドには所有ヒープが格納されます。RequestedSize は、ユーザーが要求したブロック サイズです。スタックを取得するには、StackTrace アドレスで dds デバッガ コマンドを発行します。Windows 2000 でこの構造のダンプを取得するには、SP1 を使用して dd デバッガ コマンドを実行するのが最適な方法です。
dd DPH_BLOCK_INFORMATION ADDRESS
StackTrace フィールドには常に非ヌル値が含まれるわけではありません。スタックトレース検出は x86 プラットフォームでしかサポートされず、また x86 コンピュータでも、スタックトレース検出アルゴリズムは完全に信頼できるものではありません。ブロックが割り当てられたブロックである場合、スタックトレースは割り当て時点の情報を示します。ブロックが解放されている場合、スタックトレースは解放時点の情報を示します。
ページ ヒープ デバッガ拡張機能
ページ ヒープ デバッガ拡張機能は、!heap 拡張機能 (NT ヒープ デバッガ拡張機能) の一部です。次のコマンドで簡単なヘルプが表示されます。
!heap -?
詳細な情報を表示するには、次のコマンドを使用します。
!heap -p -?
拡張機能は、プロセスにページ ヒープが有効化されている場合、それ自身で検出を実行できるほど高機能ではありません。ユーザーは、ページ ヒープが有効化されていることを認識し、!heap -p のプレフィックスを付けてコマンドを実行する必要があります。
!heap -p - プロセス内で作成されたすべての全ページ ヒープのアドレスをダンプします。
!heap -p -h ADDRESS-OF-HEAP - ADDRESS-OF-HEAP 箇所の全ページ ヒープをすべてダンプします。
!heap -p -a ADDRESS - ADDRESS 箇所にヒープ ブロックがあるかどうかの判断を試みます。この値は、ブロック先頭のアドレスとは限りません。このコマンドは、メモリ領域の性質について情報がないときに便利です。
!heap -p -t NUMBER - ヒープ割り当てのヘビー ユーザーの最初の NUMBER をダンプします。このコマンドは、リーク分析に便利です。
一般的なデバッグのシナリオ
よく発生するいくつかのエラーのシナリオについて説明します。これらのシナリオの中には、全体の状況を把握するために、さらなる調査が必要な場合もあります。
アクセス不可能ページでのアクセス違反
これは、全ページ ヒープが有効化されているときに、テスト対象のアプリケーションがバッファの末尾を越えてヒープにアクセスしようとした場合に発生します。また、解放されているブロックにアプリケーションが触れたときにも発生します。例外が発生したアドレスの性質を理解するには、デバッガで次のコマンドを実行します。
!heap -p -f ADDRESS-OF-AV
破損ブロック メッセージ
割り当て、ユーザー解放、実際の解放といった、割り当て持続期間中のいくつかの時点で、ページ ヒープ マネージャは、ブロックのフィル パターンがすべて変更されずに残っており、ブロック ヘッダのデータに一貫性があることを確認します。そうなっていない場合は、"Page heap: block @ ADDRESS is corrupted" というメッセージが送られます。
これが全ページ ヒープ ブロックの場合 (すべての割り当てに全ページ ヒープが有効化されているとわかっている場合など) は、次のコマンドを使ってブロックの性質を調べることができます。
!heap -p -f ADDRESS
これが標準ページ ヒープ ブロックの場合は、ブロック ヘッダの開始アドレスを判断する必要があります。これを実行するには、レポートされたアドレス以降の 30 ~ 40 バイトをダンプし、ブロック ヘッダの開始と終了のパターン (ABCDAAAA, ABCDBBBB, ABCDAAA9, ABCDBBBA) を探します。先の「ページ ヒープ ブロックの構造」を参照してください。
ヘッダには、エラーを理解するために必要なほとんどの情報が含まれています。特に、開始と終了のパターンを見ることで、そのブロックが割り当てられているか解放されているか、また標準ページ ヒープ ブロックか全ページ ヒープ ブロックかがわかります。情報は、問題となっている呼び出しと慎重に照合する必要があります。たとえば、HeapFree への呼び出しが、ブロックのアドレスに 4 バイトを足したアドレスで実行されている場合は、破損ブロック メッセージを受け取ることになります。ブロック ヘッダに問題がないように見えても、ヘッダ末尾以降の最初のバイト (0xDCBAxxxx 以降の最初のバイト) が、呼び出しと異なるアドレスを持っています。
注意 : 全ページ ヒープが有効化されていても、"数バイトのずれ" エラーがあると、整列のために小さなフィル パターンが割り当ての末尾に必要となるため、破損ブロック メッセージが送られることがあります。先の「整列されていない割り当て」を参照してください。
特別なフィル ポインタ
ページ ヒープ マネージャは、ユーザー割り当てを、カーネル ポインタに似た値で埋めます。これは、ブロックが解放された (フィル値が F0) 場合、およびブロックが割り当てられた場合、しかしブロックをゼロにするように要求されなかった (フィル値は、標準ページ ヒープでは E0、全ページ ヒープでは C0) 場合に発生します。非ゼロの割り当ては、malloc/new users によくあります。0xF0F0F0F0、0xE0E0E0E0、0xC0C0C0C0 といったアドレスで読み書きが試行されたときにエラー (アクセス違反) が発生した場合は、テスト アプリケーションが以下のいずれかのケースに当てはまったと考えられます。
0xF0F0F0F0 での読み書きは、ブロックが解放後に使用されたことを示します。どのブロックがその原因となっているか調査する必要があります。エラーのスタックトレース、およびスタックの関数のコードを検査してください。割り当てが解放後に利用可能になることに関して誤った想定をしている関数を探します。
0xE0E0E0E0 または 0xC0C0C0C0 での読み書きは、アプリケーションが割り当てプロパティを初期化しなかったことを意味します。現在のスタックトレース内の関数のコードを検査してください。一例として、次のようなエラーが考えられます。
テスト アプリケーション内で、アドレス 0xE0E0E0E0 で HeapFree を実行している間にアクセス違反がありました。アプリケーションは、構造にメモリを割り当て、構造を初期化せず、構造要素を割り当て解除しようとしました。デストラクタは、初期化された正しい値でなく、0xE0E0E0E0 を含む、構造内の初期化されていないポインタの 1 つを使用しました。
PageHeap ユーザー ガイド
700 KB
Microsoft Word file