將事件追蹤新增至 Kernel-Mode 驅動程式

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

提示

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

本節內容:

工作流程 - 將事件追蹤新增至 Kernel-Mode 驅動程式

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

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

3.使用訊息編譯器 (Mc.exe) 編譯檢測資訊清單

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

5.建置驅動程式

6.安裝資訊清單

7.測試驅動程式以確認 ETW 支援

工作流程 - 將事件追蹤新增至 Kernel-Mode 驅動程式

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

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

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

事件種類會以通道識別。 通道是一種具名的事件串流,類型為 管理員、Operational、Analysis 或 Debug 導向特定物件,類似于電視頻道。 通道會將事件提供者的事件傳遞至事件記錄檔和事件取用者。 如需詳細資訊,請參閱 定義通道

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

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

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

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

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

此範例會使用具名通道系統,這是類型為 管理員事件的通道。此通道定義于 Windows Driver Kit (WindowsSdkDir%\include\um 目錄中的 WDK) 提供的 Winmeta.xml 檔案中。 系統通道會保護在系統服務帳戶下執行的應用程式。 資訊清單包含事件範本,其描述發行事件時所提供的資料類型,以及其靜態和動態內容。 此範例資訊清單會定義三個事件: 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. 訊息編譯器屬性隨即出現。 在 [ 一般 設定] 下,設定下列選項,然後選取 [ 套用]。

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

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

根據預設,訊息編譯器會使用輸入檔的基底名稱做為其產生的檔案基底名稱。 若要指定基底名稱,請設定 [產生的檔案基底名稱 ] (-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 imdrivermanifest

例如,若要安裝 Evntdrv.sys 範例驅動程式的資訊清單,請開啟具有提高許可權的命令提示字元視窗, (以 系統管理員 身分執行,) 流覽至 evntdrv.xml 檔案所在的目錄,然後輸入下列命令:

Wevtutil.exe im evntdrv.xml

當您的追蹤會話完成時,請使用下列語法卸載資訊清單。

wevtutil.exe umdrivermanifest

例如,若要卸載 Eventdrv 範例的資訊清單:

Wevtutil.exe um evntdrv.xml

7.測試驅動程式以確認 ETW 支援

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