Archive for September, 2006

Legacy Drivers, WDM, WDF, KMDF, UMDF… WTF?!

25 de September de 2006

Estes dias me perguntaram qual a relação entre o sistema operacional e essa sopa de letrinhas que vem se acumulando com o passar dos builds. Neste post vou tentar resumir o que cada um destes modelos traz de novo e qual sua relação com o sistema operacional.

No inicio Deus criou a Terra, logo em seguida veio o Windows NT que introduziu o que chamamos de “Legacy Drivers”. Este modelo foi utilizado em todos os drivers do Windows NT e ainda existe muito dos conceitos dele nos novos modelos. Aqui os conceitos clássicos de IRP e I/O Manager foram adotados.

Com a chegada do Plug-and-Play, os sistemas Windows 98 e Windows 2000 foram os primeiros da família a implementar o WDM (Windows Driver Model). Este modelo tinha a pretensão de unificar os conceitos de desenvolvimento para drivers em ambas as plataformas (9x e NT). Quem já teve contato com VXDs sabe o quanto são diferentes dos conceitos dos drivers do Windows NT. Programadores de VXDs tiveram que aprender uma tecnologia completamente diferente. Com o WDM, além de poder utilizar os mesmos fontes para gerar drivers para ambas as plataformas, os mesmos binários podem ser utilizados em ambas plataformas. Ou seja, eu posso gerar um único binário que irá rodar tanto em Windows 98 como em Windows 2000. Além disso, o WDM é capaz de identificar e classificar dispositivos pela classe e sub-classe do Plug-and-Play, e assim, associar drivers e filtros a eles. Os Legacy Drivers ainda são suportados em Windows 2000 assim como os VXDs ainda são suportados em Windows 98. Na prática, para a plataforma NT, drivers WDM são Legacy Drivers que compartilham de novas regras para classificar e associar drivers aos dispositivos. Já com relação à plataforma 9x, a mudança foi drástica. As chamadas WDM são encaminhadas a um VXD que faz a tradução de IRP para IOP.

Com o fim decretado da plataforma 9x, a grande necessidade agora era tornar o desenvolvimento de drivers uma coisa menos dolorosa de se fazer. Isso foi prioridade no desenvolvimento do WDF (Windows Driver Foundation). Um comentário que não esqueço foi do vídeo sobre o KMFD (Kernel Mode Driver Framework) no Channel9 que diz: “É difícil escrever driver de Kernel Mode. Realmente difícil. De fato, é difícil acreditar o quão dificil é. Bem, o pessoal de Windows Driver tem trabalhado intensamente para tornar um pouco menos difícil (não fácil) escrever drivers de Kernel Mode que não derrubem seu sistema. Você sabe, telas azuis e coisas assim.”. O WDF traz muita coisa pronta, detalhes que eram repetitivos mesmo em WDM agora têm um comportamento padrão no WDF, mas que podem ser alterados à medida da necessidade. Aplicando uma abstração muito maior que permite, por exemplo, registrar rotinas de callback para manipular somente os eventos do seu interesse e deixar que o framework tome conta do gerenciamento de energia, que diga-se de passagem, né brinquedo não. Maior abstração não significa necessariamente menor controle. Ainda se pode ter acesso a todos os membros de uma IRP caso você pense que é fácil e queira fazer com suas próprias mãos. Eu li num destes artigos da NT Insider que um desenvolvedor ainda tem saber muito mesmo para fazer pouco em Kernel Mode e a Microsoft quer mudar isso. Desenvolvedores poderiam saber menos sobre tantas coisas envolvidas no desenvolvimento de drivers para fazer algo simples. O WDF é composto pelo KMDF e UMDF (User Mode Driver Framework). Isso mesmo!!! User Mode. Fazer com que drivers sejam menos nocivos ao sistema e conseqüentemente impedir que uma falha em um driver que não seja crítico ao sistema (como o seu MP3 player) cause uma tela azul e derrube todo o seu sistema. É realmente interessante ver como isso funciona. Seu driver roda como um serviço COM, utilizando uma conta de sistema e que utiliza um framework de comunicação que é baseado em COM para ter uma interação com o sistema como se estivesse em Kernel Mode. Existe um .ppt muito interessante que foi utilizado na WinHEC 2006 que demonstra simplificadamente como isso acontece. Ainda não é possível desenvolver drivers .Net por causa do impacto de performance no sistema, mas que isso é desejável, não há sombra de dúvidas. É claro que apenas alguns tipos de dispositivos poderão trabalhar em User Mode, mas para a crescente linha de dispositivos USB já é tudibão. O WDF virá junto com o DDK do Windows Vista, muitos (para não mencionar todos) dos drivers do novo sistema foram migrados para WDF, mas este não é um luxo apenas do Windows Vista. O KMDF pode ser instalado mesmo em Windows 2000 SP4 e o UMDF beta pode ser instalado no Windows XP SP2.

Getting Started…

11 de September de 2006

