Windowsで簡単なデバイスドライバを書いてみる

VirtualBox上の仮想マシンWindowsデバイスドライバの開発環境を構築し、簡単なデバイスドライバの作成とロードをやってみる。

環境

VirtualBox 4.3.28

開発用のWindows VMを用意する

デバイスドライバの開発ではたびたび再起動が必要となるため、VirtualBox上の仮想マシンに開発環境を構築する。

まず、modern.IEからWindows VirtualBox用のWindows 8.1 + IE11イメージをダウンロードし、仮想マシンを作成する。 なお、このイメージのWindowsは32bit版である。

次に、Download kits and tools for Windows 10に従い、仮想マシン上にVisual Studio Community 2015とWDK 10.0をインストールする。 Visual Studioをインストールする際は、Customインストールを選択し、

にチェックを入れる。 インストールが終わったら、WinDbgを使えるよう環境変数PATHにデバッガのパス(C:\Program Files\Windows Kits\10\Debuggers\x86)を追加し、環境変数_NT_SYMBOL_PATHにシンボルサーバを設定する(参考)。

さらに、DbgViewやLiveKDなどのデバッグツールを含むSysinternals Suiteをダウンロードし、展開しておく。 必要に応じて、自動アップデートの無効化、RDP接続のためのネットワーク・Windowsファイアウォール設定を行い、この状態でスナップショットを取っておくとよい。

デバイスドライバを書いてみる

Windowsデバイスドライバを書く際のフレームワークには、WDMWindows Driver Model)、KMDF(Kernel-Mode Driver Framework)、UMDF(User-Mode Driver Framework)の3つがある。 KMDFはWDMを書きやすいようにラップしたものであり、Win32APIに対するMFCMicrosoft Foundation Classes)のような関係にある。 WDMおよびKMDFで作成されたデバイスドライバは、拡張子sysをもつファイルとして実装される。 一方、UMDFはカーネル内に実装されたドライバ(リフレクタと呼ばれる)をユーザモードから呼び出すタイプのドライバを作るためのものであり、ドライバはCOM-based DLLとして実装される。

現在WDMはレガシーとみなされ、カーネルモードで動作するデバイスドライバはKMDFを用いて書かれることが多い。 しかし、ここでは実装を単純にするためWDMを用いてデバイスドライバを書くことにする。

まず、Visual Studioを起動し、「File」→「New」→「Project…」から新規プロジェクトを作成する。 テンプレートには「Visual C++」→「Windows Driver」→「Legacy」にある「Empty WDM Driver」を選択し、プロジェクト名を適当に指定する。 ここではプロジェクト名を「hello」とする。

次に、Solution Explorerに表示されたプロジェクト「hello」を右クリックし、「Add」→「New Item…」で新規ファイルを追加する。 テンプレートには「C++ File (.cpp)」を選び、ファイル名は「hello.c」とする。 ここでは、C++ではなくCでコードを書くため、拡張子にはcppではなくcを指定する。

Codeペインでhello.cが開かれたら、次のようなコードを書き保存する。

/* hello.c */
#include <wdm.h>
#pragma warning(disable: 4100)

DRIVER_INITIALIZE DriverEntry;

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    DbgPrint("Hello, World!\n");
    return STATUS_SUCCESS;
}

デバイスドライバにおけるmain関数にはDriverEntry関数が対応する。 DbgPrint関数デバッグログを出力するための関数であり、Sysinternals Suiteに含まれるDbgViewなどで表示させることができる。 また、参照されていない仮引数に関する警告(C4100)によるエラーを抑制するため、pragma warningを指定している。

次に、メニューの「Build」→「Build Solution」を選択し実行すると、hello.infの複数箇所についてエラーが表示される。 そこで、「Driver Files」にあるhello.infを開き、エラーが出ている項目を次のように適当に補完する。

ClassGuid={00000000-0000-0000-0000-000000000000}
Provider=foo
CatalogFile=hello.cat

再度「Build Solution」を実行すると、ビルドが成功する。

f:id:inaz2:20150912215128p:plain

Solution Explorerに表示されたプロジェクト「hello」を右クリックし「Open Folder in File Explorer」を選択することで、プロジェクトフォルダをエクスプローラで開くことができる。 ビルドされたsysファイルは、このフォルダから見て..\hello\Debug\helloにある。

デバイスドライバをロードしてみる

デバイスドライバをロードする前に、カーネルログを表示させるためDbgViewを管理者権限で起動する。 さらに起動後、メニューの「Capture」から「Capture Kernel」と「Enable Verbose Kernel Output」を有効にしておく。

デバイスドライバをロードする簡便な方法として、scコマンドを利用してサービスとして起動する方法がある。 具体的には、次のようなバッチファイルを作成し、管理者権限で実行する。

@echo off
sc create hello binPath="C:\Users\IEUser\Documents\visual studio 2015\Projects\hello\Debug\hello\hello.sys" type=kernel
sc start hello
sc delete hello
pause

なお、一度ロードしたデバイスドライバは再度ロードできない。 したがって、改めてロードするためにはサービス名とファイル名の両方を変更するか、再起動する必要がある。

実際に上のバッチファイルを管理者権限で実行すると、デバイスドライバがロードされ、DbgViewにデバッグログが表示されることが確認できる。

f:id:inaz2:20150912215148p:plain

関連リンク