アジョブジ星通信

進捗が出た頃に更新されるブログ。

カーネルモードドライバーをそこそこデバッグできるようにした話

はじめての仮想HID」では、デバッグ手段が手に入れられなかったという終わり方をしてしまいましたが、それなりにデバッグできるようになったので、その方法をまとめておきます。

1. KdPrintEx でログを出力する

KdPrintEx マクロを使うと、デバッグビルド時(DBG 定数があるとき)に雑に Windows にログを残すことができます。 Microsoft から出ているサンプルでもよく使われているので、これで出力したログを見れる環境を作っていきます。

これを読んで理解できれば終わりですが……。

KdPrintEx の使い方

KdPrintEx はこのように使います。

KdPrintEx((ComponentId, Level, Format, フォーマットのための可変長引数));

マクロの都合上、括弧が二重になっています。

ComponentId には、どこで発生したログかを表す値を入れますが、サードパーティーのドライバーの場合は「DPFLTR_IHVDRIVER_ID」を指定しておくのが無難そうです。

Level はいろいろな指定の仕方がありますが、基本的には次の中から選べば良いと思います。

  • DPFLTR_ERROR_LEVEL
  • DPFLTR_WARNING_LEVEL
  • DPFLTR_TRACE_LEVEL
  • DPFLTR_INFO_LEVEL

Format および、その後に続く可変長引数には、 printf 形式でログの内容を指定することができます。最後に改行を入れておかないと、出力されるログも改行されないので、前後の出力がまとまってしまうことに注意してください。

以上を踏まえて、私はこのようなヘッダーファイルを作っておき、利用することにしました。カーネルモードでのログは「カーネルからのログ」という情報しかないので、ログの文字列の最初にプロジェクト名を入れておくことで、あとでフィルターをかけることができるようにしておきます。

#define TraceEnterFunc() KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "ProjectName: Enter %s, IRQL: %d\n", __FUNCTION__, KeGetCurrentIrql()))
#define TraceErrorStatus(funcName, status) KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "ProjectName: %s returned 0x%x in %s\n", funcName, status, __FUNCTION__))

#if DBG

#define TraceInfo(msg, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "ProjectName: " msg "\n", __VA_ARGS__)
#define TraceError(msg, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "ProjectName: " msg "\n", __VA_ARGS__)

#else

#define TraceInfo(msg, ...)
#define TraceError(msg, ...)

#endif

DebugView でログを見る

ログを見るのには、 Sysinternals の DebugView を使用すると便利です。

オプション次第(/v)ですべてのログを見ることができますが、それをやるとログの収集が追い付かず、ものすごく遅延が発生していくので、先に表示するログの設定をしておきます。

  1. レジストリHKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter キーを(なければ)作成します。
  2. ログの ComponentId に対応する DWORD 値を作成します。例えば DPFLTR_IHVDRIVER_ID なら「IHVDRIVER」という名前にします。
  3. その値を、表示させたいレベルのビットマスクにします。例えば、 INFO だけを表示(実際には値が 0 である ERROR も表示される)したいならば、 1 << DPFLTR_INFO_LEVEL(3) = 0x8 を値とします。 ERROR から INFO まですべてを表示させる場合は、すべてのビットを立てるので 0xF にします。(この範囲外の Level を指定した場合はまた話が変わってくるけれど割愛)

f:id:azyobuzin:20180809013052p:plain

準備ができたら、管理者権限で DebugView を起動し、 Capture Kernel を有効にするとログが見れるようになります。 Visual Studio から配置する場合は、 WDKRemoteUser 上で起動しておけば良いと思います。

f:id:azyobuzin:20180809014549p:plain

バグなのかと思いますが、一度 Capture Kernel を有効にして、 DebugView を終了させた後、再度 DebugView を起動すると、アクセス拒否エラーが出ます。再起動しないと直らなくてつらい。。

DebugView はリモートでも使用することができます。デバッグターゲットのマシンでは、管理者権限で「Dbgview /a /k」を起動します。 /a がエージェントモード、 /k がカーネルログの有効化です。ログを見る側のマシンでは、普通に DebugView を起動し、 Computer → Connect を選択して、 IP アドレスを入れると接続できます。こちら側でも Capture Kernel を有効にする操作が必要です。

