スタック ベースのエラー挿入

注: この機能を有効にする手順は、Windows 8 用 WDK にのみ適用されます。 Windows 8.1 の場合、この機能はドライバー検証ツールに統合されています。 Windows 8.1 を実行しているコンピューターでは、体系的な低リソースのシミュレーション オプションを使用します。

スタックベースの障害挿入オプションは、カーネル モード ドライバーにリソース障害を挿入します。 このオプションでは、特別なドライバー KmAutoFail.sys をドライバー検証ツールと組み合わせて使用し、ドライバーのエラー処理パスを通過します。 こうしたパスのテストは、これまで非常に困難でした。 スタック ベースの障害挿入オプションは、予測可能な方法でリソース エラーを挿入します。これにより、検出された問題が再現可能になります。 エラー パスは簡単に再現できるため、これらの問題に対する修正の確認も容易になります。

エラーの根本原因を特定するために、デバッガー拡張機能が提供されており、どのエラーがどの順番で挿入されたのかを正確に把握できます。

特定のドライバーでスタック ベースの障害挿入オプションを有効にすると、そのドライバーからカーネルと Ndis.sys へのいくつかのコールをインターセプトします。 スタック ベースの障害挿入では、呼び出し履歴 (具体的には、有効になっているドライバーから取得された呼び出し履歴の部分) が確認されます。 そのスタックを初めて見た場合、その呼び出しのセマンティクスに従って呼び出しが失敗します。 それ以外では、以前にその呼び出しが確認された場合は、そのまま渡されます。 スタック ベースの障害挿入には、ドライバーを複数回読み込んでアンロードできるという事実に対処するためのロジックが含まれています。 ドライバーが異なるメモリ位置に再読み込みされた場合でも、呼び出し履歴が同じであることを認識します。

このオプションをアクティブにする

テスト コンピューターにドライバーを展開するときに、1 つ以上のドライバーのスタック ベースのエラー挿入機能をアクティブ化できます。 ドライバー パッケージ プロジェクトのドライバー検証ツールのプロパティを構成するときに、[スタック ベースのエラー挿入] オプションを選択できます。 スタック ベースの障害挿入オプションをアクティブ化または非アクティブ化するには、コンピューターを再起動する必要があります。 テスト ユーティリティを実行して、テスト コンピューターでドライバー検証ツールとこの機能を有効にすることもできます。

重要: テスト コンピューターでスタック ベースのエラー挿入をアクティブ化する場合は、[低リソース シミュレーション] も選択しないようにしてください。

  • [ドライバー検証ツールの使用] プロパティ ページ

    1. ドライバーパッケージのプロパティ ページを開きます。 「ソリューション エクスプローラー」でドライバー パッケージ プロジェクトを右クリックし、[プロパティ] を選択します。
    2. ドライバー パッケージのプロパティ ページで、[構成プロパティ][ドライバーのインストール][ドライバーの検証ツール] の順にクリックします。
    3. [ドライバー検証ツールを有効にする] を選択します。 テスト コンピューターでドライバー検証ツールを有効にすると、コンピューター上のすべてのドライバー、ドライバー プロジェクトのみ、または指定したドライバーのリストに対してドライバー検証ツールを有効にすることができます。
    4. スタック ベースの障害インジェクタで、(チェック) スタック ベースの障害挿入を選択します。
    5. [適用] または [OK] をクリックします。
    6. 詳細については、「テスト コンピューターへのドライバーの展開」をご参照ください。 このオプションをアクティブにするには、テスト コンピューターを再起動する必要があります。
  • ドライバー検証ツールの有効化と無効化テストの使用

    1. ユーティリティ テストを実行して、ドライバー検証ツールを有効にすることもできます。 Visual Studio を使用して実行時にドライバーをテストする方法に関するページで説明されている手順に従います。 [すべてのテスト] \ [ドライバー検証ツール] テスト カテゴリで、[ドライバー検証ツールを有効にする (再起動が必要になる可能性があります)] テストと [ドライバー検証ツールを無効にする (再起動が必要になる可能性があります)] テストを選択します。

    2. ドライバー検証ツールのオプションを選択するには、[ドライバー テスト グループ] ウィンドウで [ドライバー検証ツールを有効にする (再起動が必要になる可能性があります)] テストの名前をクリックします。

    3. [スタック ベースの障害挿入] を選択 (チェック) します。

    4. これらのテストをテスト グループに追加したら、テスト グループを保存できます。 スタック ベースのエラー挿入を有効にするには、テスト用に [構成したコンピューターでドライバー検証ツールを有効にする (再起動が必要になる可能性があります)] テストを実行します。

      ドライバー検証ツールを非アクティブ化するには、ドライバー検証ツールの [無効化 (再起動が必要になる可能性があります)] テストを実行します。

