Share via


DriverObject と DriverEntry

こんにちは、K 里です。今回は、WDM ドライバの基本部分について、WDK サンプルドライバの Toaster を用いて説明しようと思います。基本とはいえその範囲は広いので、まずは OS とドライバの関わりとオブジェクトベースとなる Driver Object、ドライバの初期化についてまとめました。今後は Tips 記事と交互に WDK のコア コンポーネントについて説明していきたいと考えています。お役にたてれば幸いです。

 

I/O Manager

Windows 上で動作するデバイス ドライバは、I/O Manager を始めとした Kernel (ntoskrnl.exe) 内の各種コンポーネントと密接な関係を持つことになります。I/O Manager は、I/O 操作を実現するための IRP (I/O Request Packet) というパケット駆動型のモデルを実装しています。あるドライバに対して I/O 操作が必要な場合、I/O Manager はこの IRP を介してドライバの制御を行います。ドライバは、I/O Manager から受け取った IRP を基にデバイスを制御することになります。以下は、ユーザーモードのアプリケーションが CreateFile 関数を呼び出した際の I/O 要求の流れを図解しています。

 io_req_flow

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1. User mode Application は、Win32 ファイル名 (例: C:\Windows\xxx) を使用して CreateFile を呼び出す

2. ntdll.dll (NT Layer DLL) は、Win32 ファイル名を NT 名 (例: \??\C:\Windows\xxx) に変換する

3. kernel32.dll (Windows NT Base API Client DLL) は、NT 名を使用して NtCreateFile を呼び出す

4. I/O Manager は、リクエストを再構成し、Object Manager に処理を渡す

5. Object Manager は、シンボリックリンクを解決し、トラバース (走査) チェックを実施する

6. Object Manager は、Device Object の処理のために I/O Manager に処理を戻す

7. I/O Manager は、Device Object のセキュリティをチェック (ACL など) する

8. I/O Manager は、ハンドルを作成し、IRP_MJ_CREATE をデバイス ドライバに送信する

9. デバイス ドライバは、必要に応じて追加のチェックを実施する

 

Kernel-Mode Managers and Libraries

https://msdn.microsoft.com/en-us/library/cc264615.aspx

 

WDM Driver の種類

WDM ドライバは、PnP (プラグアンドプレイ) 、電源管理、WMI をサポートする Windows ドライバモデルです。WDM では、特定のデバイスの全ての制御をドライバ単体で賄うことはできません。WDM ドライバには以下の 3 タイプのドライバがあり、デバイスに対して必ずバスドライバとファンクション ドライバとセットで制御することになります。フィルタドライバはオプションとして存在しています。

Ø バス ドライバ – 特定の論理、物理バスを管理します。PCMCIA、PCI、USB、IEEE1394、ISA などが対象です。後ほど出てくる WDK Sample の Toaster 論理バス (BusEnum.sys) もバス ドライバの位置づけとなります。バスドライバは、自身が管理するバス上に接続されるデバイスを検知し、PnP Manager (Kernel 内のコンポーネント) へ通知します。また、バスの電力供給状態も管理します。

Ø ファンクション ドライバ – 特定のデバイスを管理します。ファンクション ドライバは、I/O Manager に対してデバイスを制御するためのインターフェースを提供します。一般的に ”デバイス ドライバ” と呼ばれるものです。

Ø フィルタ ドライバ – 前述したドライバの上位、もしくは下位に位置づけられるドライバで、対象ドライバの補完ないし変更を行うドライバです。

 

Types of WDM Drivers

https://msdn.microsoft.com/en-us/library/aa490241.aspx

 

Driver の構成

I/O Manager と併せて、ハードウェア動作を管理する (抜き差しの検出、リソース割り当ての調整など) PnP (Plug and Play) Manager、デバイス単位で電力消費を管理する Power Manager、実装と診断性のための情報提供を行う WMI (Windows Management Instrumentation) といったコンポーネントを包括して I/O System と呼びます。以下は、I/O System と関係するドライバの主要な処理となります。

 

