共用方式為


將事件追蹤新增至內核模式驅動程式

本節說明如何使用 Windows 事件追蹤 (ETW) 核心模式 API,將事件追蹤新增至內核模式驅動程式。 ETW 核心模式 API 是 Windows Vista 引進,舊版操作系統不支援。 如果您的驅動程式需要支援 Windows 2000 和更新版本中的追蹤功能,請使用 WPP 軟體追蹤WMI 事件追蹤

提示

若要檢視示範如何使用 Windows 驅動程式套件 (WDK) 和 Visual Studio 實作 ETW 的範例程式代碼,請參閱 Eventdrv 範例

本節內容:

工作流程 - 將事件追蹤新增至內核模式驅動程式

1.決定要引發的事件類型,以及發佈事件的位置

2.建立可定義提供者、事件和通道的檢測指令清單

3.使用訊息編譯程式編譯檢測指令清單 (Mc.exe)

4.新增產生的程式代碼以引發事件(註冊、取消註冊和寫入事件)

5.建置驅動程式

6.安裝指令清單

7.測試驅動程式以驗證 ETW 支援

工作流程 - 將事件追蹤新增至內核模式驅動程式

流程圖,顯示將事件追蹤新增至內核模式驅動程序的程式。

1.決定要引發的事件類型,以及發佈事件的位置

開始撰寫程式代碼之前,您必須決定您希望驅動程式透過 Windows 事件追蹤記錄的事件類型(ETW)。 例如,您可能想要記錄事件,以協助您在散發驅動程式之後診斷問題,或可能會在開發驅動程式時協助您的事件。如需詳細資訊,請參閱 Windows 事件記錄檔參考

事件類型會以通道來識別。 頻道是系統管理、操作、分析或偵錯類型的具名事件串流,指向特定對象,類似於電視頻道。 通道會將事件從事件提供者傳遞至事件記錄檔和事件取用者。 如需詳細資訊,請參閱 定義通道

在開發期間,您最可能有興趣追蹤可協助您偵錯程式代碼的事件。 此相同通道可用於生產程序代碼,以協助針對部署驅動程序之後可能發生的問題進行疑難解答。 您可能也想要追蹤可用來測量效能的事件;這些事件可協助 IT 專業人員微調伺服器效能,並有助於找出網路瓶頸。

2.建立可定義提供者、事件和通道的檢測指令清單

檢測指令清單是一個 XML 檔案,可提供提供者將引發之事件的正式描述。 檢測指令清單會識別事件提供者、指定通道或通道(最多八個),並描述事件,以及事件使用的範本。 此外,檢測指令清單允許字串本地化,因此您可以將追蹤訊息當地語系化。 事件系統和事件取用者可以使用指令清單中提供的結構化 XML 數據來執行查詢和分析。

如需檢測指令清單的相關信息,請參閱撰寫檢測指令清單 (Windows)EventManifest 架構 (Windows) 和使用 Windows 事件記錄檔 (Windows)。

下列檢測指令清單顯示使用 「範例驅動程式」名稱的事件提供者。請注意,此名稱不一定與驅動程式二進位檔的名稱相同。 指令清單也會指定提供者的 GUID,以及訊息和資源檔案的路徑。 訊息和資源檔可讓 ETW 知道在何處找出譯碼和報告事件所需的資源。 這些路徑會指向驅動程式 (.sys) 檔案的位置。 驅動程式必須安裝在目標電腦上的指定目錄中。

此範例會使用具名通道系統,這是 Admin 類型事件的通道。此通道定義於 %WindowsSdkDir%\include\um 目錄中的 Winmeta.xml 檔案中,該檔案隨附於 Windows Driver Kit (WDK) 中。 系統通道會受到在系統服務帳戶下執行的應用程式所保護。 指令清單包含事件範本,描述發佈事件時所提供的數據類型,以及其靜態和動態內容。 這個範例指令清單會定義三個事件: StartEventSampleEventAUnloadEvent

