Tutorial: Escrever um Windows Driver "Hello World" (Kernel-Mode Driver Framework)
Este artigo descreve como escrever um pequeno de driver Universal do Windows usando Kernel-Mode Driver Framework (KMDF) e, em seguida, implantar e instalar o driver em um computador separado.
Pré-requisitos
Siga as etapas para instalar Windows Driver Kit (WDK). Ferramentas de Depuração para Windows são incluídas quando instalas o WDK.
Instale Visual Studio 2022. Ao instalar o Visual Studio 2022, selecione o Desenvolvimento de área de trabalho com C++ workload e, em seguida, nos Componentes individuais adicionar:
- MSVC v143 - VS 2022 C++ ARM64/ARM64EC bibliotecas mitigadas contra Spectre (Mais recente)
- MSVC v143 - VS 2022 C++ x64/x86 bibliotecas mitigadas contra Spectre (Mais recente)
- C++ ATL para as mais recentes ferramentas de compilação v143 com Spectre Mitigations (ARM64/ARM64EC)
- C++ ATL para as mais recentes ferramentas de compilação v143 com Spectre Mitigations (x86 & x64)
- C++ MFC para as mais recentes ferramentas de compilação v143 com Spectre Mitigations (ARM64/ARM64EC)
- C++ MFC para as mais recentes ferramentas de compilação v143 com Spectre Mitigations (x86 & x64)
- Windows Driver Kit
Criar e construir um driver
Abra o Microsoft Visual Studio. No menu Arquivo, escolha Novo > Projeto.
Na caixa de diálogo Criar um novo projeto, selecione C++ na lista suspensa esquerda, escolha Windows no meio e escolha Driver na lista suspensa direita.
Selecione driver do modo kernel, vazio (KMDF) na lista de tipos de projeto. Selecione Avançar.
Dica
Se você não conseguir encontrar modelos de projeto de driver no Visual Studio, a extensão WDK Visual Studio não foi instalada corretamente. Para resolver esse problema, inicie Visual Studio Installer, selecione Modificar, adicione de Kits de Driver do Windows na guia Componente Individual e selecione Modificar.
Na caixa de diálogo Configurar seu novo projeto, digite "KmdfHelloWorld" no campo Nome do projeto.
Observação
Ao criar um novo driver KMDF ou UMDF, você deve selecionar um nome de driver que tenha 32 caracteres ou menos. Este limite de comprimento é definido em wdfglobals.h.
No campo Local, insira o diretório onde você deseja criar o novo projeto.
Marque Colocar solução e projeto no mesmo diretório e selecione Criar.
Visual Studio cria um projeto e uma solução. Você pode vê-los na janela Explorador de Soluções. (Se a janela Gestor de Soluções não estiver visível, escolha Gestor de Soluções do menu Exibir.) A solução tem um projeto de driver chamado KmdfHelloWorld.
Na janela Solution Explorer, selecione com o botão direito do mouse solução 'KmdfHelloWorld' (1 de 1 projeto) e escolha Configuration Manager. Escolha uma configuração e plataforma para o projeto de driver. Por exemplo, escolha Depurar e x64.
Na janela Explorador de Soluções, clique com o botão direito no projeto KmdfHelloWorld, escolha Adicionare, em seguida, selecione Novo Item.
Na caixa de diálogo Adicionar Novo Item, digite "Driver.c".
Observação
A extensão do nome de arquivo é .c, não .cpp.
Selecione Adicionar. O ficheiro Driver.c é adicionado sob Source Files, conforme mostrado aqui.
Escreva o seu primeiro código de driver
Agora que você criou seu projeto Hello World vazio e adicionou o arquivo de origem Driver.c, você escreve o código mais básico necessário para que o driver seja executado implementando duas funções básicas de retorno de chamada de eventos.
Em Driver.c, comece incluindo estes cabeçalhos:
#include <ntddk.h> #include <wdf.h>
Dica
Se não for possível adicionar
Ntddk.h
, abra Configuration -> C/C++ -> General -> Additional Include Directories e adicioneC:\Program Files (x86)\Windows Kits\10\Include\<build#>\km
, substituindo<build#>
pelo diretório apropriado na instalação do WDK.Ntddk.h contém definições principais do kernel do Windows para todos os drivers, enquanto Wdf.h contém definições para drivers baseados no Windows Driver Framework (WDF).
Em seguida, forneça declarações para os dois retornos de chamada:
DRIVER_INITIALIZE DriverEntry; EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd;
Use o código a seguir para escrever seu DriverEntry:
NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath ) { // NTSTATUS variable to record success or failure NTSTATUS status = STATUS_SUCCESS; // Allocate the driver configuration object WDF_DRIVER_CONFIG config; // Print "Hello World" for DriverEntry KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: DriverEntry\n" )); // Initialize the driver configuration object to register the // entry point for the EvtDeviceAdd callback, KmdfHelloWorldEvtDeviceAdd WDF_DRIVER_CONFIG_INIT(&config, KmdfHelloWorldEvtDeviceAdd ); // Finally, create the driver object status = WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE ); return status; }
DriverEntry é o ponto de entrada para todos os drivers, como
Main()
é para muitos aplicativos de modo de usuário. O trabalho de DriverEntry é inicializar as estruturas e recursos do controlador como um todo. Neste exemplo, você imprimiu "Hello World" para DriverEntry, configurou o objeto de driver para registrar seu EvtDeviceAdd ponto de entrada do retorno de chamada, em seguida, criou o objeto de driver e retornou.O objeto de driver atua como o objeto pai para todos os outros objetos de estrutura que você pode criar em seu driver, que incluem objetos de dispositivo, filas de E/S, temporizadores, spinlocks e muito mais. Para obter mais informações sobre objetos de estrutura, consulte Introdução aos objetos do Framework.
Dica
Para DriverEntry, recomendamos fortemente manter o nome como "DriverEntry" para facilitar a análise de código e a depuração.
Em seguida, use o código a seguir para escrever seu KmdfHelloWorldEvtDeviceAdd:
NTSTATUS KmdfHelloWorldEvtDeviceAdd( _In_ WDFDRIVER Driver, _Inout_ PWDFDEVICE_INIT DeviceInit ) { // We're not using the driver object, // so we need to mark it as unreferenced UNREFERENCED_PARAMETER(Driver); NTSTATUS status; // Allocate the device object WDFDEVICE hDevice; // Print "Hello World" KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: KmdfHelloWorldEvtDeviceAdd\n" )); // Create the device object status = WdfDeviceCreate(&DeviceInit, WDF_NO_OBJECT_ATTRIBUTES, &hDevice ); return status; }
EvtDeviceAdd é invocado pelo sistema quando deteta que o dispositivo chegou. Seu trabalho é inicializar estruturas e recursos para esse dispositivo. Neste exemplo, você imprimiu uma mensagem "Hello World" para EvtDeviceAdd, criou o objeto device e retornou. Em outros drivers que você escreve, você pode criar filas de E/S para seu hardware, configurar um contexto de dispositivo espaço de armazenamento para informações específicas do dispositivo ou executar outras tarefas necessárias para preparar seu dispositivo.
Dica
Para o dispositivo adicionar retorno de chamada, observe como você o nomeou com o nome do driver como um prefixo (KmdfHelloWorldEvtDeviceAdd). Geralmente, recomendamos nomear as funções do seu motorista desta forma para diferenciá-las das funções de outros drivers. DriverEntry é o único que se deve nomear exatamente assim.
Seu Driver.c completo agora se parece com isto:
#include <ntddk.h> #include <wdf.h> DRIVER_INITIALIZE DriverEntry; EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd; NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath ) { // NTSTATUS variable to record success or failure NTSTATUS status = STATUS_SUCCESS; // Allocate the driver configuration object WDF_DRIVER_CONFIG config; // Print "Hello World" for DriverEntry KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: DriverEntry\n" )); // Initialize the driver configuration object to register the // entry point for the EvtDeviceAdd callback, KmdfHelloWorldEvtDeviceAdd WDF_DRIVER_CONFIG_INIT(&config, KmdfHelloWorldEvtDeviceAdd ); // Finally, create the driver object status = WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE ); return status; } NTSTATUS KmdfHelloWorldEvtDeviceAdd( _In_ WDFDRIVER Driver, _Inout_ PWDFDEVICE_INIT DeviceInit ) { // We're not using the driver object, // so we need to mark it as unreferenced UNREFERENCED_PARAMETER(Driver); NTSTATUS status; // Allocate the device object WDFDEVICE hDevice; // Print "Hello World" KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: KmdfHelloWorldEvtDeviceAdd\n" )); // Create the device object status = WdfDeviceCreate(&DeviceInit, WDF_NO_OBJECT_ATTRIBUTES, &hDevice ); return status; }
Salvar Driver.c.
Este exemplo ilustra um conceito fundamental de drivers: eles são uma "coleção de retornos de chamada" que, uma vez inicializados, ficam à espera que o sistema os chame quando precisa de algo. Uma chamada do sistema pode ser um novo evento de chegada do dispositivo, uma solicitação de E/S de um aplicativo de modo de usuário, um evento de desligamento de energia do sistema, uma solicitação de outro driver ou um evento de remoção surpresa quando um usuário desconecta o dispositivo inesperadamente. Felizmente, para dizer "Olá Mundo", você só precisava se preocupar com a criação de drivers e dispositivos.
Em seguida, você constrói seu driver.
Construa o controlador
Na janela Solution Explorer, selecione com o botão direito do mouse solução 'KmdfHelloWorld' (1 de 1 projeto) e escolha Configuration Manager. Escolha uma configuração e plataforma para o projeto de driver. Para este exercício, escolha Debug e x64.
Na janela Gestor de Soluções, clique com o botão direito em KmdfHelloWorld e escolha Propriedades. Em Wpp Tracing > All Options, defina Run Wpp tracing como Não. Selecione Aplicar e, em seguida, OK.
Para criar seu driver, escolha Build Solution no menu Build. Visual Studio mostra o progresso da compilação na janela Output. (Se a janela Saída não estiver visível, escolha Saída no menu Exibir.) Quando verificar que a solução foi criada com êxito, poderá fechar o Visual Studio.
Para ver o driver compilado, no Explorador de Ficheiros, vá para a pasta KmdfHelloWorld e, em seguida, para x64\Debug\KmdfHelloWorld. A pasta inclui:
- KmdfHelloWorld.sys -- o arquivo de driver de modo kernel
- KmdfHelloWorld.inf -- um arquivo de informações que o Windows usa quando você instala o driver
- KmdfHelloWorld.cat -- um arquivo de catálogo que o instalador usa para verificar a assinatura de teste do driver
Dica
Se você vir DriverVer set to a date in the future
ao criar seu driver, altere as configurações do projeto do driver para que o Inf2Cat defina /uselocaltime
. Para fazer isso, use Propriedades de Configuração->Inf2Cat->General->Use a hora local. Agora, tanto o Stampinf quanto o Inf2Cat usam a hora local.
Instalar o driver
Normalmente, quando você testa e depura um driver, o depurador e o driver são executados em computadores separados. O computador que executa o depurador é chamado de computador hoste o computador que executa o driver é chamado de computador de destino. O computador de destino também é chamado de computador de teste .
Até agora, você usou o Visual Studio para criar um driver no computador host. Agora você precisa configurar um computador de destino.
Siga as instruções em Preparar um computador para implantação e testes de drivers (WDK 10).
Dica
Ao seguir as etapas para provisionar o computador de destino automaticamente usando um cabo de rede, anote a porta e a chave. Você os usará mais tarde na etapa de depuração. Neste exemplo, você usa 50000 como a porta e 1.2.3.4 como a chave.
Em cenários reais de depuração de drivers, recomendamos o uso de uma chave gerada pelo KDNET. Para obter mais informações sobre como usar o KDNET para gerar uma chave aleatória, consulte o tópico Debug Drivers - Step by Step Lab (Sysvad Kernel Mode).
No computador host, abra sua solução no Visual Studio. Você pode clicar duas vezes no arquivo de solução, KmdfHelloWorld.sln, na pasta KmdfHelloWorld.
Na janela Explorador de Soluções, clique com o botão direito do rato no projeto KmdfHelloWorld e escolha Propriedades.
Vá para Instalação do Driver Implantação >.
Para Nome do Dispositivo de Destino, selecione o nome do computador que você configurou para teste e depuração. Neste exercício, usamos um computador chamado MyTestComputer.
Para garantir que você está testando a versão mais recente do driver, verifique Remover versões anteriores do driver antes da implantação.
Selecione Atualização do Driver ID de Hardwaree insira a ID de hardware do driver. Para este exercício, o ID de hardware é Root\KmdfHelloWorld. Selecione OK.
Observação
Neste exercício, o ID de hardware não identifica uma peça real de hardware. Ele identifica um dispositivo imaginário que recebe um lugar na árvore de dispositivos como filho do nó raiz. Em relação ao hardware real, não selecione Atualização de ID de Hardware do Driver; em vez disso, selecione Instalar e Verificar. Você vê o ID de hardware no arquivo de informações do driver (INF). Na janela Gerenciador de Soluções, vá para KmdfHelloWorld > Driver Filese clique duas vezes em KmdfHelloWorld.inf. O ID do hardware está localizado em [Standard.NT$ARCH$].
[Standard.NT$ARCH$] %KmdfHelloWorld.DeviceDesc%=KmdfHelloWorld_Device, Root\KmdfHelloWorld
No menu Build, escolha Implantar solução. O Visual Studio copia automaticamente os arquivos necessários para instalar e executar o driver no computador de destino. A implantação pode levar um ou dois minutos.
Quando você implanta um driver, os arquivos de driver são copiados para a pasta %Systemdrive%\drivertest\drivers no computador de teste. Se algo der errado durante a implantação, você poderá verificar se os arquivos são copiados para o computador de teste. Verifique se os arquivos .inf, .cat, test cert e .sys e quaisquer outros arquivos necessários estão presentes na pasta \drivertest\drivers do %systemdrive%.
Para obter mais informações sobre como implantar drivers, consulte Implantando um driver em um computador de teste.
Instale o driver
Com o driver Hello World implantado no computador de destino, agora você instala o driver. Quando você provisionou anteriormente o computador de destino com o Visual Studio usando a opção automática, o Visual Studio configurou o computador de destino para executar drivers assinados de teste como parte do processo de provisionamento. Agora você só precisa instalar o driver usando a ferramenta DevCon.
No computador host, navegue até a pasta Ferramentas na instalação do WDK e localize a ferramenta DevCon. Por exemplo, procure na seguinte pasta:
C:\Program Files (x86)\Windows Kits\10\Tools\x64\devcon.exe
Copie a ferramenta DevCon para o computador remoto.
No computador de destino, instale o driver navegando até a pasta que contém os arquivos do driver e executando a ferramenta DevCon.
Aqui está a sintaxe geral da ferramenta devcon que você usará para instalar o driver:
devcon instalar <arquivo INF><ID de hardware>
O arquivo INF necessário para instalar este driver é KmdfHelloWorld.inf. O arquivo INF contém o ID de hardware para instalar o binário do driver, KmdfHelloWorld.sys. Lembre-se de que o ID de hardware, localizado no arquivo INF, é Root\KmdfHelloWorld.
Abra uma janela de Prompt de Comando como Administrador. Navegue até a pasta que contém o arquivo de .sys do driver construído e digite este comando:
devcon instalar kmdfhelloworld.inf root\kmdfhelloworld
Se você receber uma mensagem de erro sobre devcon não sendo reconhecido, tente adicionar o caminho à ferramenta devcon. Por exemplo, se você copiou para uma pasta no computador de destino chamada C:\Tools, tente usar o seguinte comando:
c:\tools\devcon install kmdfhelloworld.inf root\kmdfhelloworld
Uma caixa de diálogo é exibida indicando que o driver de teste é um driver não assinado. Selecione Instalar este driver mesmo assim para continuar.
Depurar o driver
Agora que você instalou o driver KmdfHelloWorld no computador de destino, anexe um depurador remotamente do computador host.
No computador host, abra uma janela do Prompt de Comando como Administrador. Mude para o diretório WinDbg.exe. Você usa a versão x64 do WinDbg.exe do Windows Driver Kit (WDK) que foi instalado como parte da instalação do kit do Windows. Aqui está o caminho padrão para WinDbg.exe:
C:\Arquivos de Programas (x86)\Windows Kits\10\Debuggers\x64
Inicie o WinDbg para se conectar a uma sessão de depuração do kernel no computador de destino usando o seguinte comando. O valor da porta e da chave deve ser o mesmo que você usou para provisionar o computador de destino. Use 50000 para a porta e 1.2.3.4 para a chave, os valores usados durante a etapa de implantação. O sinalizador k indica que esta é uma sessão de depuração do kernel.
WinDbg -k net:port=50000,key=1.2.3.4
No menu Depurar, escolha Quebrar. O depurador no computador host invade o computador de destino. Na janela Debugger Command, podes ver o prompt de comando de depuração do kernel: kd>.
Neste ponto, você pode experimentar com o depurador inserindo comandos no prompt kd>. Por exemplo, você pode tentar estes comandos:
Para permitir que o computador de destino volte a executar, escolha Ir no menu Depurar ou pressione "g" e, depois, pressione "enter".
Para interromper a sessão de depuração, escolha Desanexar Depurador no menu Depurar.
Importante
Certifique-se de usar o comando "go" para permitir que o computador de destino seja executado novamente antes de sair do depurador, ou o computador de destino permanecerá sem responder à entrada do mouse e do teclado porque ainda está falando com o depurador.
Para obter um passo a passo detalhado do processo de depuração do driver, consulte Debug Universal Drivers - Step by Step Lab (Echo Kernel-Mode).
Para obter mais informações sobre depuração remota, consulte Depuração remota usando WinDbg.
Artigos relacionados
Ferramentas de Depuração para Windows
Debug Universal Drivers - Laboratório passo a passo (Echo Kernel-Mode)