カーネル モード ドライバーへのイベント トレーシングの追加
このセクションでは、Event Tracing for Windows (ETW) カーネル モード API を使用して、カーネル モード ドライバーにイベント トレースを追加する方法について説明します。 ETW カーネル モード API は Windows Vista で導入されており、以前のオペレーティング システムではサポートされていません。 ドライバーが Windows 2000 以降でトレース機能をサポートする必要がある場合は、 WPP ソフトウェア トレース または WMI イベント トレース を使用します。
ヒント
Windows Driver Kit (WDK) と Visual Studio を使用して ETW を実装する方法を示すサンプル コードを表示するには、 Eventdrv サンプルを参照してください。
このセクションの内容は次のとおりです。
Workflow - カーネル モード ドライバーへのイベント トレーシングの追加
2. プロバイダー、イベント、チャネルを定義するインストルメンテーション マニフェストを作成する
3. メッセージ コンパイラを使用してインストルメンテーション マニフェストをコンパイルする (Mc.exe)
4. 生成されたコードを追加してイベントを発生 (発行) します (イベントの登録、登録解除、および書き込み)
Workflow - カーネル モード ドライバーへのイベント トレーシングの追加
1. 発生させるイベントの種類と発行する場所を決定する
コーディングを開始する前に、ドライバーが Windows イベント トレーシング (ETW) を使用してログに記録するイベントの種類を決定する必要があります。 たとえば、ドライバーの配布後に問題を診断するのに役立つイベントや、ドライバーの開発時に役立つ可能性のあるイベントをログに記録できます。詳細については、「Windows イベント ログ リファレンス」を参照してください。
イベントの種類はチャネルで識別されます。 チャネル は、テレビ チャンネルと同様に、特定の対象ユーザーに向けられた、管理、運用、分析、またはデバッグの種類のイベントの名前付きストリームです。 チャネルは、イベント プロバイダーからイベント ログとイベント コンシューマーにイベントを配信します。 詳細については、 チャネル の定義を参照してください。
開発中は、コードのデバッグに役立つイベントのトレースに関心がある可能性が最も高くなります。 この同じチャネルを運用コードで使用して、ドライバーの展開後に発生する可能性がある問題のトラブルシューティングに役立てることができます。 パフォーマンスの測定に使用できるイベントをトレースすることもできます。これらのイベントは、IT プロフェッショナルがサーバーのパフォーマンスを微調整するのに役立ち、ネットワークのボトルネックを特定するのに役立ちます。
2. プロバイダー、イベント、チャネルを定義するインストルメンテーション マニフェストを作成する
インストルメンテーション マニフェストは、プロバイダーが発生させるイベントの正式な説明を提供する XML ファイルです。 インストルメンテーション マニフェストは、イベント プロバイダーを識別し、チャネルまたはチャネル (最大 8 つ) を指定し、イベントを記述し、イベントで使用するテンプレートを指定します。 また、インストルメンテーション マニフェストでは文字列のローカライズが可能であるため、トレース メッセージをローカライズできます。 イベント システムおよびイベント コンシューマーは、マニフェストで提供される構造化 XML データを使用して、クエリと分析を実行できます。
インストルメンテーション マニフェストの詳細については、「インストルメンテーション マニフェスト (Windows)の書き込み、 EventManifest スキーマ (Windows) および Windows イベント ログ (Windows)の使用」を参照してください。
次のインストルメンテーション マニフェストは、"Sample Driver" という名前を使用するイベント プロバイダーを示しています。この名前は、ドライバー バイナリの名前と同じである必要はありません。 マニフェストでは、プロバイダーの GUID と、メッセージファイルとリソース ファイルへのパスも指定されます。 メッセージ ファイルとリソース ファイルを使用すると、イベントのデコードとレポートに必要なリソースの場所が ETW に通知されます。 これらのパスは、ドライバー (.sys) ファイルの場所を指します。 ドライバーは、ターゲット コンピューター上の指定したディレクトリにインストールする必要があります。
この例では、 管理型のイベントのチャネルである名前付きチャネル System を使用します。このチャネルは、%WindowsSdkDir%\include\um ディレクトリの Windows Driver Kit (WDK) で提供されるWinmeta.xml ファイルで定義されます。 システム チャネルは 、システム サービス アカウントで実行されているアプリケーションに対してセキュリティで保護されます。 マニフェストには、公開時にイベントと共に提供されるデータの種類と、静的および動的なコンテンツを記述するイベント テンプレートが含まれています。 このマニフェスト例では、次の 3 つのイベント、 StartEvent
、 SampleEventA
、 UnloadEvent
を定義します。
チャネルに加えて、イベントをレベルとキーワード (keyword)に関連付けることができます。 キーワードとレベルは、イベントを有効にし、イベントが公開されたときにフィルター処理するためのメカニズムを提供する方法を提供します。 キーワードを使用して、論理的に関連するイベントをまとめてグループ化できます。 レベルは、重大、エラー、警告、情報など、イベントの重大度または詳細度を示すために使用できます。 Winmeta.xml ファイルには、イベント属性の定義済みの値が含まれています。
イベント ペイロード (イベント メッセージ、およびデータ) のテンプレートを作成するときは、入力と出力の種類を指定する必要があります。 サポートされている型については、 InputType 複合型 (Windows)の「解説」セクションを参照してください。
<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<instrumentationManifest
xmlns="http://schemas.microsoft.com/win/2004/08/events"
xmlns:win="http://manifests.microsoft.com/win/2004/08/windows/events"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://schemas.microsoft.com/win/2004/08/events eventman.xsd"
>
<instrumentation>
<events>
<provider
guid="{b5a0bda9-50fe-4d0e-a83d-bae3f58c94d6}"
messageFileName="%SystemDrive%\ETWDriverSample\Eventdrv.sys"
name="Sample Driver"
resourceFileName="%SystemDrive%\ETWDriverSample\Eventdrv.sys"
symbol="DriverControlGuid"
>
<channels>
<importChannel
chid="SYSTEM"
name="System"
/>
</channels>
<templates>
<template tid="tid_load_template">
<data
inType="win:UInt16"
name="DeviceNameLength"
outType="xs:unsignedShort"
/>
<data
inType="win:UnicodeString"
name="name"
outType="xs:string"
/>
<data
inType="win:UInt32"
name="Status"
outType="xs:unsignedInt"
/>
</template>
<template tid="tid_unload_template">
<data
inType="win:Pointer"
name="DeviceObjPtr"
outType="win:HexInt64"
/>
</template>
</templates>
<events>
<event
channel="SYSTEM"
level="win:Informational"
message="$(string.StartEvent.EventMessage)"
opcode="win:Start"
symbol="StartEvent"
template="tid_load_template"
value="1"
/>
<event
channel="SYSTEM"
level="win:Informational"
message="$(string.SampleEventA.EventMessage)"
opcode="win:Info"
symbol="SampleEventA"
value="2"
/>
<event
channel="SYSTEM"
level="win:Informational"
message="$(string.UnloadEvent.EventMessage)"
opcode="win:Stop"
symbol="UnloadEvent"
template="tid_unload_template"
value="3"
/>
</events>
</provider>
</events>
</instrumentation>
<localization xmlns="http://schemas.microsoft.com/win/2004/08/events">
<resources culture="en-US">
<stringTable>
<string
id="StartEvent.EventMessage"
value="Driver Loaded"
/>
<string
id="SampleEventA.EventMessage"
value="IRP A Occurred"
/>
<string
id="UnloadEvent.EventMessage"
value="Driver Unloaded"
/>
</stringTable>
</resources>
</localization>
</instrumentationManifest>
3. メッセージ コンパイラを使用してインストルメンテーション マニフェストをコンパイルする (Mc.exe)
ソース コードを コンパイルする前に、 メッセージ コンパイラ (Mc.exe) を実行する必要があります。 メッセージ コンパイラは、Windows Driver Kit (WDK) に含まれています。 メッセージ コンパイラは、イベント プロバイダー、イベント属性、チャネル、およびイベントの定義を含むヘッダー ファイルを作成します。 このヘッダーファイルをソースコードに含める必要があります。 メッセージ コンパイラは、生成されたリソース コンパイラ スクリプト (*.rc) と、リソース コンパイラ スクリプトに含まれる生成された.bin ファイル (バイナリ リソース) も配置します。
この手順は、いくつかの方法でビルド プロセスの一部として含めることができます。
メッセージ コンパイラ タスクをドライバー プロジェクト ファイルに追加します (Eventdrv サンプルに示すように)。
Visual Studio を使用してインストルメンテーション マニフェストを追加し、メッセージ コンパイラのプロパティを構成します。
プロジェクト ファイルへのメッセージ コンパイラ タスクの追加 ビルド プロセスにメッセージ コンパイラを含める方法の例については、 Eventdrv サンプルのプロジェクト ファイルを参照してください。 Eventdrv.vcxproj ファイルには、メッセージ コンパイラを 呼び出す <MessageCompile> セクションがあります。 メッセージ コンパイラは、マニフェスト ファイル (evntdrv.xml) を入力として使用して、ヘッダー ファイル evntdrvEvents.h を生成します。 このセクションでは、生成された RC ファイルのパスも指定し、カーネル モードのログマクロを有効にします。 このセクションをコピーし、ドライバー プロジェクト ファイル (.vcxproj) に追加できます。
<MessageCompile Include="evntdrv.xml">
<GenerateKernelModeLoggingMacros>true</GenerateKernelModeLoggingMacros>
<HeaderFilePath>.\$(IntDir)</HeaderFilePath>
<GeneratedHeaderPath>true</GeneratedHeaderPath>
<WinmetaPath>"$(SDK_INC_PATH)\winmeta.xml"</WinmetaPath>
<RCFilePath>.\$(IntDir)</RCFilePath>
<GeneratedRCAndMessagesPath>true</GeneratedRCAndMessagesPath>
<GeneratedFilesBaseName>evntdrvEvents</GeneratedFilesBaseName>
<UseBaseNameOfInput>true</UseBaseNameOfInput>
</MessageCompile>
Eventdrv.sys サンプルをビルドすると、Visual Studio によってイベント トレースに必要なファイルが作成されます。 また、ドライバー プロジェクトのリソース ファイルの一覧にevntdrv.xml マニフェストを追加します。 マニフェストを長押し (または右クリック) すると、メッセージ コンパイラのプロパティ ページを表示できます。
Visual Studio を使用してインストルメンテーション マニフェストを追加する
インストルメンテーション マニフェストをドライバー プロジェクトに追加し、必要なリソースファイルとヘッダー ファイルをビルドするようにメッセージ コンパイラ プロパティを構成できます。
Visual Studio を使用してインストルメンテーション マニフェストをプロジェクトに追加するには
ソリューション エクスプローラーで、マニフェスト ファイルをドライバー プロジェクトに追加します。 リソース ファイル> を選択して長押し (または右クリック)、>既存の項目 (evntdrv.xmlや mydriver.man など) を追加します。
追加したファイルを長押し (または右クリック) し、プロパティ ページを使用して項目の種類を MessageCompile に変更し、 [適用]を選択します。
メッセージ コンパイラのプロパティが表示されます。 [全般] 設定で、次のオプションを設定し、 [適用]を選択します。
全般 設定 Generate Kernel Mode Logging Macros (カーネル モード ログ記録マクロを生成する) はい (-km) Use Base Name Of Input (入力のベース名を使う) はい (-b) [ファイル オプション]で、次のオプションを設定し、 [適用]を選択します。
ファイル オプション 設定 カウンターを格納するためのヘッダー ファイルを生成する はい Header File Path (ヘッダー ファイル パス) $(IntDir) Generated RC and Binary Message Files Path (生成される RC ファイルとバイナリ メッセージ ファイルのパス) はい RC File Path (RC ファイル パス) $(IntDir) Generated Files Base Name (生成されるファイルのベース名) $(Filename)
既定では、メッセージ コンパイラは、入力ファイルの基本名を生成するファイルのベース名として使用します。 ベース名を指定するには、 [生成されたファイルのベース名] (-z) フィールドを設定します。 Eventdr.sys サンプルでは、ベース名が evntdrvEvents に設定され、evntdrv.c に含まれるヘッダー ファイル evntdrvEvents.h の名前と一致します。
Note
生成された .rc ファイルを Visual Studio プロジェクトに含めない場合、マニフェスト ファイルのインストール時にリソースが見つからないというエラー メッセージが表示されることがあります。
4. 生成されたコードを追加してイベントを発生 (発行) します (イベントの登録、登録解除、および書き込み)
インストルメンテーション マニフェストで、イベント プロバイダーの名前とイベント記述子を定義しました。 メッセージ コンパイラを使用してインストルメンテーション マニフェストをコンパイルすると、メッセージ コンパイラによって、リソースを記述するヘッダー ファイルが生成され、イベントのマクロも定義されます。 次に、生成されたコードをドライバーに追加して、これらのイベントを発生させる必要があります。
ソース ファイルに、メッセージ コンパイラ (MC.exe) によって生成されるイベント ヘッダー ファイルを含めます。 たとえば、 Eventdrv サンプルのEvntdrv.c ソース ファイルには、前の手順で生成されたヘッダー ファイル (evntdrvEvents.h) が含まれています。
#include "evntdrvEvents.h"
ドライバーを登録および登録解除するマクロをイベント プロバイダーとして追加します。 たとえば、 Eventdrv サンプル のヘッダー ファイル (evntdrvEvents.h) では、メッセージ コンパイラはプロバイダーの名前に基づいてマクロを作成します。 マニフェストでは、 Eventdrv サンプル はプロバイダーの名前として "Sample Driver" という名前を使用します。 メッセージ コンパイラは、プロバイダーの名前とイベント マクロを組み合わせてプロバイダーを登録します。この場合は、 EventRegisterSample_Driverです。
// This is the generated header file envtdrvEvents.h // // ... // // // Register with ETW Vista + // #ifndef EventRegisterSample_Driver #define EventRegisterSample_Driver() McGenEventRegister(&DriverControlGuid, McGenControlCallbackV2, &DriverControlGuid_Context, &Sample_DriverHandle) #endif
EventRegister<プロバイダー> マクロを DriverEntry 関数に追加します。 この関数は、デバイス オブジェクトを作成して初期化するコードの後に追加します。 EventRegister<プロバイダー> 関数の呼び出しを EventUnregister<プロバイダー>の呼び出しと一致させる必要があることに注意してください。 ドライバーの アンロード* ルーチンでドライバーの登録を解除できます。
// DriverEntry function // ... // Register with ETW // EventRegisterSample_Driver();
生成されたコードをドライバーのソース ファイルに追加して、マニフェストで指定したイベントを書き込みます (発生させます)。 マニフェストからコンパイルしたヘッダー ファイルには、ドライバー用に生成されたコードが含まれています。 ヘッダー ファイルで定義されている EventWrite<イベント> 関数を使用して、トレース メッセージを ETW に公開します。 たとえば、次のコードは、 Eventdrv サンプルの evntdrvEvents.h で定義されているイベントのマクロを示しています。
これらのイベントを書き込むマクロは、
EventWriteStartEvent
、EventWriteSampleEventA
、EventWriteUnloadEvent
のように呼び出されます。 これらのマクロの定義でわかるように、マクロ定義には、イベントが有効かどうかを確認する EventEnabled<イベント> マクロが自動的に含まれます。 チェックでは、イベントが有効になっていない場合にペイロードをビルドする必要がなくなります。/// // This is the generated header file envtdrvEvents.h // // ... // // Enablement check macro for StartEvent // #define EventEnabledStartEvent() ((Sample_DriverEnableBits[0] & 0x00000001) != 0) // // Event Macro for StartEvent // #define EventWriteStartEvent(Activity, DeviceNameLength, name, Status)\ EventEnabledStartEvent() ?\ Template_hzq(Sample_DriverHandle, &StartEvent, Activity, DeviceNameLength, name, Status)\ : STATUS_SUCCESS\ // // Enablement check macro for SampleEventA // #define EventEnabledSampleEventA() ((Sample_DriverEnableBits[0] & 0x00000001) != 0) // // Event Macro for SampleEventA // #define EventWriteSampleEventA(Activity)\ EventEnabledSampleEventA() ?\ TemplateEventDescriptor(Sample_DriverHandle, &SampleEventA, Activity)\ : STATUS_SUCCESS\ // // Enablement check macro for UnloadEvent // #define EventEnabledUnloadEvent() ((Sample_DriverEnableBits[0] & 0x00000001) != 0) // // Event Macro for UnloadEvent // #define EventWriteUnloadEvent(Activity, DeviceObjPtr)\ EventEnabledUnloadEvent() ?\ Template_p(Sample_DriverHandle, &UnloadEvent, Activity, DeviceObjPtr)\ : STATUS_SUCCESS\
発生させるイベントの ソース コードに EventWrite<イベント> マクロを追加します。 たとえば、次のコード スニペットは、 Eventdrv サンプルの DriverEntry ルーチンを示しています。 DriverEntry には、ドライバーを ETW (EventRegisterSample_Driver) に登録するマクロと、ETW (EventWriteStartEvent) にドライバー イベントを書き込むマクロが含まれています。
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) /*++ Routine Description: Installable driver initialization entry point. This entry point is called directly by the I/O system. Arguments: DriverObject - pointer to the driver object RegistryPath - pointer to a unicode string representing the path to driver-specific key in the registry Return Value: STATUS_SUCCESS if successful STATUS_UNSUCCESSFUL otherwise --*/ { NTSTATUS Status = STATUS_SUCCESS; UNICODE_STRING DeviceName; UNICODE_STRING LinkName; PDEVICE_OBJECT EventDrvDeviceObject; WCHAR DeviceNameString[128]; ULONG LengthToCopy = 128 * sizeof(WCHAR); UNREFERENCED_PARAMETER (RegistryPath); KdPrint(("EventDrv: DriverEntry\n")); // // Create Dispatch Entry Points. // DriverObject->DriverUnload = EventDrvDriverUnload; DriverObject->MajorFunction[ IRP_MJ_CREATE ] = EventDrvDispatchOpenClose; DriverObject->MajorFunction[ IRP_MJ_CLOSE ] = EventDrvDispatchOpenClose; DriverObject->MajorFunction[ IRP_MJ_DEVICE_CONTROL ] = EventDrvDispatchDeviceControl; RtlInitUnicodeString( &DeviceName, EventDrv_NT_DEVICE_NAME ); // // Create the Device object // Status = IoCreateDevice( DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &EventDrvDeviceObject); if (!NT_SUCCESS(Status)) { return Status; } RtlInitUnicodeString( &LinkName, EventDrv_WIN32_DEVICE_NAME ); Status = IoCreateSymbolicLink( &LinkName, &DeviceName ); if ( !NT_SUCCESS( Status )) { IoDeleteDevice( EventDrvDeviceObject ); return Status; } // // Choose a buffering mechanism // EventDrvDeviceObject->Flags |= DO_BUFFERED_IO; // // Register with ETW // EventRegisterSample_Driver(); // // Log an Event with : DeviceNameLength // DeviceName // Status // // Copy the device name into the WCHAR local buffer in order // to place a NULL character at the end, since this field is // defined in the manifest as a NULL-terminated string if (DeviceName.Length <= 128 * sizeof(WCHAR)) { LengthToCopy = DeviceName.Length; } RtlCopyMemory(DeviceNameString, DeviceName.Buffer, LengthToCopy); DeviceNameString[LengthToCopy/sizeof(WCHAR)] = L'\0'; EventWriteStartEvent(NULL, DeviceName.Length, DeviceNameString, Status); return STATUS_SUCCESS; }
発生させるイベントの ソース コードに すべての EventWrite<イベント> マクロを追加します。 Eventdrv サンプル を調べて、ドライバーのソース コードでイベントに対して他の 2 つのマクロがどのように呼び出されるかを確認できます。
生成されたヘッダー ファイルから EventUnregister<プロバイダー> マクロを使用して、ドライバーをイベント プロバイダーとして登録解除します。
この関数呼び出しをドライバーのアンロード ルーチンに配置します。 EventUnregister<プロバイダー> マクロが呼び出された後、トレース呼び出しを行う必要はありません。 イベント プロバイダーの登録を解除しないと、プロセスに関連付けられているコールバック関数が無効になったため、プロセスがアンロードされたときにエラーが発生する可能性があります。
// DriverUnload function // ... // // Unregister the driver as an ETW provider // EventUnregisterSample_Driver();
5. ドライバーをビルドします
プロジェクトにインストルメント マニフェストを追加し、メッセージ コンパイラ (MC.exe) プロパティを構成している場合は、Visual Studio と MSBuild を使用してドライバー プロジェクトまたはソリューションをビルドできます。
Visual Studio でドライバーソリューションファイルを開きます。
[ビルド] メニューの [ソリューションのビルド] をクリックして、サンプルをビルドします。 ソリューションの構築の詳細については、「ドライバー のビルド」を参照してください。
6. マニフェストをインストールする
イベント コンシューマー (イベント ログなど) がイベント メタデータを含むバイナリの場所を見つけられるように、ターゲット システムにマニフェストをインストールする必要があります。 手順 3 でドライバー プロジェクトにメッセージ コンパイラ タスクを追加した場合、インストルメンテーション マニフェストがコンパイルされ、ドライバーのビルド時にリソース ファイルが生成されました。 Windows イベント コマンド ライン ユーティリティ (Wevtutil.exe) を使用してマニフェストをインストールします。 マニフェストをインストールする構文は次のとおりです。
wevtutil.exe im drivermanifest
たとえば、Evntdrv.sysサンプル ドライバーのマニフェストをインストールするには、管理者特権 (管理者として実行) を使用してコマンド プロンプト ウィンドウを開き、evntdrv.xml ファイルがあるディレクトリに移動し、次のコマンドを入力します。
Wevtutil.exe im evntdrv.xml
トレース セッションが完了したら、次の構文を使用してマニフェストをアンインストールします。
wevtutil.exe um drivermanifest
たとえば、 Eventdrv サンプルのマニフェストをアンインストールするには、次のようにします。
Wevtutil.exe um evntdrv.xml
7. ドライバーをテストして ETW サポートを確認する
ドライバーをインストールする。 ドライバーを実行してトレース アクティビティを生成します。 Event ビューアーで結果を表示する。 Tracelog を実行してから、イベント トレース ログを処理するためのツールである Tracerptを実行して、イベント トレース ログを制御、収集、および表示することもできます。