io_sys

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Initialize Routine は、ドライバがロードされるタイミングで呼び出される初期化処理です。一般的に DriverEntry という関数名になります (ANSI C プログラムで言う main 関数に該当します)。Add Device Routine は、PnP サポート対象のドライバで実装されます。PnP Manager は、この処理を介してデバイスがシステムに追加されたことを知り得ることができます。Dispatch Routine は、ドライバの各制御関数となります。例えば、Create/Close/Read/Write といった基本的な制御関数です。これらは、I/O Manager からの IRP を基に適切な処理が呼び出されることになります。後の Start I/O、ISR、DPC については、別記事で説明する予定ですので今回は割愛します。

 

上記の処理 (関数) をドライバ側で実装し、それらの呼び出しタイミングを I/O Manager に委ねることになります。

 

Introduction to Standard Driver Routines

https://msdn.microsoft.com/en-us/library/ms794996.aspx

Standard Driver Routine Requirements

https://msdn.microsoft.com/en-us/library/ms794972.aspx

 

Driver Object

I/O Manager は、インストール&ロードされたシステム内の個々のドライバを表現するために、Driver Object を作成します。I/O Manager は、各ドライバが実装する初期化ルーチン (DriverEntry) を呼び出す際に Driver Object を渡します。ドライバは、その Driver Object に各 Dispatch Routine のエントリポイントを登録します。I/O Manager は、Driver Object に登録された各 Routine を、必要に応じて呼び出すことになります。なお Driver Object は、DRIVER_OBJECT 構造体として定義されます。

 

Introduction to Driver Objects

https://msdn.microsoft.com/en-us/library/ms795005.aspx

 

それでは、WDK sample プログラムの Toaster を使用して Driver Object を確認してみましょう。ここでは、Toaster バスドライバ (BusEnum.sys) を使用します。BusEnum.sys のインストールは以下のとおりです。デバイスマネージャーで、[表示] -> [デバイス(接続別)] とし、Toaster Bus Enumerator が追加されていれば成功です。なお今回は先日リリースされました WDK 7.0.0 を使用しています。

 

Toaster

https://msdn.microsoft.com/en-us/library/dd163450.aspx

 

 

(1) 以下のフォルダをビルドします (ビルドについては、なおきお~さんの過去記事をご参照ください)。

    %WinDDK%\7100.0.0\src\general\toaster\wdm\bus

(2) 対象 OS が Windows 7 の場合には、toaster\devicemetadatapackage フォルダ内のメタデータファイルを ProgramData フォルダにコピーします。コピー先は以下になります。

%ProgramData%\Microsoft\Windows\DeviceMetadataStore

(3) BusEnum.sys と toaster\wdm\inf フォルダ内の bus.inf を適当なフォルダを作成、コピーします。

(4) WDK 付属ツール Devcon を使用して BusEnum.sys をインストールします (*1)(*2)。

    >devcon.exe install businf.inf “root\busenum”

    *1. DevCon については、以下をご参照ください。

   デバイス マネージャとして機能する DevCon コマンド ライン ユーティリティ

https://support.microsoft.com/kb/311272/ja

    *2. DevCon は、%WinDDK%\7100.0.0\tools\devcon 配下にあります。

    *3. [コントロール パネル] -> [ハードウェアの追加] からもドライバのインストールは可能です。

詳細については、toaster フォルダ内のヘルプファイル (htm) をご覧ください。

toaster

 

次に、上記で Toaster Bus Enumerator をインストールしたシステムに対して、カーネルデバッグを実施します (ホスト側で、BusEnum.sys のシンボルファイル (BusEnum.pdb) をロードすることをお忘れなく!)。デバッグ接続が完了したら、BusEnum.sys の Driver Object を確認してみましょう。確認するためのコマンドは、!drvobj コマンド+ドライバ名、もしくは !object コマンド+シンボルリンク名などがあります。ただ、個々のドライバを確認するだけなら、!drvobj コマンドでオプション 7 を指定すると事足りると思います。!drvobj 7 で、アドレス、シンボルリンク名、Device Object のアドレス、各種 Dispatch Routine で登録されているエントリポイントが表示できます。

 

