Share via


タグを使用したオブジェクト参照トレース

カーネル オブジェクト は、Windows カーネルによってシステム メモリに実装されるプリミティブ データ オブジェクトです。 デバイス、ドライバー、ファイル、レジストリ キー、イベント、セマフォ、プロセス、スレッドなどの各種エンティティを表します。

ほとんどのカーネル オブジェクトは永続しません。 カーネル モード ドライバーで使用している非永続カーネル オブジェクトが Windows によって削除されないように、そのドライバーではカーネル オブジェクトへのカウントされた参照を取得します。 オブジェクトを必要としなくなったドライバーは、そのオブジェクトへの参照を解放します。

ドライバーでオブジェクトへのすべての参照を解放しないと、そのオブジェクトの参照カウントがゼロにならないので、そのオブジェクトがオブジェクト マネージャーによって削除されることはありません。 オペレーティング システムが再起動するまで、リークしたリソースを再利用することはできません。

ドライバーによるオブジェクトの参照が不足していると、別の種類の参照エラーが発生します。 この場合は、ドライバーがオブジェクトに対して実際に保持している参照よりも多くの参照が解放されます。 このエラーは、他のクライアントがオブジェクトにアクセスしようとしているにもかかわらず、オブジェクト マネージャーによってそのオブジェクトが早い段階で削除される原因となることがあります。

カーネル オブジェクトのリークや参照不足は、追跡が困難なバグになる可能性があります。 たとえば、プロセス オブジェクトやデバイス オブジェクトには数万の参照があることが考えられます。 このような状況では、オブジェクト参照バグの発生源の特定が困難になることがあり得ます。

Windows 7 以降の Windows では、オブジェクト参照のタグを指定してこのようなバグの発見を容易にすることができます。 次のルーチンは、カーネル オブジェクトに対する参照の取得と解放にタグを関連付けます。

ObDereferenceObjectDeferDeleteWithTag

ObDereferenceObjectWithTag

ObReferenceObjectByHandleWithTag

ObReferenceObjectByPointerWithTag

ObReferenceObjectWithTag

たとえば、Windows 7 以降の Windows で使用できる ObReferenceObjectWithTagObDereferenceObjectWithTag は、Windows 2000 以降のWindows で使用できる ObReferenceObject ルーチンと ObDereferenceObject ルーチンの機能を強化したルーチンです。 これらの機能強化ルーチンを使用すると、4 バイトのカスタム タグ値を入力パラメーターとして指定できます。 Windows デバッグ ツールを使用すると、呼び出しごとにタグ値が記述されたオブジェクト参照トレースを調べることができます。 ObReferenceObjectObDereferenceObject では、呼び出し元がカスタム タグを指定することはできませんが、Windows 7 以降の Windows では、これらのルーチンによって既定のタグ (タグ値 "Dflt") がトレースに追加されます。 したがって、ObReferenceObject または ObDereferenceObject の呼び出しには、タグ値 "Dflt" を指定する ObReferenceObjectWithTag または ObDereferenceObjectWithTag の呼び出しと同じ効果があります (プログラムでは、このタグ値は 0x746c6644 または 'tlfD' として表示されます)。

表面化しないオブジェクト リークや参照不足を追跡するには、特定のオブジェクトへの参照カウントをインクリメントする ObReferenceObjectXxxWithTag 呼び出しとデクリメントする ObDereferenceObjectXxxWithTag 呼び出しのペアを特定します。 このペアでのすべての呼び出しに共通で使用するタグ値 ("Lky8" など) を選択します。 ドライバーでオブジェクトの使用を完了した後は、デクリメント回数とインクリメント回数が正確に一致している必要があります。 これらの数値が一致しない場合、そのドライバーにはオブジェクト参照のバグがあります。 デバッガーを使用すると、タグ値ごとにインクリメント回数とデクリメント回数を比較し、それらが一致するかどうかを確認できます。 この機能により、参照カウントが一致しない原因を短時間で特定できます。

Windows デバッグ ツールでオブジェクト参照トレースを確認するには、カーネル モード デバッガーの拡張機能である !obtrace を使用します。 オブジェクト参照トレースが有効であれば、拡張機能 !obtrace を使用してオブジェクト参照タグを表示できます。 既定では、オブジェクト参照トレースは無効になっています。 オブジェクト参照トレースを有効にするには、グローバル フラグ エディター (Gflags) を使用します。 Gflags の詳細については「オブジェクト参照トレースの構成」を参照してください。

拡張機能 !obtrace の出力には、次の例のように "Tag" 列があります。

0: kd> !obtrace 0x8a226130
Object: 8a226130
 Image: leakyapp.exe
Sequence   (+/-)   Tag    Stack
--------   -----   ----   --------------------------------------------
      36    +1     Dflt      nt!ObCreateObject+1c4
                             nt!NtCreateEvent+93
                             nt!KiFastCallEntry+12a

      37    +1     Dflt      nt!ObpCreateHandle+1c1
                             nt!ObInsertObjectEx+d8
                             nt!ObInsertObject+1e
                             nt!NtCreateEvent+ba
                             nt!KiFastCallEntry+12a

      38    -1     Dflt      nt!ObfDereferenceObjectWithTag+22
                             nt!ObInsertObject+1e
                             nt!NtCreateEvent+ba
                             nt!KiFastCallEntry+12a

      39    +1     Lky8      nt!ObReferenceObjectByHandleWithTag+254
                             leakydrv!LeakyCtlDeviceControl+6c
                             nt!IofCallDriver+63
                             nt!IopSynchronousServiceTail+1f8
                             nt!IopXxxControlFile+6aa
                             nt!NtDeviceIoControlFile+2a
                             nt!KiFastCallEntry+12a

      3a    -1     Dflt      nt!ObfDereferenceObjectWithTag+22
                             nt!ObpCloseHandle+7f
                             nt!NtClose+4e
                             nt!KiFastCallEntry+12a
 
--------   -----   ----   --------------------------------------------
References: 3, Dereferences 2
Tag: Lky8 References: 1 Dereferences: 0 Over reference by: 1

この例の最後の行は、"Lky8" タグに関連する参照カウントと参照解放カウントが一致せず、この不一致によって 1 回の参照過剰 (リーク) が発生していることを示しています。

結果が参照不足である場合、!obtrace 出力の最後の行は次のようになります。

Tag: Lky8 References: 1 Dereferences: 2 Under reference by: 1

既定では、オペレーティング システムは、オブジェクトが解放された後、そのオブジェクト参照トレースを削除することでメモリ使用量を抑えます。 システムによってオブジェクトが解放された後もメモリにトレースを保持しておくと、参照不足の追跡で便利です。 この目的のために、Gflags ツールには "永続" オプションが用意されています。このオプションを使用すると、コンピューターをシャットダウンしてから再起動しても、メモリにトレースが保持されています。

オブジェクト参照トレースは Windows XP で導入されました。 当初はトレースにタグが記述されていなかったので、オブジェクト参照のバグを特定するには、利便性に欠ける手法を使用する必要がありました。 オブジェクトの種類別に選択したオブジェクト グループの参照をデバッガーで追跡できます。 オブジェクトの参照と参照解放のさまざまな参照元を識別できる唯一の方法は、呼び出し履歴を比較することでした。 上記の !obtrace の例で扱っているスタックは 5 つにすぎませんが、プロセス (EPROCESS) オブジェクトなどの特定の種類のオブジェクトに対する参照と参照解放の回数は数千回に達することもあります。 検査するスタックの数が数千にもなると、タグを使用せずにオブジェクト リークや参照不足の原因を特定することは困難と考えられます。