Compartilhar via


Adicionando rastreamento de eventos a drivers de modo kernel

Esta seção descreve como usar a API do modo kernel ETW (Rastreamento de Eventos para Windows) para adicionar o rastreamento de eventos aos drivers do modo kernel. A API do modo kernel do ETW foi introduzida com o Windows Vista e não tem suporte em sistemas operacionais anteriores. Use o Rastreamento de Software WPP ou o Rastreamento de Eventos WMI se o driver precisar dar suporte à funcionalidade de rastreamento no Windows 2000 e posterior.

Dica

Para exibir o código de exemplo que mostra como implementar o ETW usando o WDK (Windows Driver Kit) e o Visual Studio, consulte o exemplo Eventdrv.

Nesta seção:

Fluxo de trabalho – adicionando rastreamento de eventos a drivers no modo kernel

1. Decida o tipo de eventos a serem levantados e onde publicá-los

2. Criar um manifesto de instrumentação que defina o provedor, os eventos e os canais

3. Compile o manifesto de instrumentação usando o compilador de mensagens (Mc.exe)

4. Adicione o código gerado para gerar (publicar) os eventos (registrar, cancelar o registro e gravar eventos)

5. Crie o driver

6. Instalar o manifesto

7. Teste o driver para verificar o suporte ao ETW

Fluxo de trabalho – adicionando rastreamento de eventos a drivers no modo kernel

Fluxograma que mostra o processo para adicionar rastreamento de eventos aos drivers do modo kernel.

1. Decida o tipo de eventos a serem levantados e onde publicá-los

Antes de começar a codificar, você deve decidir que tipo de eventos deseja que o driver faça logon por meio do ETW (Rastreamento de Eventos para Windows). Por exemplo, talvez você queira registrar eventos que podem ajudá-lo a diagnosticar problemas depois que o driver é distribuído ou eventos que podem ajudá-lo durante o desenvolvimento do driver. Para obter informações, consulte Referência do log de eventos do Windows.

Os tipos de eventos são identificados com canais. Um canal é um fluxo nomeado de eventos do tipo Admin, Operacional, Analítico ou Depuração direcionado a um público específico, semelhante a um canal de televisão. Um canal entrega os eventos do provedor de eventos para os logs de eventos e consumidores de eventos. Para obter informações, consulte Definição de canais.

Durante o desenvolvimento, você provavelmente está interessado em rastrear eventos que o ajudem a depurar seu código. Esse mesmo canal pode ser usado no código de produção para ajudar a solucionar problemas que podem aparecer depois que o driver é implantado. Você também pode querer rastrear eventos que podem ser usados para medir o desempenho; esses eventos podem ajudar os profissionais de TI a ajustar o desempenho do servidor e podem ajudar a identificar gargalos de rede.

2. Criar um manifesto de instrumentação que defina o provedor, os eventos e os canais

O manifesto de instrumentação é um arquivo XML que fornece uma descrição formal dos eventos que um provedor gerará. O manifesto de instrumentação identifica o provedor de eventos, especifica o canal ou canais (até oito) e descreve os eventos e modelos que os eventos usam. Além disso, o manifesto de instrumentação permite a localização de cadeia de caracteres, para que você possa localizar as mensagens de rastreamento. O sistema de eventos e os consumidores de eventos podem usar os dados XML estruturados fornecidos no manifesto para executar consultas e análises.

Para obter informações sobre o manifesto de instrumentação, consulte Escrevendo um manifesto de instrumentação (Windows), Esquema EventManifest (Windows) e Usando o log de eventos do Windows (Windows).

O manifesto de instrumentação a seguir mostra um provedor de eventos que usa o nome "Driver de Exemplo". Observe que esse nome não precisa ser o mesmo que o nome do binário do driver. O manifesto também especifica um GUID para o provedor e os caminhos para os arquivos de mensagem e recurso. Os arquivos de mensagem e recurso permitem que o ETW saiba onde localizar os recursos necessários para decodificar e relatar os eventos. Esses caminhos apontam para o local do arquivo de driver (.sys). O driver deve ser instalado no diretório especificado no computador de destino.

O exemplo usa o canal nomeado System, um canal para eventos do tipo Admin. Esse canal é definido no arquivo Winmeta.xml fornecido com o WDK (Windows Driver Kit) no diretório %WindowsSdkDir%\include\um. O canal do sistema é protegido para aplicativos em execução em contas de serviço do sistema. O manifesto inclui os modelos de evento que descrevem os tipos de dados fornecidos com os eventos quando eles são publicados, juntamente com seu conteúdo estático e dinâmico. Este manifesto de exemplo define três eventos: StartEvent, SampleEventAe UnloadEvent.

Além dos canais, você pode associar eventos a níveis e palavras-chave. Palavras-chave e níveis fornecem uma maneira de habilitar eventos e fornecer um mecanismo para filtrar eventos quando eles são publicados. As palavras-chave podem ser usadas para agrupar eventos logicamente relacionados. Um nível pode ser usado para indicar a gravidade ou o detalhamento de um evento, por exemplo, crítico, erro, aviso ou informativo. O arquivo Winmeta.xml contém valores predefinidos para atributos de evento.