Vamos deixar de papo e vamos meter logo a mão na massa. Atendendo a alguns pedidos, neste post vou dar os passos para o desenvolvimento mínimo de um driver. Ao final deste post, teremos um módulo que será instalado e carregado no Kernel sem nenhum objetivo funcional. Ainda não vou falar sobre contexto de processo, Devices, Symbolic Links, IOCTL, IRP, DPC, ISR, FGTS, IR, IPVA e muito menos de IPTU. Falarei um pouco de cada coisa com o passar do tempo. Hoje vamos apenas compilar do zero um módulo vazio que poderá futuramente servir como ponto de partida para futuras experiências.

Para compilar um driver para Windows, vamos precisar minimamente do Windows Device Driver Kit (DDK). O DDK pode der obtido no site da Microsoft. Existem algumas alternativas para se obter o DDK, mas a mais econômica é optar pelo download do arquivo ISO. Quando este post foi criado, a versão disponibilizada no site era a “Windows Server 2003 SP1 DDK” com 230 MB.

Creio que não será necessário dar os mínimos detalhes da instalação do DDK, algo como “Leve o mouse até o botão cujo texto diz NEXT e pressione o botão esquerdo do mouse”. As opções defaults são mais que suficientes para nossas experiências. Caso você esteja com pouco espaço em disco, mude as opções do “Build Environments” para que sejam instalados apenas os ambientes do Windows 2000 como é exibido abaixo. Isso fará com que o espaço necessário caia de 628 MB para 246 MB. Se ainda assim você tiver problemas para instalar o DDK, talvez você deva considerar a ajuda profissional.

Terminada a instalação do DDK, já temos tudo que precisamos para criar drivers. Obviamente existem varias outras ferramentas que tornariam nosso trabalho mais confortável, mas neste post ficaremos apenas a pão e água.

Para nosso teste, crie uma pasta onde vamos colocar o fonte do driver (Ex: C:\Projects\Useless). Dentro desta pasta crie o arquivo texto de nome “Useless.c” e utilize o seu editor de texto do coração para digitar o fonte abaixo. Não vale utilizar o “Microsoft Word”, mas se você pensou nisso, utilize o “Notepad” e procure ajuda profissional.

#include <ntddk.h>
 
VOID OnDriverUnload(IN PDRIVER_OBJECT   pDriverObject);
 
/****
***     DriverEntry 
**
**      Ponto de entrada do nosso driver, tudo começa aqui,
**      depois vai enrolando, enrolando, ...
*/
 
NTSTATUS DriverEntry(IN PDRIVER_OBJECT  pDriverObject,
                     IN PUNICODE_STRING pusRegistryPath)
{
    //-f--> Se houver um depurador atachado ao nosso sistema
    //      poderemos ver a mensagem abaixo.
    DbgPrint("Cagamba, não é que funciona mesmo?\n");
 
    //-f--> Aqui informamos ao sistema que nosso driver é
    //      capaz de ser descarregado dinamicamente e que a
    //      rotina de CallBack que vai tratar a finalização
    //       de tudo é a OnDriverUnload.
    pDriverObject->DriverUnload = OnDriverUnload;
 
    //-f--> Ufa, conseguimos chegar até aqui, isso merece
    //      um retorno de sucesso para o sistema.
    return STATUS_SUCCESS;
}
 
 
/****
***     OnDriverUnload
**
**      Função de CallBack que trata das finalizações
**      necessárias para a descarga do driver.
*/
 
VOID OnDriverUnload(IN PDRIVER_OBJECT   pDriverObject)
{
    //-f--> Se houver um depurador atachado ao nosso sistema
    //      poderemos ver a mensagem abaixo.
    DbgPrint("Mas já? Eu nem fiz nada...\n");
}

Depois de fazer o “Copy” e “Paste” do fonte acima, vamos notar alguns pontos. Neste exemplo, lembre-se de salvar seu arquivo fonte com extensão “.c”, caso contrário teremos alguns problemas em exportar o nosso ponto de entrada. Daqui a alguns posts, pretendo demonstrar como ter arquivos “.cpp” e como utilizar a orientação a objeto na construção de drivers. Outro ponto importante a notar é que a atribuição feita para a função de DriverUnload é opcional. Caso você não queira prover uma função de callback para a descarga do seu driver, basta não fazer esta atribuição. Entretanto, nestas condições o driver não será apto de ser descarregado. Você precisará desta atribuição mesmo que seja para uma função vazia a fim de que seu driver possa ser desligado. Vamos falar disso com mais detalhes posteriormente (detalhes no DDK).

Ainda é necessário criar os arquivos que definem o projeto. No mesmo diretório do fonte, crie o arquivo de nome “makefile” e copie o texto abaixo para ele. Este arquivo simplesmente implementa o verdadeiro “makefile” que é utilizado para compilar diversos componentes do DDK.

!INCLUDE $(NTMAKEENV)\makefile.def