除了通道之外,您還可以將事件與層級和關鍵詞產生關聯。 關鍵詞和層級提供啟用事件的方式,並提供在發佈事件時篩選事件的機制。 關鍵詞可用來將邏輯相關事件分組在一起。 層級可用來指出事件的嚴重性或詳細資訊,例如重大、錯誤、警告或資訊。 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 驅動程式套件 (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 將檢測指令清單新增至專案

  1. 在 方案總管 中,將指令清單檔新增至驅動程序專案。 選取並按住 [或以滑鼠右鍵按兩下] [資源檔 > ] [新增 > 現有專案 ] (例如,evntdrv.xml或 mydriver.man)。

  2. 選取並按住您剛才新增的檔案,並使用屬性頁將專案類型變更為 MessageCompile ,然後選取 [ 套用]。

  3. 訊息編譯程式屬性隨即出現。 在 [ 一般 ] 設定下,設定下列選項,然後選取 [ 套用]。

    一般 設定
    產生核心模式記錄宏 是 (公里)
    使用輸入的基底名稱 是 (-b)
  4. 在 [檔案選項] 底下,設定下列選項,然後選取 [套用]。

    檔案選項 設定
    產生包含計數器的頭檔
    標頭檔案路徑 $(IntDir)
    產生的 RC 和二進位訊息檔案路徑
    RC 檔案路徑 $(IntDir)
    產生的檔案基底名稱 $(檔名)

根據預設,訊息編譯程式會使用輸入檔的基底名稱作為其產生的檔案基底名稱。 若要指定基底名稱,請設定 [產生的檔案基底名稱 ](-z) 字段。 在Eventdr.sys範例中,基底名稱會設定為 evntdrvEvents ,使其符合 evntdrvEvents.h 頭文件的名稱,其包含在 evntdrv.c 中。

注意

如果您未在 Visual Studio 專案中包含產生的 .rc 檔案,當您安裝指令清單檔時,可能會收到有關找不到資源的錯誤訊息。

4.新增產生的程式代碼以引發事件(註冊、取消註冊和寫入事件)

在檢測指令清單中,您已定義事件提供者的名稱和事件描述元。 當您使用訊息編譯程式編譯檢測指令清單時,訊息編譯程式會產生一個頭檔來描述資源,也會定義事件的宏。 現在,您必須將產生的程式代碼新增至驅動程式,以引發這些事件。

  1. 在您的原始程序檔中,包含訊息編譯程式所產生的事件頭檔 (MC.exe)。 例如,在 Eventdrv 範例,Evntdrv.c 來源檔案包含上一個步驟中產生的頭檔 (evntdrvEvents.h):

    #include "evntdrvEvents.h"  
    
  2. 新增宏,以將驅動程序註冊並取消註冊為事件提供者。 例如,在 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<<提供者>的呼叫。 您可以在驅動程式的 Unload* 例程中取消註冊驅動程式。

       // DriverEntry function
       // ...
    
    
        // Register with ETW
        //
        EventRegisterSample_Driver();
    
  3. 將產生的程式代碼新增至驅動程式的來源檔案,以撰寫您在指令清單中指定的事件(引發)。 您從指令清單編譯的頭檔包含驅動程式產生的程式代碼。 使用頭檔中定義的 EventWrite<事件>函式,將追蹤訊息發佈至 ETW。 例如,下列程式代碼會顯示 Eventdrv 範例在 evntdrvEvents.h 中定義的事件宏。

    要寫入這些事件的宏稱為: EventWriteStartEventEventWriteSampleEventAEventWriteUnloadEvent。 如您在這些宏的定義中所見,宏定義會自動包含 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 範例 ,以查看驅動程式原始程式碼中事件呼叫其他兩個宏的方式。

  1. 從產生的頭檔使用 EventUnregister<提供者宏,將驅動程式取消註冊為事件提供者>

    將此函式呼叫放在驅動程式卸除例程中。 呼叫 EventUnregister<提供者>宏之後,不應進行追蹤呼叫。 當卸除進程時,無法取消註冊事件提供者可能會導致錯誤,因為與進程相關聯的任何回呼函式都不再有效。

        // DriverUnload function
        // ...
        //
    
        //  Unregister the driver as an ETW provider
        //
        EventUnregisterSample_Driver();
    

5.建置驅動程式

如果您已將檢測指令清單新增至專案,並已設定訊息編譯程式 (MC.exe) 屬性,您可以使用 Visual Studio 和 MSBuild 來建置驅動程式專案或方案。

  1. 在 Visual Studio 中開啟驅動程式解決方案。

  2. 選取 [建置方案],從 [建置] 功能表建置範例。 如需建置解決方案的詳細資訊,請參閱 建置驅動程式

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 支援

安裝驅動程式。 練習驅動程式以產生追蹤活動。 在 事件檢視器 中檢視結果。 您也可以執行 Tracelog,然後執行 Tracerpt,這是處理事件追蹤記錄的工具,以控制、收集及檢視事件追蹤記錄。