スタック ベースの障害挿入オプションの使用

スタック ベースの障害挿入を使用してテストする際の重要な考慮事項の 1 つは、検出されたほとんどのバグがバグ チェックになるということです。 これは、ドライバーがブートロードされたドライバーである場合、多少面倒なことになる可能性があります。 このため、ドライバー検証ツールが無効になっている場合、スタック ベースのエラー挿入が自動的に無効になります。 つまり、!verifier –disable コマンドを使用してドライバー検証ツールを無効にすることで、デバッガーから起動時にスタック ベースのエラー挿入を無効にすることができます。

可能な場合は、スタック ベースのエラー挿入を使用した最初のテストで、起動時に読み込まれないようにドライバーを設定します。 その後、いくつかの単純なロード テストとアンロード テストを実行できます。 スタック ベースのエラー挿入によって検出されたバグの多くは、初期化中またはクリーンアップ中に発生します。 ドライバーの読み込みとアンロードを繰り返し行うのが、これらを見つける良い方法です。

ロード テストやアンロード テストを成功させるために必要な修正を行った後は、IOCTL ベースのテスト、完全な機能テスト、最後にストレス テストに進むことができます。 一般に、このテストの進行に従う場合、ほとんどのコード パスは、この前に既に実行されているため、ストレス テスト中に多くの新しい問題を発見することはできません。

スタック ベースの障害挿入 (SBFI) デバッガー拡張機能の使用

スタック ベースの障害挿入で見つかった問題のほとんどは、バグ チェックになります。 これらのコード バグの原因を特定するために、WDK にはスタック ベースのエラー挿入デバッガー拡張機能と必要なシンボルが用意されています。 インストール手順により、両方がデバッガー システムにインストールされます。 既定の場所は C:\Program Files (x86)\Windows Kits\8.0\Debuggers\<arch> です。

デバッガー拡張機能を実行する方法

  • デバッガーのコマンド プロンプトで、次のコマンドを入力します: !<path>\kmautofaildbg.dll.autofail. たとえば、デバッガー拡張機能が c:\dbgext にインストールされ、kmautofail.pdb がシンボル パスにあると仮定すると、次のコマンドを入力します。

    !c:\dbgext\kmautofaildbg.dll.autofail
    

これにより、挿入された最新のエラーからの呼び出し履歴を示す情報がデバッガーにダンプします。 各エントリは、実際のテスト実行から取得された次のようになります。 次の例では、Mydriver.sys でスタック ベースの障害挿入が有効になっています

Sequence: 2, Test Number: 0, Process ID: 0, Thread ID: 0
                 IRQ Level: 2, HASH: 0xea98a56083aae93c
 0xfffff8800129ed83 kmautofail!ShimHookExAllocatePoolWithTag+0x37
 0xfffff88003c77566 mydriver!AddDestination+0x66
 0xfffff88003c5eeb2 mydriver!ProcessPacketDestination+0x82
 0xfffff88003c7db82 mydriver!ProcessPacketSource+0x8b2
 0xfffff88003c5d0d8 mydriver!ForwardPackets+0xb8
 0xfffff88003c81102 mydriver!RoutePackets+0x142
 0xfffff88003c83826 mydriver!RouteNetBufferLists+0x306
 0xfffff88003c59a76 mydriver!DeviceSendPackets+0x156
 0xfffff88003c59754 mydriver!ProcessingComplete+0x4a4
 0xfffff88001b69b81 systemdriver2!ProcessEvent+0x1a1
 0xfffff88001b3edc4 systemdriver1!CallChildDriver+0x20
 0xfffff88001b3fc0a systemdriver1!ProcessEvent+0x3e
 0xfffff800c3ea6eb9 nt!KiRetireDpcList+0x209
 0xfffff800c3ea869a nt!KiIdleLoop+0x5a

出力の先頭にあるシーケンス番号は、挿入された障害の数をカウントします。 この例では、このテストの実行中に挿入された 2 番目の障害を示します。 プロセス ID は 0 なので、これがシステム プロセスでした。 IRQL は 2 なので、ディスパッチ レベルで呼び出されます。

スタックから、KmAutoFail はスタック ベースの障害挿入ドライバーです。 KmAutoFail 関数名は、Mydriver.sys からのどの関数呼び出しがインターセプトされ、障害が挿入されたかを示します。 ここで、失敗した関数は ExAllocatePoolWithTag でした。 Ntoskrnl.sys または Ndis.sys への呼び出しをインターセプトする KmAutoFail 内のすべての関数は、この名前付け規則を使用します。 次に、ドライバーがテストされている呼び出し履歴が表示されます (Mydriver.sys)。 これは、スタックの一意性を判断するために使用される呼び出し履歴の部分です。 したがって、デバッガー拡張機能によってダンプされた各エントリは、呼び出し履歴のこの部分で一意になります。 呼び出し履歴の残りの部分は、ドライバーを呼び出したユーザーを示します。 主な重要点は、ドライバーがユーザー モード (IOCTL を使用) から呼び出されるか、カーネル モード ドライバーから呼び出されるかです。

