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インストールを選択し、
- 「Programming Languages」→「Visual C++」→「Common Tools for Visual C++ 2015」
- 「Windows and Web Development」→「Windows 8.1 and Windows Phone 8.0/8.1 Tools」→「Tools and Windows SDKs」
にチェックを入れる。
インストールが終わったら、WinDbgを使えるよう環境変数PATH
にデバッガのパス(C:\Program Files\Windows Kits\10\Debuggers\x86
)を追加し、環境変数_NT_SYMBOL_PATH
にシンボルサーバを設定する(参考)。
さらに、DbgViewやLiveKDなどのデバッグツールを含むSysinternals Suiteをダウンロードし、展開しておく。 必要に応じて、自動アップデートの無効化、RDP接続のためのネットワーク・Windowsファイアウォール設定を行い、この状態でスナップショットを取っておくとよい。
デバイスドライバを書いてみる
Windowsでデバイスドライバを書く際のフレームワークには、WDM(Windows Driver Model)、KMDF(Kernel-Mode Driver Framework)、UMDF(User-Mode Driver Framework)の3つがある。 KMDFはWDMを書きやすいようにラップしたものであり、Win32APIに対するMFC(Microsoft 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」を実行すると、ビルドが成功する。
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にデバッグログが表示されることが確認できる。