Добавление трассировки событий в драйверы Kernel-Mode

В этом разделе описывается использование API трассировки событий Для Windows (ETW) в режиме ядра для добавления трассировки событий в драйверы режима ядра. API режима ядра ETW появился в Windows Vista и не поддерживается в более ранних операционных системах. Используйте трассировку программного обеспечения WPP или трассировку событий WMI , если драйвер должен поддерживать возможности трассировки в Windows 2000 и более поздних версиях.

Совет

Чтобы просмотреть пример кода, демонстрирующий реализацию трассировки событий Windows с помощью пакета драйверов Windows (WDK) и Visual Studio, см. пример Eventdrv.

Содержание

Рабочий процесс. Добавление трассировки событий в драйверы Kernel-Mode

1. Определите тип событий для создания и место их публикации

2. Создание манифеста инструментирования, который определяет поставщика, события и каналы

3. Компиляция манифеста инструментирования с помощью компилятора сообщений (Mc.exe)

4. Добавьте созданный код для создания (публикации) событий (регистрация, отмена регистрации и запись событий)

5. Сборка драйвера

6. Установка манифеста

7. Протестируйте драйвер, чтобы проверить поддержку трассировки событий Windows

Рабочий процесс. Добавление трассировки событий в драйверы Kernel-Mode

Блок-схема, показывающая процесс добавления трассировки событий в драйверы режима ядра.

1. Определите тип событий для создания и место их публикации

Прежде чем приступить к написанию кода, необходимо решить, какие типы событий драйвер должен регистрировать с помощью трассировки событий Windows (ETW). Например, может потребоваться регистрировать события, которые могут помочь в диагностике проблем после распространения драйвера, или события, которые могут помочь вам при разработке драйвера. Дополнительные сведения см. в справочнике по журналу событий Windows.

Типы событий идентифицируются с помощью каналов. Канал — это именованный поток событий типа Администратор, операционных, аналитических или отладочных, направленных на определенную аудиторию, аналогично телевизионному каналу. Канал доставляет события от поставщика событий в журналы событий и потребители событий. Дополнительные сведения см. в разделе Определение каналов.

Во время разработки вы, скорее всего, заинтересованы в трассировке событий, которые помогут отлаживать код. Этот же канал можно использовать в рабочем коде для устранения неполадок, которые могут возникнуть после развертывания драйвера. Также может потребоваться трассировка событий, которые можно использовать для измерения производительности; Эти события помогут ИТ-специалистам точно настроить производительность сервера и выявить узкие места в сети.

2. Создание манифеста инструментирования, который определяет поставщика, события и каналы

Манифест инструментирования — это XML-файл, предоставляющий официальное описание событий, которые будет вызывать поставщик. Манифест инструментирования идентифицирует поставщика событий, задает канал или каналы (до восьми), описывает события и шаблонов, используемых событиями. Кроме того, манифест инструментирования позволяет локализовать строки, что позволяет локализовать сообщения трассировки. Система событий и потребители событий могут использовать структурированные XML-данные, предоставленные в манифесте, для выполнения запросов и анализа.

Дополнительные сведения о манифесте инструментирования см. в разделах Написание манифеста инструментирования (Windows),Схема EventManifest (Windows) и Использование журнала событий Windows (Windows).

В следующем манифесте инструментирования показан поставщик событий, использующий имя "Sample Driver". Обратите внимание, что это имя необязательно должно совпадать с именем двоичного файла драйвера. Манифест также указывает GUID для поставщика и пути к файлам сообщений и ресурсов. Файлы сообщений и ресурсов позволяют трассировке событий Windows определить, где найти ресурсы, необходимые для декодирования событий и создания отчетов о событиях. Эти пути указывают на расположение файла драйвера (.sys). Драйвер должен быть установлен в указанном каталоге на целевом компьютере.

В примере используется именованный канал System, канал для событий типа Администратор. Этот канал определен в файле Winmeta.xml, который предоставляется вместе с комплектом драйверов Windows (WDK) в каталоге%WindowsSdkDir%\include\um. Системный канал защищен для приложений, работающих под учетными записями системных служб. Манифест включает шаблоны событий, описывающие типы данных, предоставляемых вместе с событиями при их публикации, а также их статическое и динамическое содержимое. В этом примере манифеста определяются три события: StartEvent, SampleEventAи UnloadEvent.

Помимо каналов, события можно связывать с уровнями и ключевыми словами. Ключевые слова и уровни предоставляют способ включения событий и механизм фильтрации событий при их публикации. Ключевые слова можно использовать для группировки логически связанных событий. Уровень можно использовать для указания серьезности или детализации события, например критического, ошибки, предупреждения или информационной информации. Файл 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 и двоичным файлам сообщений Да
    Путь к файлу-кандидату $(IntDir)
    Базовое имя созданных файлов $(Имя файла)

По умолчанию компилятор сообщений использует базовое имя входного файла в качестве базового имени создаваемых файлов. Чтобы указать базовое имя, задайте поле Созданное базовое имя файлов (-z). В примере Eventdr.sys базовое имя имеет значение evntdrvEvents , чтобы оно совпадало с именем файла заголовка evntdrvEvents.h, включенного в evntdrv.c.

Примечание

Если не включить созданный RC-файл в проект Visual Studio, при установке файла манифеста могут появиться сообщения об ошибках о том, что ресурсы не найдены.

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<. Вы можете отменить регистрацию драйвера в процедуре выгрузки* драйвера.

       // DriverEntry function
       // ...
    
    
        // Register with ETW
        //
        EventRegisterSample_Driver();
    
  3. Добавьте созданный код в исходные файлы драйвера для записи (создания) событий, указанных в манифесте. Файл заголовка, скомпилированный из манифеста, содержит созданный код для драйвера. Используйте функции событий> EventWrite,< определенные в файле заголовка, для публикации сообщений трассировки в трассировке событий Windows. Например, в следующем коде показаны макросы для событий, определенных в evntdrvEvents.h для примера Eventdrv.

    Макросы для записи этих событий называются : 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< в исходный код для вызываемого события. Например, в следующем фрагменте кода показана подпрограмма DriverEntry из примера Eventdrv. 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. Протестируйте драйвер, чтобы проверить поддержку трассировки событий Windows

Установите драйвер. Выполните драйвер для создания действия трассировки. Просмотрите результаты в Просмотр событий. Вы также можете запустить Tracelog, а затем запустить Tracerpt, средство для обработки журналов трассировки событий, чтобы управлять, собирать и просматривать журналы трассировки событий.