ドライバーが DriverEntry ルーチンからエラーを返した場合、通常は別のメモリ位置で再読み込み試行が行われます。 その場合、以前の場所からの呼び出し履歴には、ドライバーからのスタック情報ではなく、「ガベージ」が含まれている可能性があります。 しかし、これは問題ではありません。これは、ドライバーが挿入されたエラーを正しく処理したことを示します。

この次のエントリは、ユーザー モードからの IOCTL を使用してドライバーへの呼び出しを示します。 プロセス ID と IRQ レベルをメモします。 Mydriver.sys は NDIS フィルター ドライバーであるため、IOCTL は Ndis.sys を通過しました。 nt!NtDeviceIoControlFile はスタック上にあります。 IOCTL を使用するドライバーで実行するテストはすべて、この関数を通過します。

Sequence: 5, Test Number: 0, Process ID: 2052, Thread ID: 4588
                 IRQ Level: 0, HASH: 0xecd4650e9c25ee4
 0xfffff8800129ed83 kmautofail!ShimHookExAllocatePoolWithTag+0x37
 0xfffff88003c6fb39 mydriver!SendMultipleOids+0x41
 0xfffff88003c7157b mydriver!PvtDisconnect+0x437
 0xfffff88003c71069 mydriver!NicDisconnect+0xd9
 0xfffff88003ca3538 mydriver!NicControl+0x10c
 0xfffff88003c99625 mydriver!DeviceControl+0x4c5
 0xfffff88001559d93 NDIS!ndisDummyIrpHandler+0x73
 0xfffff88001559339 NDIS!ndisDeviceControlIrpHandler+0xc9
 0xfffff800c445cc96 nt!IovCallDriver+0x3e6
 0xfffff800c42735ae nt!IopXxxControlFile+0x7cc
 0xfffff800c4274836 nt!NtDeviceIoControlFile+0x56
 0xfffff800c3e74753 nt!KiSystemServiceCopyEnd+0x13

スタック ベースの障害挿入の結果の分析

ドライバーでテストを実行していて、突然問題が発生しました。 これはバグ チェックである可能性が最も高いですが、コンピューターが応答しなくなる可能性もあります。 どのようにして原因を見つけることができるのでしょうか。 バグ チェックであると仮定して、最初に上記の拡張機能を使用して挿入されたエラーの一覧を見つけ、次にデバッガー コマンド !analyze –v を使用します。

最も一般的なバグ チェックは、割り当ての成功をチェックしないことが原因です。 この場合、バグ チェック分析のスタックは、挿入された最後の障害とほぼ同じです。 割り当てが失敗した後のある時点で (多くの場合、非常に次の行)、ドライバーは null ポインターにアクセスします。 この種のバグは非常に簡単に修正できます。 失敗した割り当てが一覧の 1 つまたは 2 つ上にある場合がありますが、この種類は引き続き非常に簡単に見つけて修正できます。

2 番目に一般的なバグ チェックは、クリーンアップ中に発生します。 この場合、ドライバーは割り当てエラーを検出し、クリーンアップにジャンプした可能性があります。ただし、クリーンの間、ドライバーはポインターをチェックせず、もう一度 null ポインターにアクセスしました。 密接に関連するケースでは、クリーンアップを 2 回呼び出すことができます。 クリーンアップが構造体を解放した後にポインターを null に設定しない場合、クリーンアップ関数を 2 回目に呼び出すと、2 回目に構造体が解放され、バグ チェックが発生します。

コンピューターが応答しなくなる原因となるエラーは診断が難しくなりますが、デバッグ手順は似ています。 これらのエラーは、多くの場合、参照カウントまたはスピン ロックの問題によって発生します。 幸いなことに、ドライバー検証ツールは、問題につながる前に多くのスピン ロックの問題をキャッチします。 このような場合は、デバッガーに分割し、デバッガー拡張機能を使用して、スタック ベースの障害挿入によって挿入されたエラーの一覧をダンプします。 最新のエラーに関するコードを簡単に確認すると、エラーの前に取得されたが、その後にリリースされない参照カウントが表示される場合があります。 そうでない場合は、スピン ロックを待機しているドライバー内のスレッド、または明らかに間違っている参照カウントを探します。