デバッグメッセージの読み取りとフィルタリング

最近、なぜか唐突に和訳の楽しさに目覚めたので、URLカテゴリで英語の記事を紹介するとともに日本語に訳すことがあるかもしれない。


というわけでDbgPrintExの働きについての文書、Reading and Filtering Debugging Messagesで和訳の練習。これで休日丸1日かかった。


ちなみに中の人はTOEIC200点台をたたき出す体たらくぶり(ただし今後の成長が見込まれるともっぱらの噂である。たぶん!)なので、読みにくい・間違っているなどを踏まえて参考として利用すること^^;


デバッグメッセージの読み取りとフィルタリング

DbgPrintEx、vDbgPrintEx、vDbgPrintExWithPrefix、KdPrintEx関数は特定の条件下でカーネルデバッガに対してメッセージを送る。この行為は、低優先度のメッセージを除去することをあなたに可能にする。


注釈:Microsoft Windows Server 2003とそれ以前のWindowsのバージョンでは、DbgPrintとKdPrint関数は無条件にカーネルデバッガに対してメッセージを送る。Windows Vistaとそれ以降のWindowsのバージョンにおいては、これらの関数は条件的に、DbgPrintExやKdPrintExのようにメッセージを送る。どちらのWindowsのバージョンも使うなら、あなたはDbgPrintEx、vDbgPrintEx、vDbgPrintExWithPrefix、KdPrintExを使うべきで、なぜならこれらの関数は、どのメッセージが送られるかの条件を制御することをあなたに可能にするからである。

デバッグメッセージのフィルタについて

  1. 各メッセージは、あなたのドライバのコードにおいてDbgPrintEx、vDbgPrintEx、vDbgPrintExWithPrefix、KdPrintExが使われて、デバッガに送ろうとする。ComponentId引数によって適切なコンポーネントを渡し、重大性もしくはこのメッセージの性質を反映するLevel引数の値を渡す。メッセージ自身はprintfのような構文を利用することによってFormatとarguments引数で渡される。
  2. 適切なコンポーネントフィルタマスクの値をセットする。各コンポーネントは異なるマスクを持つ。マスク値はそのコンポーネントのメッセージが表示されるかを示す。あなたはレジストリエディタを使用するか、カーネルデバッガを使用することによってメモリの中でレジストリコンポーネントフィルタマスクをセットすることができる。
  3. コンピュータにカーネルデバッガを接続する。DbgPrintEx、vDbgPrintEx、vDbgPrintExWithPrefix、KdPrintExからあなたのドライバにメッセージを渡すときはいつも、ComponentIdとLevelが対応するコンポーネントフィルタマスクの値と比較されその値が渡される。


完全な詳細は以下である。


注釈:DbgPrintExへのこのページにおけるすべての言及は、KdPrintEx、vDbgPrintEx、vDbgPrintExWithPrefixについても同様に当てはまる。

コンポーネント名を識別する

コンポーネントは個別のフィルタマスクを持つ。このマスクはデバッガから各コンポーネント個々のためにフィルタを設定することができる。


コンポーネントは文脈に依存する異なる方法で表される。DbgPrintExのComponentId引数においては、コンポーネント名は"DPFLTR_"接頭辞と"_ID"接尾辞である。レジストリにおいては、コンポーネントフィルタマスクはコンポーネント自身と同じような名前を持つ。デバッガにおいては、コンポーネントフィルタマスクは"Kd_"接頭辞と"_Mask"接尾辞である。


Windows Driver Kit (WDK)のDpfilter.hヘッダファイルで(DPFLTR_XXXX_IDというフォーマットで)命名されたコンポーネントの完全なリストがある。これらの大部分のコンポーネント名はWindowsMicrosoftによって書かれたドライバのために予約されている。


そこの6つのコンポーネント名はハードウェアベンダに依存しないで予約された。Windowsコンポーネントの出力とあなたのドライバの出力を混ぜるべきではなく、あなたは以下のコンポーネント名のひとつを使用するべきである。

コンポーネント ドライバタイプ
IHVVIDEO ビデオドライバ
IHVAUDIO オーディオドライバ
IHVNETWORK ネットワークドライバ
IHVSTREAMING カーネルストリーミングドライバ
IHVBUS バスドライバ
IHVDRIVER ほかのあらゆるドライバ


たとえば、もしあなたがビデオドライバを書いたら、あなたはDbgPrintExのComponentId引数はDPFLTR_IHVVIDEO_IDを使い、レジストリにおいてはIHVVIDEOと名づけられた値を使い、デバッガにおいてはKd_IHVVIDEO_Maskを参照する。


Windows Vistaとそれ以降のWindowsのバージョンにおいては、DbgPrintとKdPrintが送ったすべてのメッセージはDEFAULTコンポーネントと関連付けられている。

正しいLevelの選択