Ao criar um modelo para a carga útil do evento (mensagem de evento e dados), você deve especificar os tipos de entrada e saída. Os tipos com suporte são descritos na seção Comentários de Tipo de Entrada Tipo Complexo (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 o manifesto de instrumentação usando o compilador de mensagens (Mc.exe)

O compilador de mensagens (Mc.exe) deve ser executado antes de compilar seu código-fonte. O compilador de mensagens está incluído no WDK (Windows Driver Kit). O compilador de mensagens cria um arquivo de cabeçalho que contém definições para o provedor de eventos, atributos de evento, canais e eventos. Você deve incluir esse arquivo de cabeçalho em seu código-fonte. O compilador de mensagens também coloca o script do compilador de recursos gerado (*.rc) e os arquivos de .bin gerados (recursos binários) que o script do compilador de recursos inclui.

Você pode incluir essa etapa como parte do processo de compilação de duas maneiras:

  • Adicionando a tarefa do compilador de mensagens ao arquivo de projeto do driver (conforme mostrado no exemplo Eventdrv).

  • Usando o Visual Studio para adicionar o manifesto de instrumentação e configurar as propriedades do Compilador de Mensagens.

Adicionando uma tarefa do compilador de mensagens ao arquivo de projeto Para obter um exemplo de como você pode incluir o compilador de mensagens no processo de compilação, examine o arquivo de projeto para obter o exemplo Eventdrv. No arquivo Eventdrv.vcxproj, há uma <seção MessageCompile> que chama o compilador de mensagens. O compilador de mensagens usa o arquivo de manifesto (evntdrv.xml) como entrada para gerar o arquivo de cabeçalho evntdrvEvents.h. Esta seção também especifica os caminhos para os arquivos RC gerados e habilita as macros de log do modo kernel. Você pode copiar esta seção e adicioná-la ao arquivo de projeto do driver (.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>

Quando você cria o exemplo de Eventdrv.sys, o Visual Studio cria os arquivos necessários para o rastreamento de eventos. Ele também adiciona o manifesto evntdrv.xml à lista de Arquivos de Recursos para o projeto de driver. Você pode selecionar e segurar (ou clicar com o botão direito do mouse) no manifesto para exibir as páginas de propriedades do Compilador de Mensagens.

Usando o Visual Studio para adicionar o manifesto de instrumentação

Você pode adicionar o manifesto de instrumentação ao projeto de driver e, em seguida, configurar as propriedades do compilador de mensagens para criar os arquivos de cabeçalho e recursos necessários.

Para adicionar o manifesto de instrumentação ao projeto usando o Visual Studio

  1. No Gerenciador de Soluções, adicione o arquivo de manifesto ao projeto de driver. Selecione e segure (ou clique com o botão direito do mouse) Arquivos > de recurso Adicionar > item existente (por exemplo, evntdrv.xml ou mydriver.man).

  2. Selecione e segure (ou clique com o botão direito do mouse) no arquivo que você acabou de adicionar e use as páginas de propriedades para alterar o tipo de item para MessageCompile e selecione Aplicar.

  3. As propriedades do Compilador de Mensagens são exibidas. Em Configurações gerais, defina as seguintes opções e selecione Aplicar.

    Geral Configuração
    Gerar macros de log do modo kernel Sim (-km)
    Usar nome base da entrada Sim (-b)
  4. Em Opções de arquivo, defina as opções a seguir e selecione Aplicar.

    Opções de arquivo Configuração
    Gerar arquivo de cabeçalho para conter o contador Sim
    Caminho do arquivo de cabeçalho $(IntDir)
    Caminho de arquivos de mensagens binárias e RC gerado Sim
    Caminho do arquivo RC $(IntDir)
    Nome Base dos Arquivos Gerados $(Nome do arquivo)

Por padrão, o compilador de mensagens usa o nome base do arquivo de entrada como o nome base dos arquivos que ele gera. Para especificar um nome base, defina o campo Nome Base dos Arquivos Gerados (-z). No exemplo Eventdr.sys, o nome base é definido como evntdrvEvents para que corresponda ao nome do arquivo de cabeçalho evntdrvEvents.h, que está incluído em evntdrv.c.

Observação

Se você não incluir o arquivo .rc gerado em seu projeto do Visual Studio, poderá receber mensagens de erro sobre recursos não encontrados ao instalar o arquivo de manifesto.

4. Adicione o código gerado para gerar (publicar) os eventos (registrar, cancelar o registro e gravar eventos)

No manifesto de instrumentação, você definiu os nomes do provedor de eventos e os descritores de eventos. Quando você compila o manifesto de instrumentação com o compilador de mensagens, o compilador de mensagens gera um arquivo de cabeçalho que descreve os recursos e também define macros para os eventos. Agora, você deve adicionar o código gerado ao driver para gerar esses eventos.

  1. No arquivo de origem, inclua o arquivo de cabeçalho de evento produzido pelo compilador de mensagens (MC.exe). Por exemplo, no exemplo Eventdrv, o arquivo de origem Evntdrv.c inclui o arquivo de cabeçalho (evntdrvEvents.h) que foi gerado na etapa anterior:

    #include "evntdrvEvents.h"  
    
  2. Adicione as macros que registram e cancelam o registro do driver como um provedor de eventos. Por exemplo, no arquivo de cabeçalho do exemplo Eventdrv (evntdrvEvents.h), o compilador de mensagens cria macros com base no nome do provedor. No manifesto, o exemplo Eventdrv usa o nome "Driver de Exemplo" como o nome do provedor. O compilador de mensagens combina o nome do provedor com a macro de evento para registrar o provedor, nesse 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
    

    Adicione a macro do provedor> EventRegister<à sua função DriverEntry. Adicione essa função após o código que cria e inicializa o objeto do dispositivo. Observe que você deve corresponder a chamada para a função do provedor> EventRegister<com uma chamada para o provedor> EventUnregister.< Você pode cancelar o registro do driver na rotina de descarga* do driver.

       // DriverEntry function
       // ...
    
    
        // Register with ETW
        //
        EventRegisterSample_Driver();
    
  3. Adicione o código gerado aos arquivos de origem do driver para gravar (gerar) os eventos especificados no manifesto. O arquivo de cabeçalho compilado do manifesto contém o código gerado para o driver. Use as funções de evento> EventWrite<definidas no arquivo de cabeçalho para publicar mensagens de rastreamento no ETW. Por exemplo, o código a seguir mostra as macros para eventos definidos em evntdrvEvents.h para o exemplo Eventdrv.

    As macros para gravar esses eventos são chamadas: EventWriteStartEvent, EventWriteSampleEventAe EventWriteUnloadEvent. Como você pode ver na definição dessas macros, a definição de macro inclui automaticamente uma macro de evento> EventEnabled<que verifica se o evento está habilitado. A verificação elimina a necessidade de criar a carga se o evento não estiver 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\
    
    

    Adicione as macros de evento> EventWrite<ao código-fonte para os eventos que você está gerando. Por exemplo, o snippet de código a seguir mostra a rotina DriverEntry do exemplo Eventdrv. O DriverEntry inclui as macros para registrar o driver no ETW (EventRegisterSample_Driver) e a macro para gravar o evento do driver no 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;
    }
    

Adicione todas as macros de eventos> EventWrite <ao código-fonte dos eventos que você está gerando. Você pode examinar o exemplo Eventdrv para ver como as outras duas macros são chamadas para os eventos no código-fonte do driver.

  1. Cancele o registro do driver como um provedor de eventos usando a macro do provedor> EventUnregister<do arquivo de cabeçalho gerado.

    Coloque essa chamada de função em sua rotina de descarregamento do driver. Nenhuma chamada de rastreamento deve ser feita depois que a macro do provedor> EventUnregister<for chamada. A falha ao cancelar o registro do provedor de eventos pode causar erros quando o processo é descarregado porque todas as funções de retorno de chamada associadas ao processo não são mais válidas.

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

5. Crie o driver

Se você adicionou o manifesto do instrumento ao projeto e configurou as propriedades do compilador de mensagens (MC.exe), poderá compilar o projeto ou a solução do driver usando o Visual Studio e o MSBuild.

  1. Abra a solução de driver no Visual Studio.

  2. Crie o exemplo no menu Compilar selecionando Compilar Solução. Para obter mais informações sobre como criar soluções, consulte Criando um driver.

6. Instalar o manifesto

Você deve instalar o manifesto no sistema de destino para que os consumidores de eventos (por exemplo, o Log de Eventos) possam encontrar o local do binário que contém os metadados do evento. Se você adicionou a tarefa do compilador de mensagens ao projeto do driver na Etapa 3, o manifesto de instrumentação foi compilado e os arquivos de recurso foram gerados quando você criou o driver. Use o Utilitário de Linha de Comando de Eventos do Windows (Wevtutil.exe) para instalar o manifesto. A sintaxe para instalar o manifesto é a seguinte:

wevtutil.exe im drivermanifest

Por exemplo, para instalar o manifesto para o driver de exemplo Evntdrv.sys, abra uma janela de prompt de comando com privilégios elevados (Executar como administrador), navegue até o diretório em que o arquivo evntdrv.xml está localizado e digite o seguinte comando:

Wevtutil.exe im evntdrv.xml

Quando a sessão de rastreamento for concluída, desinstale o manifesto usando a sintaxe a seguir.

wevtutil.exe um drivermanifest

Por exemplo, para desinstalar o manifesto para o exemplo Eventdrv:

Wevtutil.exe um evntdrv.xml

7. Teste o driver para verificar o suporte ao ETW

Instale o driver. Exercite o driver para gerar atividade de rastreamento. Exiba os resultados no Visualizador de Eventos. Você também pode executar o Tracelog e, em seguida, executar o Tracerpt, uma ferramenta para processar logs de rastreamento de eventos, para controlar, coletar e exibir os logs de rastreamento de eventos.