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)
7. Teste o driver para verificar o suporte ao ETW
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
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
, SampleEventA
e 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
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).
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.
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) 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.
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"
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();
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
,EventWriteSampleEventA
eEventWriteUnloadEvent
. 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.
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.
Abra a solução de driver no Visual Studio.
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.