DbgPrintEx関数のLevel引数はDWORD型である。それは重大性ビットフィールド(importance bit field)を決定することに使用される。Level引数とこのビットフィールドの間の関係はレベルの大きさに依存する

  • もしLevelが0と31の間の数に等しければ、重大性ビットフィールドはビットシフトのように解釈される。重大性ビットフィールドは 1 << Levelの値をセットする。たとえば、もしあなたがLevelで0と31の間の値を選択すると、ビットフィールドは完全にひとつのビットセットを持つ。もしLevelが0なら、ビットフィールドは0x00000001と同義である。もしLevelが31なら、ビットフィールドは0x80000000と同義である。
  • もしLevelが32と0xFFFFFFFFの間の数に含まれるなら、重大性ビットフィールドはLevel自身の値をセットする。


このように、もしあなたが0x00004000をビットフィールドにセットしたいなら、あなたは0x00040000か単純に14のようにLevelを指定することができる。このシステムの結果、いくらかのビットフィールドの値―完全に0であるビットフィールドも含まれる―は提供することができない。


以下の定数はLevelの値を設定するために使用することができる。それらはDpfilter.h WDKヘッダファイルにおいて定義されている。

#define   DPFLTR_ERROR_LEVEL     0
#define   DPFLTR_WARNING_LEVEL   1
#define   DPFLTR_TRACE_LEVEL     2
#define   DPFLTR_INFO_LEVEL      3
#define   DPFLTR_MASK    0x8000000


Level引数を使うためのひとつの簡単なやり方は、いつも0と31の間の値―DPFLTR_XXXX_LEVELによって与えられる意味と0、1、2、3を使い、なんであれあなたの選択する意味はほかのビットを使うことである。


ほかのLevel引数を使うための簡単なやり方は、いつも明示的なビットフィールを使用することである。もしあなたがこの方法を選んだら、あなたはあなたのビットフィールドとDPFLTR_MASK値のORビット演算を使うことを望むかもしれない。この値は32未満の値を誤って使用することがないことを確実にする。


Windowsの使うメッセージレベルのやり方とあなたのドライバの互換性を持つなら、あなたは、もし深刻なエラーが発生したとき重大性ビットフィールドの最下位ビット(0x1)だけを設定すべきである。もしあなたが32未満の値のLevelを使うと、この値はDPFLTR_ERROR_LEVELに相当する。もし重大性ビットフィールドをセットすると、あなたのメッセージは、誰かがあなたのドライバを実行しているコンピューターにカーネルデバッガを接続するときはいつでも表示されるだろう。


適切な状況で警告、トレース、情報レベルを使用する。あなたは、何でもあなたが役立つとわかるあらゆる目的のためにほかのビットを使用することができる。これは選択的に見せたり隠したりすることができるメッセージタイプの多様性の幅を持つたせることをあなたに可能にする機能である。


Windows Vistaとそれ以降のWindowsのバージョンにおいては、DbgPrintとKdPrintによって送られるすべてのメッセージは、DPFLTR_INFO_LEVELと等しいLevelでのDbgPrintExとKdPrintExメッセージのように振舞う。言い換えると、これらのメッセージはそれらの重大性ビットフィールドの3番目のビットがセットされる。

コンポーネントフィルタマスクを設定する

コンポーネントフィルタマスクを設定する2つの方法がある:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter
  • もしカーネルデバッガがアクティブなら、それはシンボルKd_XXXX_Mask(XXXXは要求したコンポーネント名)に保存されたアドレスを逆参照することによってコンポーネントフィルタマスク値にアクセスできる。あなたはdd(Display DWORD)コマンド、もしくはed(Enter DWORD)コマンドで新しいコンポーネントフィルタマスクを入力することで、WinDbgやKDによってこのマスクの値を表示できる。もしそれらが曖昧なシンボルの危険性があるなら、あなたはnt!Kd_XXXX_Maskとしてこのシンボルを特定したいかもしれない。


フィルタマスクはレジストリに保存され起動している間効果を発する。フィルタマスクはデバッガによって作成されると即座に効果を発し、Windowsが再起動するまで持続する。デバッガはレジストリにおいてセットされた値を上書きすることができるが、コンポーネントフィルタマスクは、もしコンピュータが再起動したらレジストリにおいて指定された値に戻る。


WIN2000と呼ばれる常にシステムワイドなマスクがある。デフォルトでは、このマスクは0x1に等しいが、あなたはすべてのほかのコンポーネントのようにレジストリあるいはデバッガを通して変更できる。フィルタが実行されるとき、各コンポーネントフィルタマスクがまずORビット演算を使用することによってWIN2000マスクと結合される。とりわけ、この結合はマスクが指定されたことのないコンポーネントが0x1でデフォルトとすることを意味する。

表示するメッセージの基準

DbgPrintExがカーネルモードコードにおいて呼び出されるとき、WindowsはComponentIdによって指定されたコンポーネントのフィルタマスクとともにLevelによって指定されたメッセージの重大性ビットフィールドを比較する。


注釈:Level引数が0と31の間であるとき、重大性ビットフィールドは1 << Levelに等しいことを思い出そう。しかしLevel引数が32以上のとき、重大性ビットは単純にLevelと等しい。


Windowsは重大性ビットフィールドとコンポーネントフィルタマスクでAND操作を行う。もし結果が非ゼロなら、メッセージをデバッガに送る。

