Hello World Windows ドライバーの作成 (KMDF)
この記事では、カーネルモード ドライバー フレームワーク (KMDF) を使用して小さなユニバーサル Windows ドライバーを作成し、別のコンピューターにそのドライバーを展開してインストールする方法について説明します。
続行する前に、「Windows Driver Kit (WDK) のダウンロード」に記載されているインストール手順を完了します。
Windows 用デバッグ ツールは、WDK をインストールするときに組み込まれます。
ドライバーの作成とビルド
Microsoft Visual Studio を開く。 [ファイル] メニューで [新規] > [プロジェクト] を選択します。
[新しいプロジェクトの作成] ダイアログ ボックスの左側のドロップダウンで [C++] を選択し、中央のドロップダウンで [Windows] を選択し、右側のドロップダウンで [ドライバー] を選択します。
プロジェクトの種類の一覧から [Kernel Mode Driver, Empty (KMDF)]\(カーネル モード ドライバー, 空 (KMDF)\) を選択します。 [次へ] を選択します。
[新しいプロジェクトの構成] ダイアログ ボックスで、[プロジェクト名] フィールドに「KmdfHelloWorld」と入力します。
Note
新しい KMDF ドライバーか UMDF ドライバーを作成する場合、32 文字以下のドライバー名を選ぶ必要があります。 この長さの制限は wdfglobals.h で定義されています。
[場所] フィールドに、新しいプロジェクトを作成するディレクトリを入力します。
[ソリューションとプロジェクトを同じディレクトリに配置する] をオンにし、[作成] を選択します。
Visual Studio により、1 つのプロジェクトと 1 つのソリューションが作成されます。 これらはソリューション エクスプローラー ウィンドウで確認できます。 ([ソリューション エクスプローラー] ウィンドウが表示されない場合は、[表示] メニューの [ソリューション エクスプローラー] を選択します)。このソリューションには、KmdfHelloWorld という名前のドライバー プロジェクトがあります。
[ソリューション エクスプローラー] ウィンドウで、[KmdfHelloWorld] ソリューションを長押し (または右クリック) し、[構成マネージャー] を選びます。 ドライバー プロジェクトに対する構成とプラットフォームを選びます。 たとえば、Debug と x64 を選びます。
[ソリューション エクスプローラー] ウィンドウで、もう一度 [KmdfHelloWorld] プロジェクトを長押し (または右クリック) し、[追加] を選択して、[新しい項目] を選択します。
[新しい項目の追加] ダイアログ ボックスで、[C++ ファイル] を選択します。 [名前] に「Driver.c」と入力します。
Note
ファイル名拡張子は、.cpp ではなく .c です。
[追加] を選択します。 次に示すように、Driver.c ファイルが [ソース ファイル] の下に追加されます。
初めてのドライバー コードを記述する
空の Hello World プロジェクトを作成し、Driver.c ソース ファイルを追加したら、2 つの基本的なイベント コールバック関数を実装して、ドライバーの実行に必要な最も基本的なコードを記述します。
Driver.c で、最初にこれらのヘッダーを含めます。
#include <ntddk.h> #include <wdf.h>
ヒント
Ntddk.h
を追加できない場合は、[構成] -> [C/C++] -> [全般] -> [追加のインクルード ディレクトリ] の順に開いてC:\Program Files (x86)\Windows Kits\10\Include\<build#>\km
を追加し、<build#>
を WDK インストール内の適切なディレクトリに置き換えます。Ntddk.h には、すべてのドライバーの主要な Windows カーネル定義が含まれています。一方、Wdf.h には、Windows ドライバー フレームワーク (WDF) に基づくドライバーの定義が含まれています。
次に、使用する 2 つのコールバックの宣言を指定します。
DRIVER_INITIALIZE DriverEntry; EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd;
次のコードを使用して DriverEntry を記述します。
NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath ) { // NTSTATUS variable to record success or failure NTSTATUS status = STATUS_SUCCESS; // Allocate the driver configuration object WDF_DRIVER_CONFIG config; // Print "Hello World" for DriverEntry KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: DriverEntry\n" )); // Initialize the driver configuration object to register the // entry point for the EvtDeviceAdd callback, KmdfHelloWorldEvtDeviceAdd WDF_DRIVER_CONFIG_INIT(&config, KmdfHelloWorldEvtDeviceAdd ); // Finally, create the driver object status = WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE ); return status; }
DriverEntry はすべてのドライバーのエントリ ポイントで、多くのユーザー モード アプリケーションに使用される
Main()
と同様です。 DriverEntry の役割は、ドライバー全体の構造とリソースを初期化することです。 この例では、DriverEntry に対して "Hello World" を出力し、EvtDeviceAdd コールバックのエントリ ポイントを登録するためのドライバー オブジェクトを構成し、ドライバー オブジェクトを作成した後、制御を返しています。ドライバー オブジェクトは、デバイス オブジェクト、I/O キュー、タイマー、スピンロックなど、ドライバーで作成する可能性のある他のすべてのフレームワーク オブジェクトの親オブジェクトとして機能します。 フレームワーク オブジェクトの詳細については、「フレームワーク オブジェクトの概要」を参照してください。
ヒント
DriverEntry については、コード分析とデバッグに役立てるために "DriverEntry" という名前を維持することを強くお勧めします。
次に、次のコードを使用して、KmdfHelloWorldEvtDeviceAdd を記述します。
NTSTATUS KmdfHelloWorldEvtDeviceAdd( _In_ WDFDRIVER Driver, _Inout_ PWDFDEVICE_INIT DeviceInit ) { // We're not using the driver object, // so we need to mark it as unreferenced UNREFERENCED_PARAMETER(Driver); NTSTATUS status; // Allocate the device object WDFDEVICE hDevice; // Print "Hello World" KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: KmdfHelloWorldEvtDeviceAdd\n" )); // Create the device object status = WdfDeviceCreate(&DeviceInit, WDF_NO_OBJECT_ATTRIBUTES, &hDevice ); return status; }
EvtDeviceAdd は、デバイスが到着したことが検出されたときに、システムによって呼び出されます。 その役割は、デバイスの構造とリソースを初期化することです。 この例では、EvtDeviceAdd に対して単に "Hello World" メッセージを出力し、デバイス オブジェクトを作成した後、制御を返しています。 作成する他のドライバーでは、ハードウェアの I/O キューを作成したり、デバイス固有の情報用にデバイス コンテキストの記憶域スペースを設定したり、デバイスを準備するために必要な他のタスクを実行したりします。
ヒント
デバイスの追加コールバックについては、プレフィックスとしてドライバー名を含む名前が付けられている点に注目してください (KmdfHelloWorldEvtDeviceAdd)。 一般に、他のドライバーの関数と区別するために、この方法でドライバーの関数に名前を付けることをお勧めします。 DriverEntry に限り、正確にこのとおりの名前を付ける必要があります。
完全な Driver.c は、このようになります。
#include <ntddk.h> #include <wdf.h> DRIVER_INITIALIZE DriverEntry; EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd; NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath ) { // NTSTATUS variable to record success or failure NTSTATUS status = STATUS_SUCCESS; // Allocate the driver configuration object WDF_DRIVER_CONFIG config; // Print "Hello World" for DriverEntry KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: DriverEntry\n" )); // Initialize the driver configuration object to register the // entry point for the EvtDeviceAdd callback, KmdfHelloWorldEvtDeviceAdd WDF_DRIVER_CONFIG_INIT(&config, KmdfHelloWorldEvtDeviceAdd ); // Finally, create the driver object status = WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE ); return status; } NTSTATUS KmdfHelloWorldEvtDeviceAdd( _In_ WDFDRIVER Driver, _Inout_ PWDFDEVICE_INIT DeviceInit ) { // We're not using the driver object, // so we need to mark it as unreferenced UNREFERENCED_PARAMETER(Driver); NTSTATUS status; // Allocate the device object WDFDEVICE hDevice; // Print "Hello World" KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: KmdfHelloWorldEvtDeviceAdd\n" )); // Create the device object status = WdfDeviceCreate(&DeviceInit, WDF_NO_OBJECT_ATTRIBUTES, &hDevice ); return status; }
Driver.c を保存します。
この例は、ドライバーの基本的な概念を示しています。これらは、初期化されると、システムが何かを必要とするときに呼び出されるのを待つ "コールバックのコレクション" です。 システム呼び出しには、新しいデバイス到着イベント、ユーザー モード アプリケーションからの I/O 要求、システム電源シャットダウン イベント、別のドライバーからの要求、またはユーザーが予期せずデバイスを取り外したときの突然の削除イベントなどが考えられます。 さいわいにも、"Hello World" を表示するにはドライバーとデバイスの作成について心配するだけで済みました。
次に、ドライバーをビルドします。
ドライバーをビルドする
[ソリューション エクスプローラー] ウィンドウで、[ソリューション 'KmdfHelloWorld' (1 プロジェクト)] を長押し (または右クリック) し、[構成マネージャー] を選びます。 ドライバー プロジェクトに対する構成とプラットフォームを選びます。 この作業では、Debug と x64 を選びます。
[ソリューション エクスプローラー] ウィンドウで、[KmdfHelloWorld] を長押し (または右クリック) し、[プロパティ] を選びます。 [Wpp Tracing] (Wpp トレース) > [All Options] (すべてのオプション) で、[Run Wpp tracing] (Wpp トレースの実行) を [いいえ] に設定します。 [Apply](適用) 、 [OK] の順に選択します。
ドライバーをビルドするには、[ビルド] メニューの [ソリューションのビルド] を選びます。 Visual Studio により、[出力] ウィンドウにビルドの進行状況が表示されます。 ([出力] ウィンドウが表示されない場合は、[表示] メニューの [出力] を選択します)。ソリューションが正常にビルドされたことを確認したら、Visual Studio を閉じます。
ビルドされたドライバーを確認するには、エクスプローラーで KmdfHelloWorld フォルダーに移動し、さらに x64\Debug\KmdfHelloWorld に移動します。 フォルダーには次のものが含まれます。
- KmdfHelloWorld.sys -- カーネルモード ドライバー ファイル
- KmdfHelloWorld.inf -- ドライバーのインストール時に Windows が使用する情報ファイル
- KmdfHelloWorld.cat -- ドライバーのテスト署名を検証するときにインストーラーで使われるカタログ ファイル
ヒント
ドライバーのビルド時に DriverVer set to a date in the future
が表示された場合、ドライバー プロジェクトの設定を Inf2Cat が /uselocaltime
を設定するように変更します。 このためには、[構成プロパティ] -> [Inf2Cat] -> [全般] -> [Use Local Time]\(現地時刻の使用\) を使用します。 これで、Stampinf と Inf2Cat の両方で現地時刻が使用されます。
ドライバーを展開する
ドライバーをテストしてデバッグする場合、通常はデバッガーとドライバーを別個のコンピューターで実行します。 デバッガーを実行するコンピューターはホスト コンピューターと呼ばれ、ドライバーを実行するコンピューターはターゲット コンピューターと呼ばれます。 ターゲット コンピューターはテスト コンピューターとも呼ばれます。
ここまでは、Visual Studio を使用してホスト コンピューター上でドライバーをビルドしました。 次に、ターゲット コンピューターを構成する必要があります。
「ドライバーの展開およびテストのためのコンピューターのプロビジョニング (WDK 10)」の手順に従ってください。
ヒント
ネットワーク ケーブルを使用してターゲット コンピューターを自動的にプロビジョニングする手順に従う場合は、ポートとキーをメモしておきます。 これらはデバッグ手順の後半で使用します。 この例では、ポートとして 50000 を使用し、キーとして 1.2.3.4 を使用します。
実際のドライバーのデバッグ シナリオでは、KDNET によって生成されたキーを使用することをお勧めします。 KDNET を使用してランダム キーを生成する方法の詳細については、「ドライバーのデバッグ - ステップ バイ ステップ ラボ (Sysvad カーネル モード)」のトピックを参照してください。
ホスト コンピューターで、Visual Studio でソリューションを開きます。 KmdfHelloWorld フォルダー内のソリューション ファイル (KmdfHelloWorld.sln) をダブルクリックできます。
[ソリューション エクスプローラー] ウィンドウで、[KmdfHelloWorld] プロジェクトを選択して長押し (または右クリック) し、[プロパティ] をクリックします。
次に示すように、[KmdfHelloWorld のプロパティ ページ] ウィンドウで、[構成プロパティ] > [Driver Install] (ドライバーのインストール) > [Deployment] (展開) の順に移動します。
[デプロイ前に以前のドライバー バージョンを削除する] にチェックを入れます。
[Target Device Name] (ターゲット デバイス名) で、テストとデバッグ用に構成したコンピューターの名前を選択します。 この演習では、MyTestComputer という名前のコンピューターを使用します。
[Hardware ID Driver Update] (ハードウェア ID のドライバーの更新) を選択し、ドライバーのハードウェア ID を入力します。 この例では、ハードウェア ID は Root\KmdfHelloWorld です。 [OK] を選択します。
Note
この演習のハードウェア ID は実際のハードウェアを識別するものではありません。 これは、ルート ノードの子としてデバイス ツリー内の場所が与えられる架空のデバイスを識別します。 実際のハードウェアの場合は、[Hardware ID Driver Update] (ハードウェア ID のドライバーの更新) を選択しないでください。代わりに、[インストールと検証] を選択します。 ハードウェア ID は、ドライバーの情報 (INF) ファイルで確認できます。 [ソリューション エクスプローラー] ウィンドウで、[KmdfHelloWorld] > [Driver Files] (ドライバー ファイル) の順に移動し、KmdfHelloWorld.inf をダブルクリックします。 ハードウェア ID は [Standard.NT$ARCH$] の下に記載されています。
[Standard.NT$ARCH$] %KmdfHelloWorld.DeviceDesc%=KmdfHelloWorld_Device, Root\KmdfHelloWorld
[ビルド] メニューの [ソリューションの配置] を選択します。 Visual Studio により、ドライバーのインストールと実行に必要なファイルがターゲット コンピューターに自動的にコピーされます。 デプロイには 1、2 分かかる場合があります。
ドライバーを展開すると、ドライバー ファイルがテスト コンピューターの %Systemdrive%\drivertest\drivers フォルダーにコピーされます。 展開中に問題が発生した場合は、ファイルがテスト コンピューターにコピーされているかどうかを確認できます。 .inf、.cat、test cert、.sys など、必要なファイルがすべて %systemdrive%\drivertest\drivers フォルダーに存在することを確認します。
ドライバーの展開の詳細については、「テスト コンピューターへのドライバーの展開」を参照してください。
ドライバーをインストールする
Hello World ドライバーをターゲット コンピューターに展開したら、ドライバーをインストールします。 事前に Visual Studio で自動オプションを使用してターゲット コンピューターをプロビジョニングしている場合、Visual Studio により、プロビジョニング プロセスの一環としてテスト署名済みドライバーを実行するようにターゲット コンピューターが設定されます。 ここでは、DevCon ツールを使用してドライバーをインストールするだけです。
ホスト コンピューターで、WDK インストールの [ツール] フォルダーに移動し、DevCon ツールを見つけます。 たとえば、次のフォルダー内を探します。
C:\Program Files (x86)\Windows Kits\10\Tools\x64\devcon.exe
DevCon ツールをリモート コンピューターにコピーします。
ターゲット コンピューターで、ドライバー ファイルが含まれているフォルダーに移動し、DevCon ツールを実行してドライバーをインストールします。
ドライバーのインストールに使用する devcon ツールの一般的な構文を次に示します。
devcon install <INF ファイル><ハードウェア ID>
このドライバーのインストールに必要な INF ファイルは KmdfHelloWorld.inf です。 この INF ファイルには、ドライバーのバイナリ (KmdfHelloWorld.sys) をインストールするためのハードウェア ID が含まれています。 INF ファイルにあるハードウェア ID が Root\KmdfHelloWorld であることを思い出してください。
管理者として [コマンド プロンプト] ウィンドウを開きます。 ビルドされたドライバーの .sys ファイルを含むフォルダーに移動し、次のコマンドを入力します。
devcon install kmdfhelloworld.inf root\kmdfhelloworld
devcon が認識されないというエラー メッセージが表示される場合は、devcon ツールへのパスを追加してみてください。 たとえば、対象のコンピューター上の C:\Tools という名前のフォルダーにコピーする場合は、次のコマンドを使ってみてください。
c:\tools\devcon install kmdfhelloworld.inf root\kmdfhelloworld
テスト ドライバーが署名されていないドライバーであることを示すダイアログ ボックスが表示されます。 [かまわず続行] を選択して続行します。
ドライバーをデバッグする
ターゲット コンピューターに KmdfHelloWorld ドライバーをインストールしたら、ホスト コンピューターからリモートでデバッガーをアタッチします。
ホスト コンピューターで、管理者として [コマンド プロンプト] ウィンドウを開きます。 WinDbg.exe ディレクトリに移動します。 Windows キットのインストールの一部としてインストールされた Windows Driver Kit (WDK) に含まれる WinDbg.exe の x64 バージョンを使用します。 WinDbg.exe の既定のパスを次に示します。
C:\Program Files (x86)\Windows Kits\10\Debuggers\x64
次のコマンドを使用して、WinDbg を起動し、ターゲット コンピューター上のカーネル デバッグ セッションに接続します。 ポートとキーの値は、ターゲット コンピューターをプロビジョニングするために使用した値と同じである必要があります。 ここでは、ポートとして 50000、キーとして 1.2.3.4 を使用します。これらは展開の手順で使用した値です。 k フラグは、これがカーネル デバッグ セッションであることを示します。
WinDbg -k net:port=50000,key=1.2.3.4
[デバッグ] メニューの [Break] (中断) を選択します。 ホスト コンピューター上のデバッガーが、ターゲット コンピューターに割り込みます。 デバッガーのコマンド ウィンドウに、カーネルのデバッグ コマンド プロンプト kd> が表示されます。
この時点で、kd> プロンプトに対してコマンドを入力して、デバッガーの操作を試すことができます。 たとえば、これらのコマンドを試すことができます。
ターゲット コンピューターをもう一度実行するには、[デバッグ] メニューの [Go] を選択するか、g キーを押してから Enter キーを押します。
デバッグ セッションを停止するには、[デバッグ] メニューの [デバッガーのデタッチ] を選択します。
重要
デバッガーを終了する前に、必ず [Go] コマンドを使用してターゲット コンピューターをもう一度実行してください。ターゲット コンピューターはまだデバッガーとやり取りをしている状態であるため、この操作を行わないと、ターゲット コンピューターはマウスとキーボードの入力に対して応答を停止したままになります。
ドライバーのデバッグ プロセスの詳細なチュートリアルについては、「ユニバーサル ドライバーのデバッグ - ステップ バイ ステップ ラボ (エコー カーネルモード)」を参照してください。
リモート デバッグの詳細については、「WinDbg を使用したリモート デバッグ」を参照してください。