0: kd> !drvobj busenum 7

Driver object (88b33338) is for:

 \Driver\busenum

Driver Extension List: (id , addr)

 

Device Object list:

88a74368 

 

DriverEntry: b1026531 busenum!GsDriverEntry

DriverStartIo: 00000000    

DriverUnload: b10219a0 busenum!Bus_DriverUnload

AddDevice: b1021a60 busenum!Bus_AddDevice

 

Dispatch routines:

[00] IRP_MJ_CREATE b1021610 busenum!Bus_CreateClose

[01] IRP_MJ_CREATE_NAMED_PIPE 804fb739 nt!IopInvalidDeviceRequest

[02] IRP_MJ_CLOSE b1021610 busenum!Bus_CreateClose

[03] IRP_MJ_READ 804fb739 nt!IopInvalidDeviceRequest

[04] IRP_MJ_WRITE 804fb739 nt!IopInvalidDeviceRequest

[05] IRP_MJ_QUERY_INFORMATION 804fb739 nt!IopInvalidDeviceRequest

[06] IRP_MJ_SET_INFORMATION 804fb739 nt!IopInvalidDeviceRequest

[07] IRP_MJ_QUERY_EA 804fb739 nt!IopInvalidDeviceRequest

[08] IRP_MJ_SET_EA 804fb739 nt!IopInvalidDeviceRequest

[09] IRP_MJ_FLUSH_BUFFERS 804fb739 nt!IopInvalidDeviceRequest

[0a] IRP_MJ_QUERY_VOLUME_INFORMATION 804fb739 nt!IopInvalidDeviceRequest

[0b] IRP_MJ_SET_VOLUME_INFORMATION 804fb739 nt!IopInvalidDeviceRequest

[0c] IRP_MJ_DIRECTORY_CONTROL 804fb739 nt!IopInvalidDeviceRequest

[0d] IRP_MJ_FILE_SYSTEM_CONTROL 804fb739 nt!IopInvalidDeviceRequest

[0e] IRP_MJ_DEVICE_CONTROL b1021790 busenum!Bus_IoCtl