最後に起動の前に、あなたがDebug Print Filterキーにおいて以下の値を作成したとする:

  • IHVVIDEO、値はDWORD 0x2に等しい
  • IHVBUS、値はDWORD 0x7FFに等しい


現在、あなたはカーネルデバッガに以下のコマンドを発行した:

kd> ed Kd_IHVVIDEO_Mask 0x8
kd> ed Kd_IHVAUDIO_Mask 0x7


この時点で、IHVVIDEOコンポーネントは0x8のフィルタマスクを持ち、IHVAUDIOコンポーネントは0x7のフィルタマスクを持ち、IHVBUSコンポーネントは0x7FFのフィルタマスクを持つ。


しかしながら、これらのマスクはORビット演算を用いてWIN2000システムワイドマスク(これは通常0x1に等しい)と自動的に結合されるため、IHVVIDEOマスクは実質的には0x9と等しい。フィルタマスクがセットされなかったすべて(たとえば、IHVSTREAMINGやDEFAULT)のコンポーネントは0x1のフィルタマスクを持つ。


現在、さまざまなドライバにおいて、以下の関数呼出が行われたとする:

DbgPrintEx( DPFLTR_IHVVIDEO_ID,  DPFLTR_INFO_LEVEL,   "First message.\n");
DbgPrintEx( DPFLTR_IHVAUDIO_ID,  7,                   "Second message.\n");
DbgPrintEx( DPFLTR_IHVBUS_ID,    DPFLTR_MASK | 0x10,  "Third message.\n");
DbgPrint( "Fourth message.\n");


一番目のメッセージはDPFLTR_INFO_LEVELに等しいLevel引数を持ち、それは3である。この値は32未満であるから、ビットシフトのように扱われ、0x8の重大性ビットフィールドという結果になる。この値はAND操作を使用することによって0x9の実質的なIHVVIDEOコンポーネントフィルタマスクと結合され、非ゼロの結果を得る。このようにして一番目のメッセージはデバッガに転送される。


2番目のメッセージは7に等しいLevel引数を持つ。この場合もまた、この値はビットシフトのように扱われ、0x80の重大性ビットフィールドという結果になる。この対はAND操作を使用することによって0x7のIHVAUDIOコンポーネントフィルタマスクと結合され、ゼロの結果を得る。このようにして2番目のメッセージは転送されない


3番目のメッセージはDPFLTR_MASK | 0x10と等しいLevel引数を持つ。この値は31より大きく、その結果重大性ビットフィールドはLevelの値―言い換えると、0x80000010と等しい値をセットする。この値は、AND操作を使用することによって0x7FFのIHVBUSコンポーネントフィルタマスクと結合され、非ゼロの結果を得る。このようにして3番目のメッセージはデバッガに転送される。


4番目のメッセージはDbgPrintEx関数の代わりにDbgPrint関数によってメッセージが渡される。Windows2003とそれ以前のWindowsのバージョンにおいては、DbgPrintによって渡されたメッセージは常に転送される。Windows Vistaとそれ以降のWindowsのバージョンにおいては、DbgPrintによって渡されたメッセージは常にデフォルトフィルタが与えられる。重大性ビットフィールドは1 << DPFLTR_INFO_LEVELと等しく、それは0x0000008である。この関数のためのコンポーネントはDEFAULTである。あなたはDEFAULTコンポーネントフィルタマスクをセットしていないため、それは0x1の値を持つ。このマスクはAND操作を使用することによって重大性ビットフィールドと結合されるとき、結果はゼロである。このようにして4番目のメッセージは転送されない

DbgPrintバッファとデバッガ

DbgPrint、DbgPrintEx、vDbgPrintEx、vDbgPrintExWithPrefix、KdPrint、KdPrintEx関数がデバッガにメッセージを転送するとき、フォーマットされた文字列はDbgPrintバッファに送られる。このバッファの内容は、あなたがGFlagsのBuffer DbgPrint Outputオプションを使用することによって表示を無効にしない限り、即座にデバッガコマンドウィンドウに表示される。


もしあなたがこの表示を無効にしたなら、あなたは!dbgprint拡張コマンドを使用することによってのみDbgPrintバッファの内容を見ることができる。デバッガ拡張のための情報は、Debugging Tools for NT-Based Operating Systemsを参照のこと。


どのようなDbgPrint、DbgPrintEx、vDbgPrintEx、vDbgPrintExWithPrefix、KdPrint、KdPrintExへの一回の呼び出しも情報の512バイトだけが転送される。あらゆる512バイトより長い出力は失われる。DbgPrintバッファ自身は、フリービルドのWindowsでは4KBまで、チェックビルドのWindowsでは32KBまでのデータを保持することができる。Windows Server 2003とそれ以降のWindowsのバージョンでは、あなたはDbgPrintバッファのサイズを変更するためにKDbgCtrlツールを使うことができる。このツールはDebugging Tools for Windowsの一部である。


もしComponentIdとLevelの値が原因でメッセージが除去されたなら、それはデバッグ接続を超えて転送されない。したがって、デバッガにおいてこのメッセージを表示させるための方法は無い。

まとめ

ソビエトロシアではデバッグメッセージがあなたを取り除く!