Agregar seguimiento de eventos a controladores de Kernel-Mode

En esta sección se describe cómo usar la API de modo kernel seguimiento de eventos para Windows (ETW) para agregar seguimiento de eventos a controladores en modo kernel. La API de modo kernel de ETW se introdujo con Windows Vista y no se admite en sistemas operativos anteriores. Usa el seguimiento de software de WPP o el seguimiento de eventos WMI si el controlador necesita admitir la funcionalidad de seguimiento en Windows 2000 y versiones posteriores.

Sugerencia

Para ver el código de ejemplo que muestra cómo implementar ETW mediante el Kit de controladores de Windows (WDK) y Visual Studio, consulte el ejemplo Eventdrv.

En esta sección:

Flujo de trabajo: agregar seguimiento de eventos a controladores de Kernel-Mode

1. Decida el tipo de eventos que se van a generar y dónde publicarlos

2. Crear un manifiesto de instrumentación que defina el proveedor, los eventos y los canales

3. Compile el manifiesto de instrumentación mediante el compilador de mensajes (Mc.exe)

4. Agregue el código generado para generar (publicar) los eventos (registrar, anular el registro y escribir eventos)

5. Compilación del controlador

6. Instalar el manifiesto

7. Probar el controlador para comprobar la compatibilidad con ETW

Flujo de trabajo: agregar seguimiento de eventos a controladores de Kernel-Mode

Diagrama de flujo que muestra el proceso para agregar seguimiento de eventos a controladores en modo kernel.

1. Decida el tipo de eventos que se van a generar y dónde publicarlos

Antes de empezar a codificar, debe decidir qué tipo de eventos desea que el controlador registre mediante seguimiento de eventos para Windows (ETW). Por ejemplo, es posible que desee registrar eventos que pueden ayudarle a diagnosticar problemas después de distribuir el controlador o eventos que pueden ayudarle a desarrollar el controlador. Para obtener información, vea Referencia del registro de eventos de Windows.

Los tipos de eventos se identifican con canales. Un canal es una secuencia con nombre de eventos de tipo Administración, Operativo, Analítico o Depuración dirigidos a un público específico, similar a un canal de televisión. Un canal entrega los eventos del proveedor de eventos a los registros de eventos y a los consumidores de eventos. Para obtener información, consulte Definición de canales.

Durante el desarrollo, probablemente le interesen los eventos de seguimiento que le ayudarán a depurar el código. Este mismo canal se puede usar en el código de producción para ayudar a solucionar problemas que pueden aparecer después de implementar el controlador. Es posible que también desee realizar un seguimiento de los eventos que se pueden usar para medir el rendimiento; estos eventos pueden ayudar a los profesionales de TI a ajustar el rendimiento del servidor y pueden ayudar a identificar cuellos de botella de red.

2. Crear un manifiesto de instrumentación que defina el proveedor, los eventos y los canales

El manifiesto de instrumentación es un archivo XML que proporciona una descripción formal de los eventos que generará un proveedor. El manifiesto de instrumentación identifica el proveedor de eventos, especifica el canal o los canales (hasta ocho) y describe los eventos y las plantillas que usan los eventos. Además, el manifiesto de instrumentación permite la localización de cadenas, por lo que puede localizar los mensajes de seguimiento. El sistema de eventos y los consumidores de eventos pueden usar los datos XML estructurados proporcionados en el manifiesto para realizar consultas y análisis.

Para obtener información sobre el manifiesto de instrumentación, vea Escritura de un manifiesto de instrumentación (Windows),Esquema EventManifest (Windows) y Uso del registro de eventos de Windows (Windows) .

El siguiente manifiesto de instrumentación muestra un proveedor de eventos que usa el nombre "Controlador de ejemplo". Tenga en cuenta que este nombre no tiene que ser el mismo que el nombre del archivo binario del controlador. El manifiesto también especifica un GUID para el proveedor y las rutas de acceso a los archivos de mensajes y recursos. Los archivos de mensajes y recursos permiten a ETW saber dónde localizar los recursos necesarios para descodificar e informar de los eventos. Estas rutas de acceso apuntan a la ubicación del archivo del controlador (.sys). El controlador debe instalarse en el directorio especificado en el equipo de destino.

En el ejemplo se usa el canal con nombre System, un canal para eventos de tipo Administración. Este canal se define en el archivo Winmeta.xml que se proporciona con el Kit de controladores de Windows (WDK) en el directorio%WindowsSdkDir%\include\um. El canal del sistema está protegido para las aplicaciones que se ejecutan en cuentas de servicio del sistema. El manifiesto incluye las plantillas de eventos que describen los tipos de datos proporcionados con los eventos cuando se publican, junto con su contenido estático y dinámico. Este manifiesto de ejemplo define tres eventos: StartEvent, SampleEventAy UnloadEvent.

Además de los canales, puede asociar eventos con niveles y palabras clave. Las palabras clave y los niveles proporcionan una manera de habilitar eventos y proporcionar un mecanismo para filtrar eventos cuando se publican. Las palabras clave se pueden usar para agrupar eventos relacionados lógicamente. Se puede usar un nivel para indicar la gravedad o detalle de un evento, por ejemplo, crítico, error, advertencia o informativo. El archivo Winmeta.xml contiene valores predefinidos para los atributos de evento.

