カーネル モード ドライバで DLL を呼び出す
Windows プログラミングを行った経験がおありなら、少なくとも DLL を 1 つは作成したことがあるでしょう。Windows ドライバを作成し始めたばかりの場合、自分のドライバから DLL を呼び出そうとして失敗したことがあるかもしれません。これはなぜうまくいかないのでしょうか?
カーネル モードの DLL の基本的な問題は、その DLL が、ユーザー モードの任意のコードを呼び出すかどうかです。ネイティブのカーネル API 呼び出し以外のものが DLL に含まれている場合、ビルド時に自分のドライバをその DLL とリンクしようとすると、リンカ エラーが生じるでしょう (そして、結局カーネルはそれを読み込まないでしょう)。明らかに、Win32 ライブラリとして構築された DLL は、このカテゴリに分類されます。たとえ DLL のソース コード内でユーザー モード API 呼び出しを明示的に行うのを避けたとしても、コンパイラはしばしば、スタック チェックやオーバーフロー チェックなどを行う際、暗黙のユーザー モード サポート呼び出しを生成します。したがって、Visual Studio などの Windows ユーザー モード開発環境で構築されたいずれの DLL も、カーネル モードで使用する資格はないでしょう。
どうすればよいでしょうか。
DDK ビルド環境を使用して、"エクスポート ドライバ" をビルドします。エクスポート ドライバは、他のドライバが呼び出すルーチンを提供する、カーネル モードの DLL です。任意の標準ドライバと同様に、エクスポート ドライバには、カーネル モードの関数に帰結するルーチンのみが含まれます。ただし、標準ドライバと異なり、エクスポート ドライバは、IRP を受け取ったりドライバ スタック内の場所を占めたりせず、システム サービスともみなされません。
エクスポート ドライバとして、任意の標準ドライバを構築できます。それは、通常の方法で読み込まれると標準ドライバとして機能し、また、他のドライバが呼び出すことができる関数のエクスポートも行います。ただし、ライブラリのカーネル モードでの同等物を単に作成する場合、十分な機能を持つドライバに必要な要素の多くは省略できます。少なくとも、エクスポート ドライバには、DriverEntry ルーチンが存在する必要があります。これは、ビルド スクリプトを満たす空のスタブであってもかまわず、エクスポート ドライバの DriverEntry がプラグ アンド プレイによって呼び出されることはありません。また、エクスポート ドライバには、標準エントリ ポイントとアンロード ルーチン、DllInitialize、および DLLUnload が存在する必要があります。したがって、オペレーティング システムは、エクスポート ドライバの関数が他のドライバによってインポートされた回数を保持し、最後の呼び出し側ドライバがアンロードされたとき、エクスポート ドライバをアンロードできます。
エクスポート ドライバのソース コード内で DECLSPEC_EXPORT マクロを使用して関数を宣言することにより、関数をエクスポートできます。または、モジュール定義 (.def) ファイル内でエクスポートとして関数を列挙する、より単純なアプローチをとることもできます。(たとえ DECLSPEC_EXPORT を使用した場合でも、これらの関数に PRIVATE というマークを付けることができるよう、自分の .def ファイルには、少なくとも DllInitialize と DLLUnload が含まれている必要があります。)
ドライバの sources ファイルで、TARGETTYPE を EXPORT_DRIVER に設定し、DLLDEF を自分の .def ファイルのパスに設定してから、ドライバをビルドします。ビルド プロセスは、.lib 拡張子でエクスポート ライブラリを、.sys 拡張子でエクスポート ドライバを生成します。.sys ファイルは、自分のカーネル モードの DLL です。
エクスポート ドライバへの静的リンクは、ユーザー モード DLL の場合と本質的に同じです。ライブラリを呼び出すドライバのターゲット ライブラリ一覧に、エクスポート ドライバ ライブラリを追加してください。呼び出し側ドライバのソース コードで、DECLSPEC_IMPORT マクロを使用して、呼び出す関数を宣言します。(呼び出しにおいて、望んでいない名前装飾を避けるため、ntdef.h で定義されている EXTERN_C も使用します。)エクスポート ドライバの .sys ファイルを、%windir%\system32\drivers ディレクトリにインストールします。これは、他の任意のドライバが最初に呼び出したとき、読み込まれます。
動的リンクは、もう少し複雑です。なぜなら、GetProcAddress に対する、カーネル モードでの同等なものがないからです。(MmGetSystemRoutineAddress は似ていますが、これは Ntoskrnl.exe または HAL からのみ動的にエクスポートを解決します。)エクスポート ドライバに動的リンクするための技法の一部は、次のとおりです。
| • | エクスポート ドライバで、入出力制御コード (IOCTL) を定義します。IOCTL は、クラスのインスタンスを作成し、他のドライバが通常の方法によりインターフェイス オブジェクトでメソッドを呼び出すのに使用できるインターフェイス ポインタを返します。クラスは、オブジェクトを解放するメソッドを提供する必要があり、ヘッダー ファイルには、純粋な抽象メソッドのみが含まれる必要があります。 |
| • | エクスポート ドライバを、他のドライバからの IRP_MN_QUERY_INTERFACE 要求に応えて、直接呼び出しのインターフェイスをエクスポートするよう設計します。 |
詳細情報 :
Roberts, Tim. DLLs in Kernel Mode.Windows Driver Developer's Digest, Vol. 1 No. 3, July 2003
Windows DDK
Creating Export Drivers
Defining I/O Control Codes
IRP_MN_QUERY_INTERFACE
Visual C++ Programmer's Guide
Module-Definition (.def) Files
Richter, Jeffrey.Programming Applications for Microsoft Windows, 4th ed. Microsoft Press, 1999. ISBN 1-57231-996-8
Part IV: Dynamic-Link Libraries