O arquivo de makefile nunca deve ser editado. Todas as configurações sobre qual o tipo de driver será compilado, quais os fontes irão compor o driver, quais bibliotecas serão utilizadas e outras tantas definições são especificadas no arquivo de nome “sources” que deve existir também no mesmo diretório com o conteúdo que segue abaixo. Em conjunto com o “makefile.def” do DDK, estes arquivos criam uma série de macros que facilitam muito a criação do arquivo de projeto tal como um “.vcproj” do Visual Studio. Posts sobre como utilizar o Visual Studio 2005 para compilar drivers também estão em minha lista de tarefas.

TARGETNAME=Useless
TARGETPATH=obj
TARGETTYPE=DRIVER
 
SOURCES=Useless.c

Existe uma infinidade de detalhes a serem comentados sobre um arquivo “sources”, por enquanto vamos nos limitar a saber que o nome do seu binário final é definido pela variável “TARGETNAME” e que a lista de fontes que compõem seu driver deve estar na variável “SOURCES” separados por espaço. (Muito mais detalhes no DDK).

Por fim, criados os arquivos, vamos compilá-los. Para isso devemos configurar as variáveis de ambiente para definir onde estão as libs, os headers e os binários que vão compilar seus fontes. A instalação do DDK disponibiliza um atalho em seu menu “Iniciar” que ponta para o arquivo “setenv.bat”. Este atalho cria uma janela de Prompt e configura o ambiente para a compilação do driver. Você encontra o atalho em “Build Environments” que foi instalado no seu menu iniciar. Deve haver uma versão “Checked” e uma versão “Free” deste atalho. Enquanto a versão “Checked” configura o ambiente para gerar drivers com informações de Debug, a versão “Free” gera os drivers para produção, que é o equivalente ao “Release”. Selecionando o “Checked Build” do “Windows 2000”, teremos uma janela de Prompt de comando com o título como mostra a figura abaixo, siga os mesmos passos da imagem abaixo para ter os mesmos resultados.

Chamando o comando “build” deveríamos ter os seguintes resultados.

Tudo pronto, agora só precisamos de uma vítima, ou melhor, um voluntário. Pode ser o micro de um priminho, um irmão caçula, um estagiário, contanto que não seja uma pessoa maior que você ou que possa fazer com que você perca seu emprego. Não aconselho utilizar sua própria máquina, podemos precisar dela mais tarde. Normalmente eu utilizo máquinas virtuais, são vítimas perfeitas. Em seguida, vamos instalar nosso driver da maneira mais simples que eu conheço. Vamos editar algumas linhas no registro e reiniciar nosso sistema. Existem APIs e ferramentas que registram o driver e o colocam para trabalhar na mesma hora sem reiniciar o sistema, mas lembrem-se, estamos a pão e água.

Primeiro vamos copiar o arquivo “Useless.sys” que foi gerado na subpasta “objchk_w2k_x86\i386” do nosso projeto para a pasta “System32\drivers”. Em seguida, utilizando o editor de registro, precisaremos criar a chave “Useless” dentro da chave “Services” do sistema e colocar os valores como demonstrado na figura abaixo.

Estas são as linhas mínimas para um driver no registro. O valor “Type” especifica um driver de Kernel, o valor “Start” indica que este terá seu inicio manualmente e finalmente o valor “ErrorControl” informa ao sistema como ele deve se comportar caso este driver falhe em sua carga (detalhes, detalhes e detalhes).

Neste ponto temos que reiniciar o sistema para que o sistema tome conhecimento deste novo módulo. Nosso driver não irá iniciar automaticamente. Depois de reiniciada, vá ao Prompt de inicie o driver com o comando “net start Useless”. Não é magnífico como não aconteceu nada? Isso significa que o driver está fazendo exatamente o que se propunha fazer, “Nada”. Você pode interromper o driver com o comando “net stop Useless”. Se tivéssemos depurador atachado ao sistema, teríamos as seguintes saídas.

Para os que nunca criaram um driver antes e não podem ver as saídas no WinDbg, fica muito frustrante, pois não aconteceu nada que comprovasse que nosso driver estivesse realmente carregado e rodando em Kernel. Para dar um pouco mais de ação a essa monotonia, modifique a função DriverEntry como mostra a figura abaixo. Recompile o driver, o substitua no diretório “System32\drivers” e finalmente reinicie a máquina.

NTSTATUS DriverEntry(IN PDRIVER_OBJECT  pDriverObject,
                     IN PUNICODE_STRING pusRegistryPath)
{
    //-f--> Diga olá à BSOD e vá se acostumando com ela...
    *(PVOID*)0x00000000 = 0;
 
    //-f--> Não vamos viver para ver isso.
    return STATUS_SUCCESS;
}

Neste caso, quando o driver for iniciado, este vai tentar escrever no endereço zero, uma exceção será lançada e aqui teremos nossa primeira Tela Azul da Morte (Blue Screen of Dead). Divirta-se!!!

Vou tentar intercalar posts para iniciantes com posts mais avançados a fim de tentar atrair a atenção de ambos os grupos. Neste post não temos nada muito útil, mas já temos a base para que pessoas que nunca tiveram contato com este tipo de desenvolvimento possam dar o primeito passo. Todo cidadão tem o direito de gerar uma Tela Azul. 😉