Al crear una plantilla para la carga del evento (mensaje de evento y datos), debe especificar los tipos de entrada y salida. Los tipos admitidos se describen en la sección Comentarios del tipo complejo 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. Compile el manifiesto de instrumentación mediante el compilador de mensajes (Mc.exe)

El compilador de mensajes (Mc.exe) debe ejecutarse antes de compilar el código fuente. El compilador de mensajes se incluye en el Kit de controladores de Windows (WDK). El compilador de mensajes crea un archivo de encabezado que contiene definiciones para el proveedor de eventos, los atributos de evento, los canales y los eventos. Debe incluir este archivo de encabezado en el código fuente. El compilador de mensajes también coloca el script del compilador de recursos generado (*.rc) y los archivos .bin generados (recursos binarios) que incluye el script del compilador de recursos.

Puede incluir este paso como parte del proceso de compilación de dos maneras:

  • Agregar la tarea del compilador de mensajes al archivo de proyecto del controlador (como se muestra en el ejemplo eventdrv).

  • Usar Visual Studio para agregar el manifiesto de instrumentación y configurar las propiedades del compilador de mensajes.

Adición de una tarea del compilador de mensajes al archivo de proyecto Para obtener un ejemplo de cómo puede incluir el compilador de mensajes en el proceso de compilación, examine el archivo de proyecto del ejemplo Eventdrv. En el archivo Eventdrv.vcxproj, hay una <sección MessageCompile> que llama al compilador de mensajes. El compilador de mensajes usa el archivo de manifiesto (evntdrv.xml) como entrada para generar el archivo de encabezado evntdrvEvents.h. En esta sección también se especifican las rutas de acceso de los archivos RC generados y se habilitan las macros de registro del modo kernel. Puede copiar esta sección y agregarla al archivo de proyecto de controlador (.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>

Al compilar el ejemplo de Eventdrv.sys, Visual Studio crea los archivos necesarios para el seguimiento de eventos. También agrega el manifiesto evntdrv.xml a la lista de archivos de recursos para el proyecto de controlador. Puede seleccionar y mantener presionado (o hacer clic con el botón derecho) en el manifiesto para ver las páginas de propiedades del compilador de mensajes.

Uso de Visual Studio para agregar el manifiesto de instrumentación

Puede agregar el manifiesto de instrumentación al proyecto de controlador y, a continuación, configurar las propiedades del compilador de mensajes para compilar los archivos de encabezado y recursos necesarios.

Para agregar el manifiesto de instrumentación al proyecto mediante Visual Studio

  1. En el Explorador de soluciones, agregue el archivo de manifiesto al proyecto de controlador. Seleccione y mantenga presionado (o haga clic con el botón derecho) Agregar >> elemento existente (por ejemplo, evntdrv.xml o mydriver.man).

  2. Seleccione y mantenga presionado (o haga clic con el botón derecho) en el archivo que acaba de agregar y use las páginas de propiedades para cambiar el tipo de elemento a MessageCompile y seleccione Aplicar.

  3. Aparecen las propiedades del compilador de mensajes. En Configuración general , establezca las siguientes opciones y, a continuación, seleccione Aplicar.

    General Parámetro
    Generar macros de registro del modo kernel Sí (-km)
    Usar el nombre base de la entrada Sí (-b)
  4. En Opciones de archivo, establezca las siguientes opciones y, a continuación, seleccione Aplicar.

    Opciones de archivo Parámetro
    Generación de un archivo de encabezado para contener el contador
    Ruta de acceso del archivo de encabezado $(IntDir)
    Ruta de acceso de archivos de mensajes binarios y RC generados
    Ruta de acceso del archivo RC $(IntDir)
    Nombre base de archivos generados $(Filename)

De forma predeterminada, el compilador de mensajes usa el nombre base del archivo de entrada como nombre base de los archivos que genera. Para especificar un nombre base, establezca el campo Nombre base de archivos generados (-z). En el ejemplo de Eventdr.sys, el nombre base se establece en evntdrvEvents para que coincida con el nombre del archivo de encabezado evntdrvEvents.h, que se incluye en evntdrv.c.

Nota:

Si no incluye el archivo .rc generado en el proyecto de Visual Studio, es posible que reciba mensajes de error sobre los recursos no encontrados al instalar el archivo de manifiesto.

4. Agregue el código generado para generar (publicar) los eventos (registrar, anular el registro y escribir eventos)

En el manifiesto de instrumentación, definió los nombres del proveedor de eventos y los descriptores de eventos. Al compilar el manifiesto de instrumentación con el compilador de mensajes, el compilador de mensajes genera un archivo de encabezado que describe los recursos y también define macros para los eventos. Ahora, debe agregar el código generado al controlador para generar estos eventos.

  1. En el archivo de origen, incluya el archivo de encabezado de evento generado por el compilador de mensajes (MC.exe). Por ejemplo, en el ejemplo Eventdrv, el archivo de código fuente Evntdrv.c incluye el archivo de encabezado (evntdrvEvents.h) que se generó en el paso anterior:

    #include "evntdrvEvents.h"  
    
  2. Agregue las macros que registran y anulan el registro del controlador como proveedor de eventos. Por ejemplo, en el archivo de encabezado del ejemplo Eventdrv (evntdrvEvents.h), el compilador de mensajes crea macros basadas en el nombre del proveedor. En el manifiesto, el ejemplo Eventdrv usa el nombre "Controlador de ejemplo" como nombre del proveedor. El compilador de mensajes combina el nombre del proveedor con la macro de evento para registrar el proveedor, en este caso, 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
    

    Agregue la macro del proveedor> EventRegister< a la función DriverEntry. Agregue esta función después del código que crea e inicializa el objeto de dispositivo. Tenga en cuenta que debe coincidir con la llamada a la función del proveedor> EventRegister< con una llamada al proveedor> EventUnregister<. Puede anular el registro del controlador en la rutina Unload* del controlador.

       // DriverEntry function
       // ...
    
    
        // Register with ETW
        //
        EventRegisterSample_Driver();
    
  3. Agregue el código generado a los archivos de código fuente del controlador para escribir (generar) los eventos especificados en el manifiesto. El archivo de encabezado que ha compilado a partir del manifiesto contiene el código generado para el controlador. Use las funciones de evento> EventWrite< definidas en el archivo de encabezado para publicar mensajes de seguimiento en ETW. Por ejemplo, el código siguiente muestra las macros de los eventos definidos en evntdrvEvents.h para el ejemplo Eventdrv.

    Las macros para escribir estos eventos se denominan : EventWriteStartEvent, EventWriteSampleEventAy EventWriteUnloadEvent. Como puede ver en la definición de estas macros, la definición de macro incluye automáticamente una macro de evento> EventEnabled< que comprueba si el evento está habilitado. La comprobación elimina la necesidad de compilar la carga si el evento no está habilitado.

    
    ///
    // 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\
    
    

    Agregue las macros deevento> EventWrite< al código fuente para los eventos que va a generar. Por ejemplo, el siguiente fragmento de código muestra la rutina DriverEntry del ejemplo Eventdrv. DriverEntry incluye las macros para registrar el controlador con ETW (EventRegisterSample_Driver) y la macro para escribir el evento de controlador en 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;
    }
    