[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL 804fb739 nt!IopInvalidDeviceRequest

[10] IRP_MJ_SHUTDOWN 804fb739 nt!IopInvalidDeviceRequest

[11] IRP_MJ_LOCK_CONTROL 804fb739 nt!IopInvalidDeviceRequest

[12] IRP_MJ_CLEANUP 804fb739 nt!IopInvalidDeviceRequest

[13] IRP_MJ_CREATE_MAILSLOT 804fb739 nt!IopInvalidDeviceRequest

[14] IRP_MJ_QUERY_SECURITY 804fb739 nt!IopInvalidDeviceRequest

[15] IRP_MJ_SET_SECURITY 804fb739 nt!IopInvalidDeviceRequest

[16] IRP_MJ_POWER b101eb40 busenum!Bus_Power

[17] IRP_MJ_SYSTEM_CONTROL b1024140 busenum!Bus_SystemControl

[18] IRP_MJ_DEVICE_CHANGE 804fb739 nt!IopInvalidDeviceRequest

[19] IRP_MJ_QUERY_QUOTA 804fb739 nt!IopInvalidDeviceRequest

[1a] IRP_MJ_SET_QUOTA 804fb739 nt!IopInvalidDeviceRequest

[1b] IRP_MJ_PNP b1021e00 busenum!Bus_PnP

 

上記から BusEnum.sys の主要な Routine (Initialize/Add Device/Dispatch/StartIo) を確認することができます。ここでは表示されない ISR は !idt コマンドで、DPC は !timer コマンドで確認することができます。また、Dispatch Routine の中で、nt!IopInvalidDeviceRequest となっている IRP についてはドライバ側での実装がない (初期化時に routine のエントリポイントを Driver Object に紐付けていない) ことになります。つまり、BusEnum.sys で対応している IRP は以下の 6 種類となります。

Ø IRP_MJ_CREATE

Ø IRP_MJ_CLOSE

Ø IRP_MJ_DEVICE_CONTROL

Ø IRP_MJ_POWER

Ø IRP_MJ_SYSTEM_CONTROL

Ø IRP_MJ_PNP

 

ちなみに !object コマンドでは以下のようになります。

0: kd> !object \driver\busenum

Object: 88b33338 Type: (89bcc040) Driver

    ObjectHeader: 88b33320 (old version)

    HandleCount: 0 PointerCount: 3

    Directory Object: e13f35f8 Name: busenum

 

また、DRIVER_OBJECT 構造体を確認する場合は以下のようになります。

0: kd> dt nt!_DRIVER_OBJECT 88b33338 

   +0x000 Type : 4

   +0x002 Size : 168

   +0x004 DeviceObject : 0x88a74368 _DEVICE_OBJECT

   +0x008 Flags : 0x12

   +0x00c DriverStart : 0xb101e000

   +0x010 DriverSize : 0x9b80

   +0x014 DriverSection : 0x89055668

   +0x018 DriverExtension : 0x88b333e0 _DRIVER_EXTENSION

   +0x01c DriverName : _UNICODE_STRING "\Driver\busenum"

   +0x024 HardwareDatabase : 0x8069d210 _UNICODE_STRING "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"

   +0x028 FastIoDispatch : (null)

   +0x02c DriverInit : 0xb1026531 long busenum!GsDriverEntry+0

   +0x030 DriverStartIo : (null)

   +0x034 DriverUnload : 0xb10219a0 void busenum!Bus_DriverUnload+0

   +0x038 MajorFunction : [28] 0xb1021610 long busenum!Bus_CreateClose+0

 

0: kd> dt 0x88b333e0 _DRIVER_EXTENSION

nt!_DRIVER_EXTENSION

   +0x000 DriverObject : 0x88b33338 _DRIVER_OBJECT

   +0x004 AddDevice : 0xb1021a60 long busenum!Bus_AddDevice+0

   +0x008 Count : 0

   +0x00c ServiceKeyName : _UNICODE_STRING "busenum"

 

上記で赤字で示している構造体メンバが、基本的にドライバ側でアクセス可能なメンバとなります。ただ、全てのメンバに対してドライバ側で情報を埋める必要はありません。DriverExtension は、DRIVER_EXTENSION サブ構造体として定義されています。そのメンバとなる AddDevice に、Add Device Routine のエントリポイントを登録します。FastIoDispatch メンバは、ファイルシステムやネットワークドライバなどの特定のドライバのみが使用します。あとは、DriverStartIo メンバに Start I/O、DriverUnload メンバにクリーンアップ関数 (WDM では基本 Object/Register の解放のみ) となります。MajoFunction メンバは、各 Dispatch Routine のエントリポイントを登録するための配列となります。前述したドライバの主要な処理の中で、実装している処理を登録することになります。これらのデバッグ情報と併せて BusEnum.sys のソースコードを確認していただくと、より理解が深まると思います。

 

/** Busenum.c **/

 

NTSTATUS

DriverEntry (

    __in PDRIVER_OBJECT DriverObject,

    __in PUNICODE_STRING RegistryPath

    )

  :

  :

    //

    // Set entry points into the driver

    //

    DriverObject->MajorFunction [IRP_MJ_CREATE] =

    DriverObject->MajorFunction [IRP_MJ_CLOSE] = Bus_CreateClose;

    DriverObject->MajorFunction [IRP_MJ_PNP] = Bus_PnP;

    DriverObject->MajorFunction [IRP_MJ_POWER] = Bus_Power;

    DriverObject->MajorFunction [IRP_MJ_DEVICE_CONTROL] = Bus_IoCtl;

    DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = Bus_SystemControl;

    DriverObject->DriverUnload = Bus_DriverUnload;

    DriverObject->DriverExtension->AddDevice = Bus_AddDevice;

 

Writing a DriverEntry Routine

https://msdn.microsoft.com/en-us/library/ms794742.aspx

 

次回以降の WDM 関連記事では、今回ちょこちょこ出てきた Device Object について説明しようと思います。

ではまた。