Blog

  • Wow!! DDKBuild fora de Vista

    A cerca de um ano atrás, eu estava lendo o Windows Internals quando cheguei na parte em que o livro fala sobre o Wow64, então pensei comigo mesmo: “Estou até vendo, isso ainda vai dar pano para a manga.”

    Essa semana instalei uma cópia do Vista x64 em minha máquina para fazer alguns testes e tentar adiantar os problemas que eu teria quando ele fosse lançado. Para não dizer que não tive nenhum problema, tive que configurar os atalhos para alguns programas com o modo de compatibilidade setado para Windows XP SP2. Isso porque estes programas exibiam a mensagem: “Este programa não é compatível com Windows 6.0”

    Instalado o Visual Studio 2005, o DDK do Windows 2003 Server e o DDKBuild, tudo estava funcionando muito bem. Um projeto era criado, compilado e depurado no Visual Studio sem problemas. Também compilei um dos exemplos do DDK na linha de comando e também tudo OK. Porém, na hora de compilar um dos exemplos que já disponibilizei no site utilizando o DDKBuild, obtive a seguinte mensagem de erro:

    'ddkbuild' is not recognized as an internal or external command

    Executando o DDKBuild no prompt de comandos tudo funcionava, mas o mesmo erro sempre era apresentado a partir do Visual Studio. Depois de arrancar algumas mechas de cabelo, tentei abrir o arquivo DDKBuild.cmd a partir do Visual Studio, ou seja, menu File, Open…, diretório System32 e foi quando vi que o arquivo não estava no diretório System32 como deveria, pelo menos não para o Visual Studio.

    Demorou mas a ficha caiu. O Visual Studio é um processo 32 bits e caiu nas regras de redirecionamento de File System do Wow64 (Windows-On-Windows 64). Para quem não sabe, para manter compatibilidade com o passado, as plataformas 64 bits ainda utilizam o diretório C:\Windows\System32 para colocar as DLLs de sistema, mesmo sendo elas de 64 bits.

    Mas o que acontece com as DLLs de sistema de 32 bits? Os processos de 32 bits ainda precisam destas DLLs para serem carregados. Quando um processo 32 bits tenta acessar o diretório C:\Windows\System32, seu acesso é redirecionado pelo Wow64 para a pasta C:\Windows\SysWow64 (C:\Windows\System32\SysWow64 no caso do Windows XP x64). Os acessos ao registro também sofrem redirecionamentos e em alguns casos sofrem espelhamentos de 32 bits para 64 bits e vice-versa. Confira os detalhes do Wow64 no site do MSDN.

    Depois que a ficha caiu, ficou ridículo resolver o problema. Simplesmente coloquei uma cópia do DDKBuild.cmd no diretório C:\Windows\SysWow64 e todos viveram felizes para sempre.

  • Walter Oney para Gerentes

    O fato de escrever programas um pouco fora do comum traz algumas conseqüências. Alguns dos seus amigos de trabalho, aqueles que às vezes sentam ao seu lado, não entendem nada do que você está fazendo (e vice-versa). Outros são vistos sussurrando: “Cuidado! É aquele cara ali que programa drivers.”. Essa dificuldade que as outras pessoas têm de entender o que estamos escrevendo, acaba atingindo também os gerentes, diretores e assim por diante. Não que eles fossem culpados disso. Afinal de contas, se eles se interessassem pela área técnica a esse ponto, provavelmente não seriam gerentes, diretores ou seja lá o que for. Isso não é uma regra, mas pelo menos nas empresas onde trabalhei, a minoria dos meus superiores entendiam a complexidade daqueles problemas que acabavam atrasando a entrega de um produto.

    A pior das situações é quando um superior pensa que é simples resolver as coisas. Afinal de contas, quando ele programava em COBOL, não havia um problema que ele não resolvesse, e que você provalvelmente estaria fazendo corpo mole ou simplesmente se achando um super astro por resolver os problemas das telas azuis. Eu particularmente acho lindo quando eles perguntam: “Mas você já não resolveu esse problema da tela azul?”, como se houvesse apenas um problema cuja conseqüência é uma tela azul.

    Bom, desta forma acho que a opinião de uma reconhecida autoridade no assunto poderia ter alguma influência sobre a opinião destas pessoas. No primeiro capítulo do seu livro Programming The Microsoft Windows Driver Model (2º Edition), Walter Oney dá algumas dicas sobre como uma empresa poderia encarar o desenvolvimento de drivers a fim de obter resultados mais próximos dos esperados. Separei alguns trechos que seguem logo abaixo.

    “A triste realidade é que programar WDM é muito difícil, e somente programadores experientes (e caros!) são capazes de fazê-lo bem.”

    “O desenvolvimento dos drivers deve iniciar assim que houver uma especificação razoavelmente definida de como o hardware vai trabalhar. Você deveria esperar por modificações na especificação em virtude às desagradáveis descobertas durante o desenvolvimento do driver, …”

    “Você também deve esperar que programar drivers leve mais tempo e mais custo que o imaginado inicialmente. Todo programa está sujeito a uma maior demanda de tempo e custo. Uma demanda adicional viria da dificuldade de comunicação entre as pessoas de hardware e software, das ambiguidades na especificação e na documentação do DDK,…”

    “Dê atenção em saber como os usuários finais irão instalar seu driver. A maioria dos vendedores de hardware preferem entregar uma instalação customizada em um CD-ROM, e escrever o instalador é um processo longo que pode consumir um programador experiente por várias semanas…”

    “O arquivo executável provavelmente incluirá mensagens de texto em um resource especial com múltiplas línguas, e seria uma boa idéia ter uma pessoa treinada para compô-las. (Eu não estou dizendo que seu programador de drivers não possa fazer isto, mas ele pode não ser a melhor escolha.)”

    “Finalmente, não trate seus drivers como detalhes sem importância. Ter um bom driver com um suave instalador é no mínimo tão importante quanto a aparência exterior do produto. … Então, uma má escolha por um desenvolvimento barato de driver poderia ter um dramático efeito negativo em poucos anos. Este aviso é especialmente importante para fabricantes de hardware de países em desevolvimento, onde gerentes têm a tendência a procurar por qualquer corte de custos. Eu sugiro que o desenvolvimento de drivers seja em um lugar onde ter decisões baseadas em custos seja inapropriado.”

    Talvez eles nunca leiam isso, mas com certeza nos ajuda a respirar fundo e continuar programando. Agora volta a trabalhar porque você não é pago pra ficar navegando na Internet! (Essa também é ótima)… 😀

  • Step into Kernel (Serial)

    Ganhei meu primeiro computador aos 13 anos de idade, mas foi só depois de 7 anos, quando comecei meu estágio, que descobri que era possível depurar os programas que eu escrevia. Até lá, meus métodos de depuração sempre foram coisas como imprimir o valor da variável na tela. Tudo bem que nesse tempo eu não desenvolvia programas complexos o suficiente para ficar sem saída, e com o passar do tempo, acho que me acostumei a trabalhar sem esse luxo todo de breakpoint e tal.

    Quando fui apresentado a um depurador, nem dei o devido valor para aquilo. Eu pensava comigo mesmo: “É só colocar um printf e beleza. Não preciso de toda essa parafernalha.”. Na época utilizávamos o CodeView, um depurador 16 bits que roda em DOS, que é exibido na tela abaixo.

    Não demorou muito para eu começar a depender do depurador para fazer as coisas mais simples, como escovar os dentes por exemplo. Na verdade, algumas situações como escrever código para coletores de dados, escrever firmwares para hardwares que não tinham display, situações nas quais não podíamos contar nem com uma porta serial para saber o que se passava com o software, e a inesquecível situação onde havíamos colocado um ocsiloscópio em um determinado pino do processador para capturar sinais de vida do nosso software, enfim, situações onde daríamos um braço para ter um simples breakpoint me mostraram que o depurador não é perfumaria.

    O engraçado é que hoje em dia, na faculdade, a grande maioria dos alunos da minha turma não sabe depurar software. Eu já tentei explicar para meus amigos o tamanho da vantagem de utilizar um depurador, mas eles acabam tendo a mesma reação que tive anos atrás: “Ah não Fernando, isso é muito complicado. Só de olhar a gente acaba encontrando o erro.”. Por mim tudo bem…

    Bom, a gente está aqui pra beber ou pra conversar? Vamos depurar o driver gerado pelo tradicional post Getting Started. Desta vez, vamos fazer como manda a tradição. Vamos precisar de duas máquinas, um cabo serial do tipo Null Modem e por último e não menos importante, uma cópia do Windbg. Não, não adianta ler esta frase de novo, você não entendeu errado, você vai precisar realmente de duas máquinas. Não sei porque, algumas pessoas apresentam uma certa resistência em acreditar nisso. Bom, suponho que superamos essa fase e podemos continuar com o post.

    Se você ainda não tem uma cópia do Windbg, você poderá baixa-lo gratuitamente da página do Windows Debugging Tools no site da Microsoft.

    Vamos tomar esta imagem ao lado emprestada do help do Windbg, que mostra como devemos ter os micros para realizar o Debug de Kernel.

    A máquina nomeada HOST será onde teremos o WinDbg rodando. Esta máquina é, na maioria das vezes, a máquina onde o driver foi desenvolvido. Vamos partir do princípio que esta é a máquina onde o driver foi desenvolvido e adiar os detalhes de como configurar o diretório de símbolos e fontes para um outro post. A máquina nomeada TARGET é onde teremos nosso driver carregado pelo sistema. O sistema operacional da máquina TARGET não precisa necessariamente ser Checked Build, nem é necessário instalar nenhum kit adicional ao sistema operacional. Tudo o que precisamos é do seu driver e de uma porta serial. Todos os Windows da plataforma NT (Windows NT, Windows 2000, Windows XP, Windows 2003 Server e Windows Vista) já trazem seu depurador nativo desde a sua instalação. Na verdade, essa é uma excelente característica no uso do Windbg. Imagine aquele tipo de problema que só se manifesta na máquina do cliente. Você não vai querer instalar nada agressivo ou mesmo que mude o cenário de forma que o problema não se manifeste mais. Acredite, isso não é tão raro assim. Nestes casos, basta habilitar o Debug e pronto.

    Para habilitar o Debug na máquina TARGET, precisaremos editar alguns parâmetros de inicialização que estão no arquivo Boot.ini localizado na pasta raiz do sistema. Este arquivo está com os atributos de arquivo de sistema, arquivo oculto e também como somente leitura, talvez seja necessário configurar o Windows Explorer para que seja possível ver estes arquivos. Utilizando o Windows Explorer, vá até a pasta raiz, remova a propriedade de somente leitura do arquivo Boot.ini, em seguia, abra este arquivo utilizando um editor de texto como o Bloco de Notas por exemplo. O conteúdo deste arquivo é semelhante ao demonstrado baixo.

    Na sessão Operating Systems você terá que duplicar a linha do sistema que você vai querer depurar. Adicione ao final desta linha os parâmetros /debugport=com1 /baudrate=115200 que irão configurar o modo Debug. O primeiro parâmetro seleciona a porta COM a ser utilizada, enquanto o segundo seleciona a velocidade da comunicação. O protocolo serial não é o meio mais rápido de comunicação entre as máquinas HOST e TARGET, e em algumas situações chega a ser desagradável ter que esperar as interações entre o WinDbg e o sistema depurado. Existem outros meios de comunicação que podem ser utilizados caso seu computador não tenha uma porta serial disponível ou caso você queria ter mais velocidade e conforto no Debug. Veja mais detalhes no post Serial Killers.

    O resultado final do nosso arquivo Boot.ini deveria ser algo semelhante ao exibido abaixo. Vou pular o inicio as linhas que decrevem o sistema operacional para a melhor visualização colocando “…”.

    [boot loader]
    timeout=30
    default=multi(0)disk(0)rdisk(0)partition(2)\WINDOWS
    [operating systems]
    multi(0)... /NoExecute=OptIn
    multi(0)... /NoExecute=OptIn /debugport=com1 /baudrate=115200

    Feitas estas alterações, você pode salvar o arquivo e restaurar os atributos originais do arquivo. No Windows XP e Windows 2003, os mesmos passos acima descritos poderiam ser feitos com a ajuda de uma ferramenta chamada Bootcfg.exe. A partir do Windows Vista este processo é bem diferente e vou descreve-los em um post futuro.

    De volta à máquina HOST, inicie o WinDbg e selecione a opção Kernel Debug… do menu File. Deverá ser exibida uma janela que configura o meio de comunicação. Faça isso de forma que fique compatível com o que foi configurado no arquivo Boot.ini da máquina TARGET. Neste caso, estamos utilizando a porta serial COM1 e baud rate de 115200. Clicando em OK o WinDbg vai abrir a porta serial da máquina HOST e vai esperar até que a máquina TARGET esteja pronta. Neste ponto, teremos a seguinte mensagem na janela de comandos do WinDbg.

    Enquanto a máquina HOST aguarda, certifique-se de que seu driver está instalado na máquina TARGET e a reinicie. Depois de reiniciada, quando sistema for iniciar a sua carga, o Loader do sistema irá exibir as opções encontradas no arquivo Boot.ini editadas por você. Selecione a opção onde aparece [debugger enabled] como mostra abaixo.

    Selecionada esta opção, as máquinas HOST e TARGET deveriam se conectar e na janela Command seria exibiria uma saíba semelhante à mostrada abaixo.

    Microsoft (R) Windows Debugger  Version 6.6.0007.5
    Copyright (c) Microsoft Corporation. All rights reserved.
     
    Opened \\.\com1
    Waiting to reconnect...
    Connected to Windows XP 2600 x86 compatible target, ptr64 FALSE
    Kernel Debugger connection established.
    Symbol search path is: *** Invalid ***
    ****************************************************************************
    * Symbol loading may be unreliable without a symbol search path.           *
    * Use .symfix to have the debugger choose a symbol path.                   *
    * After setting your symbol path, use .reload to refresh symbol locations. *
    ****************************************************************************
    Executable search path is:
    *********************************************************************
    * Symbols can not be loaded because symbol path is not initialized. *
    *                                                                   *
    * The Symbol Path can be set by:                                    *
    *   using the _NT_SYMBOL_PATH environment variable.                 *
    *   using the -y  argument when starting the debugger. *
    *   using .sympath and .sympath+                                    *
    *********************************************************************
    *** ERROR: Symbol file could not be found.  Defaulted to export symbols for
    ntkrnlpa.exe -
    Windows XP Kernel Version 2600 UP Free x86 compatible
    Built by: 2600.xpsp_sp2_gdr.050301-1519
    Kernel base = 0x804d7000 PsLoadedModuleList = 0x805531a0
     

    Muito bem, até aqui sabemos que as máquinas estão conectadas. Pressionando as teclas Ctrl + Break no Windbg, faremos com que o sistema fique congelado na máquina TARGET e que o controle seja passado ao depurador. O depurador estará pronto para receber seus comandos quando aparecer um prompt de comandos indicado pelas letras KD no canto inferior esquerdo da janela Command.

    Apenas para fazer um simples teste, digite o comando “lm” e tecle enter. Este comando lista os módulos carregados pelo sistema. Note que o driver Useless ainda não está nesta lista. Isso acontece porque o driver exemplo está configurado para ter sua execução iniciada manualmente, desta forma, o driver ainda não foi carregado pelo sistema. Em seguida, selecione o ítem Open Source File… do menu File e abra o arquivo Useless.c. Clique sobre o nome da função DriverEntry e pressione F9. Embora não haja nenhum sinal visível disso, colocamos um breakpoint nesta função. O fato de não ter nenhuma alteração visual se deve ao fato de que nosso módulo ainda não está carregado. Podemos nos certificar de que o breakpoint está instalado listando os breakpoints com o comando “bl”.

    kd> bl
     0 e f9fb3430     0001 (0001) Useless!DriverEntry

    Agora vamos lançar o comando “g” que fará com que o sistema volte a execução normal.

    Depois que o sistema for carregado, vamos iniciar o driver através do comando net start Useless. Quando o driver for iniciado, o entry point DriverEntry será executado e por conseqüência teremos a execução interrompida pelo nosso breakpoint. A máquina TARGET ficará congelada enquanto o depurador detém o controle sobre o sistema. Repare que agora que nosso módulo está carregado, os breakpoints são facilmente identificados pelas linhas com a cor de fundo vermelha.

    Bom, os primeiros passos já foram dados, os comandos e detalhes de como depurar o Kernel de Windows estariam fora do escopo deste post. Mas uma boa dica de como obter kilos de detalhes sobre este assunto é o livro Windows 2000 Kernel Debugging. O livro não é um dos meus preferidos, ele dá uma boa introdução ao assunto além de falar sobre como utilizar e construir Debug Extensions, mas não chega a ter as técnicas ninjas utilizadas pelos verdadeiros mestres do Kernel Debugging. Uma outra excelente fonte de detalhes sobre este assunto é a lista de discussão Windows Debugger Users List dedicada ao Windbg. Isso tudo sem nos esquecermos do Help.

    Este foi um post inicial sobre o assunto, aguardem por posts futuros onde darei os passos de como utilizar o Windbg com a VMware e de como depurar o Kernel do Windows com apenas uma máquina utilizando o SoftIce (que Deus o tenha).

    Have fun… 😉

  • Off-topic HP 50G

    Finalmente o período de provas semestrais terminou. Já posso voltar à vida normal, me re-integrar à sociedade, e porque não, escrever posts. Para os que não sabiam, estou cursando Engenharia da Computação na Universidade São Judas Tadeu. Apesar dos meus trinta anos de idade, ainda estou no terceiro ano devido à uma vida um tanto agitada que um programador pode ter. Tenho certeza que muitos de vocês sabem exatamente do que estou falando. Tive que interromper meu curso diversas vezes, ou por falta de grana, ou por excesso de trabalho. Enfim, estas últimas semanas estive correndo atrás do prejuízo e não pude compor um post decente. Assim, vão ter que se contentar com algumas curiosidades sobre minha nova melhor amiga, a HP 50G.

    Costumo dizer que programador não é nome de profissão, e sim nome de doença. “Programador: Pessoa que sofre de programação”. Um dos sintomas que considero mais determinantes no programador é a grande capacidade de associar coisas diversas com programação, mesmo sem que se queira. Um exemplo? Fácil!!! Quando eu estava cursando Informática Industrial na ETE, havia um professor que falava pausadamente, cerca de 3 segundos entre uma frase e outra. Ele parecia um andróide. Eu costumava imaginar que aquela característica se dava ao fato de que o professor tinha pouca RAM, dessa forma, levava um certo tempo para carregar uma nova frase da HD para ser processada pelo sintetizador de voz. Na época dizíamos que se prestássemos atenção, poderiamos ver um led piscando dentro da orelha dele durante aquelas pausas. Felizmente aquilo poderia ser facilmente resolvido se houvesse uma segunda thread que colocasse a próxima frase na RAM à medida que a frase atual fosse processada pela thread principal. Não sei o que vocês pensam sobre isso, mas para mim parece doença. :-S

    Estudando para as provas de Cálculo Numérico Computacional e armado com uma HP 50G, foi inevitável programar a HP para automatizar alguns passos. Os exercícios desta matéria são compostos basicamente por contas simples, mas são longos e cheios de regrinhas. Portador de programação desde os 13 anos de idade, baixei os manuais e mãos à obra. Quando compramos a HP, vem um “Quick Start” de umas 120 páginas, mas é possível baixar PDF do manual completo de 918 páginas em português.

    Como era de se esperar, encontrei muitos sites e tutoriais sobre o assunto, vários em português mesmo. O site hpcalc.org foi um grande colaborador. Na página de programação pode-se encontrar muita coisa. E o melhor, é tudo free.

    Podemos programar a HP em UserRPL ou SystemRPL. Programar em SystemRPL nos dá um ganho de performance quase 10 vezes superior que em UserRPL. Entretanto, tudo na vida em um preço. O trecho abaixo foi retirado em um dos inúmeros PDFs que encontrei.

    Estranho! Eu podia jurar que já li algo parecido em algum lugar.

    Como não consegui baixar uma versão do WinDbg que depurasse a HP, resolvi programar em UserRPL mesmo. A linguagem é basicamente composta pela seqüência de teclas que seriam pressionadas durante o uso normal da calculadora, porém, podemos ainda contar com blocos de execução, laços de repetição(for, while), execução condicional (if, else), execução de sub-programas e outras tantas coisas que não tive paciência de ler. É divertido poder misturar coisas como IF e ELSE com comandos que fazem operações com matrizes como se fossem tão simples quanto somar 1 em 1.

    A HP pode ser programada nela mesma, ou seja, utilizando seu próprio teclado. Mas ficar escrevendo sobre aquele tecladinho é similiar a ficar jogando Decathlon no Atari. Funciona, mas você sabe o que vai acabar acontecendo. Outra coisa super desconfortável é tentar entender o que está errado no seu programa olhando o fonte (que não esta indentado) em uma telinha de LCD. Uma das facilidades é um cabo USB que conecta a HP em seu micro.

    Para ter acesso aos diretórios e variáveis armazenados na calculadora, você terá que fazer o download do software que faz esta conexão. Mas se você possuir uma máquina x64 (mesmo que seja da HP), não haverão drivers disponíveis para realizar esta conexão. Se este for o seu caso, você pode utilizar uma máquina virtual que esteja rodando um sistema operacional de 32 bits. Não é que funciona mesmo!

    Quando conectado, é exibido uma espécie de “Explorer” onde você pode navegar nas pastas e editar variáveis e programas. Com um double-click sobre os programas, um excelente ambiente de desenvolvimento chamado “Notepad” é aberto com o seu programa. Nem pense em identar seu programa. Quando o este é enviado para a HP, ele assume uma identação dela, que obviamente não é a mesma que gostariamos de ter.

    Pois é, foi no mínimo interessante aprender mais essa. Realmente valeu a pena gastar essa grana comprando algo que eu possa programar e poder sustentar meu vício. Mas ainda não para por aí. Se você der uma olhada na quantidade e variedade de aplicações para download da hpcalc.org, você terá uma idéia do que mais ela pode fazer além de calcular o determinante de uma matriz. Se estiver curioso, você pode baixar um emulador da calculadora em seu PC e fazer um Test Drive.

  • Kernel + Visual Studio 2005

    Programador de driver ou software de baixo nível está acostumado a compilar seus projetos na linha de comando, utilizar editores de texto como o Notepad ou mesmo o Norton Editor para codificar seus drivers. É como meu amigo Thiago diz: “Faca nos dentes e sangue nos olhos”. Utilizar o comando TYPE | MORE para ver os arquivos de erros de compilação, mostra o quanto somos seres superiores, dominates da tecnologia, que não sub-utilizamos nosso cérebro, que não precisamos de ferramentas supérfluas como interface gráfica, sintaxe colorida, intellisence ou mesmo o auto-complete e que estamos no topo da cadeia alimentar.

    Se você realmente acredita nisso, então é melhor abandonar este post por aqui mesmo. Hoje vou defender o uso das ferramentas que ajudam, e muito, na hora de codificar um driver e ter que lidar com funções com grande número de parâmetros e intermináveis estruturas.

    Utilizar ou não o compilador do Visual Studio para gerar drivers é mais uma das discussões que tendem ao infinito, tal como o uso de C++ no desenvolvimento para Kernel Mode. De um lado estão aqueles que defendem que o ambiente quase nunca está perfeitamente configurado para compilar drivers. Já li posts de verdadeiras autoridades no assunto falando sobre quantos dias foram necessários para encontrar um bug que foi gerado por uma otimização do compilador ou mesmo um define incorreto que foi gerado pelo Wizard. Estes que dizem que utilizar o Visual Studio como ambiente de desenvolvimento requer um conhecimento mínimo de cada parâmetro utilizado na chamada para o cl.exe, que o ideal mesmo é utilizar o bom e velho Build que trata os arquivos sources, dirs e makefile e ter a absoluta certeza de que o ambiente configurado pelos atalhos do DDK está correto. Do outo lado estão os que não abrem mão da comodidade de ter um ambiente integrado, de poder pressionar apenas uma tecla para cair na linha do arquivo em que o erro foi detectado, de utilizar sintaxe colorida, intellisence, auto-complete e outras tantas vantagens que o Visual Studio oferece.

    Já faço isso a algum tempo e lembro-me de quando o DDK resolveu trazer um compilador embitudo no Kit a partir do Windows XP. O principal objetivo desta mudança no DDK foi tentar separar o ambiente de desenvolvimento de drivers do ambiente de aplicações. Esse vínculo já criou muitos problemas. O DDK do Windows NT 4.0 era baseado no VC 4.2, mas com o surgimento do VC 5.0 e posteriormente do VC 6.0, o formato da tabela de símbolos foi alterado e o Windbg parou de funcionar. Nesta época eu utilizava muito mais o SoftIce que o WinDbg para depurar drivers e continuei utilizando o compilador do Visual Studio sem nem perceber este problema. Só tive problemas com símbolos quando o Visual Studio passou para a versão 7.0. O VToolsD até hoje não oferece suporte ao novo formato de símbolos. Conclusão, até hoje eu utilizo VC 6.0 para compilar VXDs. Não que seja impossível utilizar o VC 7.0 para isso, só não conseguiremos depura-lo.

    Mas foi quando fizemos o upgrade do VS2003 para o VS2005 que mudanças mais drásticas fizeram com que minha vida se tornasse um pouco mais azul. Foi a partir dessa época que comecei a utilizar o melhor dos dois mundos, ou seja, a confiança de ter um ambiente corretamente configurado e toda a comodidade das ferramentas que o editor do VS2005 nos oferece.

    Como fazer isso? Ok, vamos utilizar o projeto criado no post Getting Started como ponto de partida. Inicialmente precisaremos baixar o arquivo DDKBUILD.CMD da OSR Online e grave-o em um diretório que esteja no PATH da sua máquina. Este CMD irá precisar da variavel de ambiente WNETBASE configurada com o diretório base da sua instalação do DDK. Este arquivo é um meio de fazer um build externo com o Visual Studio. Para fazer isso, crie um projeto de Makefile no Visual Studio como mostra a seguir.

    Depois de entrar com o nome do seu novo projeto e a pasta onde ele será criado, é só clicar em “OK”. Mesmo sem configurar nada, clique em “Finish” da janela seguinte. Criado o projeto, adicione os arquivos no Solution Explorer. Em seguida, vamos editar as propriedades do projeto de forma que fique como ilustrado abaixo. Note que no campo “Include Search Path” estou assumindo que a instalação do DDK foi realizada no diretório C:\WINDDK\3790. Este campo não tem nenhuma influência sobre o processo de compilação, ele apenas informa ao intellisence quais os diretórios devem ser utilizados para servirem de fontes para sua base de dados.

    A partir deste ponto, já podemos compilar o projeto como se fosse um projeto qualquer de User Mode. As imagens abaixo dispensam comentários. Imagino que não é necessário dizer que não é possível depurar drivers utilizando o ambiente do Visual Studio. Lembre-se que depuração de Kernel exige depuradores especiais para isso como comentei em meu último post.

    Uma janela especialmente interessante que foi adicionada ao Visual Studio 2005, foi a “Code Definition Window”, que exibe o lugar onde os símbolos foram definidos conforme você os escreve na janela de código. Veja o exemplo disso quando escrevo uma chamada para IoSetCompletionRoutine.

    Enfim, dêem uma olhada nas opções oferecidas pelo DDKBUILD. Você pode listar suas opções simplesmente abrindo uma janela de prompt e executando-o sem qualquer parâmetro. Observe que podemos compilar drivers utilizando inclusive o PREfast.

    É isso aê… Have fun ! 😉

  • Serial Killers

    Não há mais dúvidas, estão mesmo acabando com as portas seriais em notebooks e desktops. Neste post vou falar sobre alguns problemas que encontramos quando é necessário depurar aquele driver que só dá problema naquela máquina que não tem portas seriais. Quais as alternativas que temos em relação a isso?

    Este mês chegaram micros novos aqui na empresa. Nestas máquinas, colocamos algumas versões beta de nossos produtos. Por algum motivo, somente nas máquinas novas, um dos nossos drivers não estava funcionando como deveria. Conforme o cita o código de ética do programador, a culpa é do estagiário que fazia os testes até que se prove o contrário. Depois de algumas tentativas frustradas de descobrir o que estava acontecendo, não tive dúvidas, vamos depurar. Para não ter que deslocar a vítima até minha mesa para conectar o cabo serial, resolvi instalar o SoftIce. Mas a vida é uma caixinha de surpresas e obtivemos uma bela BSOD quando iniciamos o SoftIce. Wow!!! Isso é raro, mas acontece. Nestes casos não temos muita escolha, vamos ter que utilizar o Windbg mesmo. Os micros que chegaram são do modelo Dimension 5150 da Dell. Dê uma olhada na parte traseira do gabinete na foto ao lado e responda rapidamente: Onde está a porta serial? Pois é, não tem mesmo. Consultando o site do fabricante, verificamos que o micro dispõe de 8 portas USB, mas nada de porta paralela e nada de porta serial.

    Neste momento, fizemos uma roda e falamos todos juntos: “Oh Deus, o que faremos agora?”. Alguns dos problemas que acontecem em máquinas reais são reproduzidos em máquinas virtuais, principalmente se o driver que você está desenvolvendo é um filtro ou um driver que não lide diretamente com o hardware. Para fazer um teste, instalamos a VMware em uma das máquinas novas e felizmente o problema foi reproduzido. Essa é uma daquelas ocasiões onde ficamos felizes pelo problema aparecer. Daí em diante, foi só utilizar o velho truque da porta serial virtual na máquina virtual e redirecionar os dados para um named pipe. Isso até poderia virar um post.;-)

    Bom, com o debugger conectado, break-point setado, não precisou de muito tempo para encontrar o problema, gerar uma nova versão, testar, funcionar e viver feliz para sempre. Mas nem sempre a história se resolve com estes poucos passos. Algumas máquinas virtuais não dão suporte a dispositivos USB. Mesmo a VMware que oferece este recurso, ainda não dá suporte a dispositivos de interface humana (HID) como teclados e mouses USB. Isso sem falar dos problemas psico-esotéricos que envolvem race conditions e/ou uma boa ação conjunta com Murphy. O que podemos fazer nestes casos além de sentar e chorar?

    Felizmente a tecnologia a serviço da humanidade previu situações como estas. Nestes casos temos tradicionalmente duas alternativas:

    1) Descrever o bug que você quer eliminar em uma folha de papel. É recomendado que esta descrição tenha trechos como “Sai bug estranho sai…”. Costurar a folha de papel dentro da boca de um sapo e com o pensamento positivo na resolução do problema, atire o sapo em um rio sem que você veja onde ele caiu. Depois de sete dias, dê um “Rebuild All” em seu projeto (Um reboot antes do build é recomendado). Caso o problema persista, repita a operação. Formatar a máquina ajuda a eliminar os maus fluídos e a espantar os espíritos atormentados que assombram o seu código. Consulte uma benzedeira de software para obter melhores resultados.

    2) Utilizar uma placa FireWire e fazer a conexão do Windbg utilizando um cabo IEEE 1394. Esta opção só estará disponível se a máquina a ser depurada for um Windows XP ou superior. Estas placas são espetadas em seu barramento PCI ou ainda PCMCIA. Ainda não cheguei a utilizar este tipo de conexão para depurar uma máquina, mas é seguramente bem rápido e confortável sabendo que os dados são transmitidos a uma taxa de até 400Mbps.

    Não posso utilizar um daqueles famosos conversores USB para portas seriais que são vendidos na Santa Ifigênia? Mesmo que não seja necessário instalar um driver no seu sistema, estes adaptadores utilizam drivers para funcionar. Toda a pilha USB é montada para que estes dispositivos funcionem. Em uma máquina que está sendo depurada em Kernel, todo o código que trata a comunicação serial ou firewire está hard coded no loader do sistema e trabalha com endereços fixos padrões. Estes adaptadores podem ser utilizados sem problemas pela máquina que fará o Debug, mas não pelas que irão sofrer o Debug.

    E se eu utilizar uma placa multi-serial PCI ? Isso vai depender de quais endereços de I/O e interrupções a placa vai oferecer. Normalmente estas placas utilizam endereços diferentes dos endereços padrões a fim de não criar nenhuma incompatibilidade com as portas que possivelmente pré-existam na máquina. Mas se a placa oferecer a opção de utilizar os endereços padrões, não haverá problemas.

    O Windows Vista trará suporte a Debug de Kernel via porta USB 2.0, entretanto algumas condições serão exigidas. O cabo USB deverá ser especial para este fim, a interface USB do computador sendo depurado deverá oferecer suporte a Debug de Kernel, e por último e não menos importante, a controladora USB do computador que fará o Debug deverá ser compatível com Extended Host Controller Interface(EHCI).

    Caso você se interesse em adquirir interfaces IEEE 1394, lembre-se que você deverá ter pelo menos duas interfaces deste tipo para que haja o link entre a máquina depuradora e a máquina depurada. Embora eu tenha visto alguns comentários perdidos na Internet sobre cabos conversores USB x FireWire (para utilizar na máquina depuradora), até onde pude ver são eletricamente impossíveis de serem construídos. Isso porque todo o protocolo é diferente. Enquanto o USB é um protocolo “Master x Slave”, o FireWire é “Peer to Peer”. Seria necessário algum hardware inteligente no meio do caminho para fazer toda a tradução.

    Estamos adquirindo estas interfaces FireWire para PCMCIA aqui na Open a fim de eliminar o problema de depurar notebooks de clientes que nasceram aleijados de portas seriais. Creio que em pouco tempo poderemos abrir mão do sapo e tudo mais. Farei algumas comparações com o bom e velho cabo serial e darei alguma notícia sobre este assunto.



  • Driver plus plus

    Desenvolver software de baixo nível é muito bom. Eu pessoalmente adoro o tipo de trabalho que realizo. Tenho amigos muito próximos que se maravilham com a beleza do C++, como as coisas parecem se encaixar perfeitamente em meio ao STL, iterators e templates. Meu negócio definitivamente é outro. Apesar de utilizar C/C++, algumas pontinhas de Assembly e gostar muito destas linguagens, não me dedico em estudar as entranhas do C++. Meu lado prático de resolver os problemas me leva a fazer algumas coisas que fariam o Lesma querer me bater, mas felizmente ele é uma pessoa calma. 😛

    Depois que meu irmão (Kabloc) leu meu post Getting Started…, ele me perguntou se não poderiamos utilizar C++ no lugar de C para desenvolver drivers. Neste post vou resumir os problemas enfrentados para se ter uma Tela Azul orientada a objetos. Não vou opinar se C++ é melhor ou pior que C para escrever drivers. Essa é uma discussão que parece não ter fim. Se você participa de alguma lista de discussão de drivers, sabe do que estou falando. Eu pessoalmente prefiro sair criando classes, namespaces e templates que resolvam de maneira modular os meus problemas. Isso me parece ser mais confortável. De fato podemos sentir uma certa tendência do desenvolvimento Kernel Mode para a programação orientada a objetos. Este é um aspecto cada vez mais presente com as DDIs (device-driver interfaces), que utilizam o modelo COM para comunicação entre camadas no Kernel, e a chegada do WDF. Vale lembrar que utilizar o modelo do COM não significa utilizar o COM, mas apenas o conceito de interfaces derivadas da tão conhecida IUnknown para implementar contadores de referência e intercâmbio de interfaces.

    Primeiro de tudo, vamos nos basear no projeto exemplo criado em meu post anterior para criar um driver que faça uso de uma classe. Como manda a tradição, vamos começar pensando que é fácil e simplesmente usar a extensão CPP no arquivo fonte abaixo e ver no que dá.

    #include 
     
    //-f--> Uma classe com um nome bem original
    class MyClass
    {
    public:
        MyClass()
        {
            DbgPrint("Construtor %p\n", this);
        }
     
        ~MyClass()
        {
            DbgPrint("Destrutor %p\n", this);
        }
     
        VOID SayHello(VOID)
        {
            DbgPrint("Hello from %p\n", this);
        }
    };
     
     
    /****
    ***
    **      DriverEntry 
    */
     
    NTSTATUS DriverEntry(IN PDRIVER_OBJECT  pDriverObject,
                         IN PUNICODE_STRING pusRegistryPath)
    {
        MyClass Instance;
     
        //-f--> Um sinal de vida
        DbgPrint("DriverEntry called\n");
     
        //-f--> Hello...
        Instance.SayHello();
     
        //-f--> Até aqui tudo bem
        return STATUS_SUCCESS;
    }

    Round #1…

    Compiling - krnclass.cpp for i386
    ...\ntddk.h(2152) : error C2220: warning treated as error - no object
                        file generated
    ...\ntddk.h(2152) : error C4162: '_ReturnAddress' : no function with
                        C linkage found
    ...\ntddk.h(6889) : error C4162: '_InterlockedExchange' : no function
                        with C linkage found
    ...\ntddk.h(6915) : error C4162: '_InterlockedIncrement' : no function
                        with C linkage found
    ...\ntddk.h(6928) : error C4162: '_InterlockedDecrement' : no function
                        with C linkage found
    ...\ntddk.h(6942) : error C4162: '_InterlockedExchangeAdd' : no function
                        with C linkage found
    ...\ntddk.h(6972) : error C4162: '_InterlockedCompareExchange' : no
                        function with C linkage found
    ...\ntddk.h(7024) : error C4162: '_InterlockedOr' : no function with C
                        linkage found
    ...\ntddk.h(7034) : error C4162: '_InterlockedAnd' : no function with C
                        linkage found
    ...\ntddk.h(7044) : error C4162: '_InterlockedXor' : no function with C
                        linkage found

    Vamos encontrar no ntddk.h algumas definições que precisam ser compiladas em C e não em C++. Este problema também vai acontecer com a função de entrada do driver DriverEntry. O DDK espera encontrar este símbolo exportado como C. Mas isso ainda é mamão com açúcar, basta mudar a maneira como incluímos este header e como declaramos do nosso entry point para que fique como mostra abaixo.

    extern "C"
    {
        #include 
    }
     
    ...
     
    extern "C"
    NTSTATUS DriverEntry(IN PDRIVER_OBJECT  pDriverObject,
                         IN PUNICODE_STRING pusRegistryPath)
    {

    Agora sim, compilou direitinho. Carregando este driver poderemos ver as seguintes mensagens no DebugMon.

    Pronto? Já temos um driver em C++? Ainda não. Já podemos desfrutar de alguns conceitos como classes, templates e sobrecargas por exemplo, mas vejamos o que acontece quando tentamos utilizar o operador new.

    extern "C"
    NTSTATUS DriverEntry(IN PDRIVER_OBJECT  pDriverObject,
                         IN PUNICODE_STRING pusRegistryPath)
    {
        MyClass *pInstance = NULL;
     
        //-f--> Criando uma instância dinamicamente
        pInstance = new MyClass();
     
        //-f--> Telazulfobia
        if (!pInstance)
            return STATUS_NO_MEMORY;
     
        //-f--> Hello from heap
        pInstance->SayHello();
     
        //-f--> Liberando
        delete pInstance;
     
        //-f--> Até aqui tudo bem
        return STATUS_SUCCESS;
    }
     

    Round #2…

    Linking Executable - objchk_wxp_x86\i386\krnclass.sys for i386
    krnclass.obj : error LNK2019: unresolved external symbol "void * __cdecl
                   operator new(unsigned int)" (??2@YAPAXI@Z) referenced in
                   function _DriverEntry@8
    krnclass.obj : error LNK2019: unresolved external symbol "void __cdecl
                   operator delete(void *)" (??3@YAXPAX@Z) referenced in
                   function "public: void * __thiscall MyClass::`scalar
                   deleting destructor'(unsigned int)" (??_GMyClass@@QAEPAXI@Z)
    objchk_wxp_x86\i386\krnclass.sys : error LNK1120: 2 unresolved externals

    Ops! Para resolver estas dependências, teremos que sobrecarregar os operadores new e delete para que as alocações sejam feitas através do ExAllocatePool. Vamos passar um parâmetro adicional ao operador new para que possamos escolher entre NonPagedPool, PagedPool and so on…

    typedef unsigned int size_t;
     
    //-f--> Sobrecarga do operador new
    __inline void* __cdecl operator new(size_t size,
                                        POOL_TYPE pool)
    {
        return ExAllocatePool(pool, size);
    }
     
    //-f--> Sobrecarga do operador delete
    __inline void __cdecl operator delete(void *pVoid)
    {
        ExFreePool(pVoid);
    }
     
    ...
     
        //-f--> Devemos especificar o pool type aqui
        pInstance = new(NonPagedPool) MyClass();
     
    ...

    Agora já compila novamente e as alocações de memória são realizadas de maneira que se deve, entretanto existe um outro problema que acontecerá quando você tentar criar objetos estáticos ou globais. Vamos demonstrar isso criando uma instância global e mais uma vez compilar este exemplo.

    Round #3…

    Linking Executable - objchk_wxp_x86\i386\krnclass.sys for i386
    krnclass.obj : error LNK2019: unresolved external symbol _atexit
                   referenced in function _$E1
    objchk_wxp_x86\i386\krnclass.sys : error LNK1120: 1 unresolved externals

    Como não temos suporte a C++ em Kernel, não temos a run time que implementa toda a inicialização dos objetos estáticos chamando todos os construtores e destrutores globais. Este é um processo um tanto complicado e que vai nos exigir um cadim de conceito. Existe um artigo muito bom do Matt Pietrek que descreve este processo. Parece que isso vai nos dar um belo trabalho, mas felizmente no site da Hollis Technology Solutions existe um MSI que traz uma implementação da run time do C++ para Kernel. O pacote é Open Source, e assim traz todo o código fonte (incluindo um exemplo) e as bibliotecas desta implementação.

    Esta implementação da run time é bem mais ampla que a descrita neste post, e teremos o suficiente para brincar bastante com C++ em Kernel. Para os malloqueiros de plantão, ela traz inclusive as definições do malloc e free. A única coisa desconfortável que encontrei neste pacote é a necessidade de substituir a definição do nosso ponto de entrada DriverEntry para CPP_DRIVER_ENTRY.

    #include 
     
    ...
     
    /****
    ***
    **      DriverEntry 
    */
    CPP_DRIVER_ENTRY(IN PDRIVER_OBJECT  pDriverObject,
                     IN PUNICODE_STRING pusRegistryPath)
    {
     
    ...

    Vou deixar um exemplo de como utilizar este pacote para download admitindo que você fez a instalação do MSI em C:\Library\HtsCpp. Este exemplo também poderá nos servir de base para futuros posts que venham a utilizar algum recurso de orientação a objeto. De qualquer forma, segue o preview de como deve ficar seu sources.

    TARGETNAME=KrnClass
    TARGETPATH=obj
    TARGETTYPE=DRIVER
     
    !if "$(DDKBUILDENV)" == "fre"
    BUILD_CONFIG=libFre
    !else
    BUILD_CONFIG=libChk
    !endif
     
    TARGETLIBS=C:\Library\HtsCpp\$(BUILD_CONFIG)\i386\HtsCpp.lib
     
    INCLUDES=C:\Library\HtsCpp\sys\inc;
     
    SOURCES=KrnClass.cpp

    Até mais mais! 🙂

    KrnClass.zip

  • RtlGetModuleBase & RtlGetProcAddress

    No post ExAllocatePool(WithoutTag) falei um pouco sobre os conflitos de utilizar APIs novas em drivers e acabar tornando-os incompatíveis com sistemas legados. Nossa proposta inicial era ter um único binário que pudesse ser executado tando em Windows NT como em sistemas mais recentes. Chegamos a uma solução que pode não ser considerada a ideal, onde utilizamos sempre as funções antigas mesmo em sistemas que suporte APIs mais novas.

    Pensando em resolver este tipo de limitação, o DDK nos trouxe a MmGetSystemRoutineAddress. Analogamente à GetProcAddress exportada pela Kernel32.dll, a MmGetSystemRoutineAddress obtém o endereço de uma função dinamicamente a partir de seu nome de exportação. Mas o mundo ainda não estará a salvo enquanto o Windows NT sobreviver. Essa nova função é implementada somente a partir do Windows 2000.

    Já que não temos uma alternativa para Windows NT, vamos fazer uma. Com um ou dois conceitos sobre PE, implementamos uma versão desta função. A estrutura do PE carrega alguns quilos de regras, mas para nossa necessidade podemos implementar uma versão light. Se você quer ter detalhes das regras adotadas pelo PE, você pode dar uma olhada no artigo An In-Depth Look into the Win32 Portable Executable File Format por Matt Pietrek.

    Bom, agora que já lemos todo o artigo e já sabemos tudo sobre PE, segue protótipo do algoritmo básico de como caminhar pela estrutura do PE procurando por uma determinada função exportada pelo nome. Esta função não é fornecida pelo DDK do Windows NT e está definida no código fonte exemplo disponível para download.

    NTSTATUS RtlGetProcAddress(IN PVOID     pBaseAddress,
                               IN LPCSTR    pszFunctionName,
                               IN NTPROC    *pProcedure);

    Repare que os parâmetros de entrada são o endereço base, o nome da função desejada e por fim o endereço onde será armazenado o endereço desta função. Mas onde é que vou conseguir esse tal de endereço base? Lembre-se de que as funções que estamos procurando são exportadas pela ntoskrnl.lib e que são implementadas no módulo ntoskrnl.exe. Para nos certificar de que a função que procuramos é realmente exportada por este módulo, utilize o “Dependecy Walker” para visualizar a tabela de exportação deste módulo. Como já comentei antes, algumas funções são implementadas como macros, desta forma, sua definição estará em um arquivo de header e não na tabela de exportação.

    Para obtermos o endereço base do módulo que exporta estas funções, vamos utilizar a ZwQuerySystemInformation, que embora não seja documentada pela Microsoft, já existem várias publicações que comentam a respeito dela. Desta forma definiremos a RtlGetModuleBase que terá o comportamento similar à bem conhecida GetModuleHandle. Esta função também está definida no exemplo para download.

    NTSTATUS
    NTAPI
    ZwQuerySystemInformation(IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
                             IN OUT PVOID SystemInformation,
                             IN ULONG SystemInformationLength,
                             OUT PULONG ReturnLength OPTIONAL)

    Existem muitas funções e definições não documentadas que podem ser de extrema utilidade. O livro Windows NT/2000 Native Api Reference por Gary Nebbett é excelente para fazer uso destas funções. Ele traz protótipos, enums, estruturas utilizadas em chamadas, descrições do que cada função faz e de cada parâmetro. Aqui em nosso exemplo, vamos utilizar apenas as declarações abaixo para obter informações sobre os módulos carregados no address space do sistema.

    typedef struct _SYSTEM_MODULE_INFORMATION   // Information Class 11
    {
        ULONG Reserved[2];
        PVOID Base;
        ULONG Size;
        ULONG Flags;
        USHORT Index;
        USHORT Unknown;
        USHORT LoadCount;
        USHORT ModuleNameOffset;
        CHAR ImageName[256];
     
    } SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;

    O membro “Base” da estrutura acima nos traz o endereço base do módulo que é onde o PE está. No código de exemplo está a definição da nossa rotina RtlGetModuleBase que tem o seguinte protótipo.

    NTSTATUS RtlGetModuleBase(IN LPCSTR     pszModuleName,
                              OUT PVOID*    ppBaseAddress);

    Com a união dos seus poderes, agora poderemos saber se uma determinada API é implementada no sistema corrente e obter seu endereço. Assim é perfeitamente possível ter um único binário que possa rodar tanto em Windows NT utilizando o ExFreePool, como em sistemas posteriores utilizando o ExFreePoolWithTag. Abaixo segue um exemplo bem básico como sempre. É claro que podemos criar uma única rotina de alocação que faria todo o trabalho sujo.

    #include "GetProcAddr.h"
     
    //-f--> Tipo para o ponteiro de função ExFreePoolWithTag
    typedef VOID (NTAPI* PF_EX_FREE_POOL_WITH_TAG)
    (
        IN PVOID  P,
        IN ULONG  Tag 
    );
     
     
    VOID OnDriverUnload(PDRIVER_OBJECT     pDriverObj)
    {
        //-f--> Esta rotina está aqui apenas para permitir
        //      que o driver seja terminado, mesmo vazia.
    }
     
     
    /****
    ***
    **           Era uma vez um Driver...
    **
    */
    NTSTATUS DriverEntry(PDRIVER_OBJECT     pDriverObj,
                         PUNICODE_STRING    pusRegistryPath)
    {
        NTSTATUS                    nts;
        PVOID                       pBaseAddress, pTemp;
        PF_EX_FREE_POOL_WITH_TAG    pfExFreePoolWithTag;
     
        //-f--> Seta rotina de finalização
        pDriverObj->DriverUnload = OnDriverUnload;
     
        //-f--> Obtem o endereço base
        nts = RtlGetModuleBase("ntoskrnl.exe",
                               &pBaseAddress);
     
        //-f--> Testar retorno não mata ninguem, mas a falta
        //      pode matar seu sistema
        if (!NT_SUCCESS(nts))
            return nts;
     
        //-f--> Alocando memória para teste do Free
        pTemp = ExAllocatePoolWithTag(NonPagedPool,
                                      10,
                                      'tseT');
     
        //-f--> Obtem o endereço da API
        nts = RtlGetProcAddress(pBaseAddress,
                                "ExFreePoolWithTag",
                                (NTPROC*)&pfExFreePoolWithTag);
     
        if (NT_SUCCESS(nts))
        {
            //-f--> Se o sistema implementa esta API, então
            //      obteremos sucesso e poderemos chama-la
            pfExFreePoolWithTag(pTemp,
                                'tseT');
        }
        else
        {
            //-f--> Quem não tem cão, caça com gato.
            ExFreePool(pTemp);
        }
     
        return STATUS_SUCCESS;
    }

    Have fun! 🙂

    ExGetProc.zip

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

    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…

    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. 😉