Agregue todas las macros de evento> EventWrite< al código fuente de los eventos que está generando. Puede examinar el ejemplo eventdrv para ver cómo se llaman las otras dos macros para los eventos del código fuente del controlador.

  1. Anule el registro del controlador como proveedor de eventos mediante la macro delproveedor> EventUnregister< del archivo de encabezado generado.

    Coloque esta llamada de función en la rutina de descarga del controlador. No se debe realizar ninguna llamada de seguimiento después de llamar a la macro del proveedor> EventUnregister<. Si no se anula el registro del proveedor de eventos, se pueden producir errores cuando se descarga el proceso porque las funciones de devolución de llamada asociadas al proceso ya no son válidas.

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

5. Compilación del controlador

Si ha agregado el manifiesto de instrumento al proyecto y ha configurado las propiedades del compilador de mensajes (MC.exe), puede compilar el proyecto de controlador o la solución mediante Visual Studio y MSBuild.

  1. Abra la solución de controlador en Visual Studio.

  2. Compile el ejemplo en el menú Compilar seleccionando Compilar solución. Para obtener más información sobre la creación de soluciones, vea Building a Driver.

6. Instalar el manifiesto

Debe instalar el manifiesto en el sistema de destino para que los consumidores de eventos (por ejemplo, el registro de eventos) puedan encontrar la ubicación del binario que contiene los metadatos del evento. Si agregó la tarea del compilador de mensajes al proyecto de controlador en el paso 3, el manifiesto de instrumentación se compiló y los archivos de recursos se generaron al compilar el controlador. Use la Utilidad de línea de comandos de eventos de Windows (Wevtutil.exe) para instalar el manifiesto. La sintaxis para instalar el manifiesto es la siguiente:

wevtutil.exe imdrivermanifest

Por ejemplo, para instalar el manifiesto para el controlador de ejemplo de Evntdrv.sys, abra una ventana del símbolo del sistema con privilegios elevados (Ejecutar como administrador) navegue hasta el directorio donde se encuentra el archivo evntdrv.xml y escriba el siguiente comando:

Wevtutil.exe im evntdrv.xml

Una vez completada la sesión de seguimiento, desinstale el manifiesto con la siguiente sintaxis.

wevtutil.exe umdrivermanifest

Por ejemplo, para desinstalar el manifiesto del ejemplo Eventdrv:

Wevtutil.exe um evntdrv.xml

7. Probar el controlador para comprobar la compatibilidad con ETW

Instale el controlador. Ejercicio del controlador para generar actividad de seguimiento. Vea los resultados en el Visor de eventos. También puede ejecutar Tracelog y, a continuación, ejecutar Tracerpt, una herramienta para procesar registros de seguimiento de eventos, controlar, recopilar y ver los registros de seguimiento de eventos.