2. KDNET でリモートカーネルデバッグ環境を構築する

以前の記事(はじめての仮想HID)では、 Visual Studio からプロビジョニングを行いましたが、そのときプロビジョニングは失敗していたはずです。しかし、次へをクリックすると「Status: Configured for driver testing」と表示されていたので成功したものだと思っていましたが、実際にはドライバーの配置のみが行える状態で、デバッグが出来ない状況でした。そこで、さらに調査を続けたところ、 Windows 10 では Visual Studio からのカーネルデバッグができなくなったようです。

This feature is not available in Windows 10, version 1507 and later versions of the WDK.

Setting Up Kernel-Mode Debugging over a Network Cable in Visual Studio | Microsoft Docs

そこで、とりあえず WinDbg 経由でリモートアクセスできる程度には環境を整えてみようということでやってみました。これを行うと Visual Studio からデバッグ実行を行ったとき、 Visual Studio 上から WinDbg を操作することができるようになりますが、 Visual Studio のエディタ上で設定したブレークポイントが反映されるようなことはありません。残念。

参考情報

これを読めば、この記事を読む必要がないリストです。

NIC の確認

リモートカーネルデバッグを行うには、対応している NIC を使用しなければいけません。つまり無線では無理ということっぽいです。

対応している NIC リストは次のページにあります。

今、テスト環境として使用しているマシンの、有線の NIC のプロパティはこのようになっています。

f:id:azyobuzin:20180809021138p:plain

赤丸がベンダーID、青丸がデバイスIDです。ベンダーID 1969、デバイスID 1091 はリストにあるので使えそうです。よかった。

次に、 NIC の場所をメモしておきます。

f:id:azyobuzin:20180809021411p:plain

この場合は、左から順に「3.0.0」と記録しておきます。これが後で使う Bus Parameter になります。

Visual Studio でプロビジョニング

せっかくなので、作業の半分くらいを Visual Studio にやってもらいます。どうせ Visual Studio から配置するわけですし。

Visual Studio で Configure Devices を開き、 Add New Device すると、勝手にポートやキーが設定されていると思います。ここで、 Bus Parameter の欄に先程調べた値を入力してプロビジョニングを行うと、この後の設定が完了した後から、 Visual Studio 上で WinDbg の操作を行うことができるようになります(その必要がないなら KDNET だけ設定しても良いです)。

f:id:azyobuzin:20180809021817p:plain

プロビジョニングには失敗しますが、デバイス一覧で「Status: Configured for driver testing」となっていれば完了です。

有線での設定を行いましたが、追加で、 WinDbg にアクセスできなくていいから、デプロイだけ無線でやりたいという場合は、さらに Add New Device して、プロビジョニングを行わず、無線 NIC 側の IP アドレスだけ指定して保存すれば、無線でデプロイできるようになります。この場合でも DebugView でログを見ながらデバッグできます(私はこの構成でやっています)。

KDNET でリモートカーネルデバッグの設定を行う

  1. C:\Program Files (x86)\Windows Kits\10\Debuggers\x64 から kdnet.exe と VerifiedNICList.xml を取ってきて、ターゲットマシンの C:\KDNET にコピーします。
  2. C:\KDNET 上で管理者としてターミナルを開き、 .\kdnet <ホストマシンのIPアドレス> <プロビジョニングで設定したポート番号> を実行します。
  3. 再起動すれば完了です。

もし、プロビジョニングを行っていない場合は、ポート番号は適当な値を使用し、実行結果として表示されるキーをメモしておいてください(bcdedit /dbgsettings でも確認できます)。

これで設定は完了です。あとは WinDbg でポートとキーを指定すれば、デバッグが開始されます。

f:id:azyobuzin:20180809024545p:plain

ブレークすると本当に Windows が丸ごとフリーズするので面白いです(他に活用できてない……)。ドライバー関連だと、 !devnode 0 1 とやると、デバイスマネージャーみたいな表示が得られます。

まとめ

ひとまず、ログを眺めながらデバッグすることができるようになって一安心です。しかし、 KMDF の内部ログとかを含めて見たい場合は、また別なことをしないといけないっぽく面倒だなぁといったお気持ちです。