Blog

  • ExAllocatePool(WithoutTag)

    Como a maioria dos desenvolvedores de drivers, eu tambĂ©m tenho que construir versĂ”es de drivers para vĂĄrias versĂ”es do Windows. Ainda nĂŁo vou comentar sobre VXDs neste post, mas jĂĄ posso comentar sobre ter um Ășnico arquivo .SYS que possa rodar tanto em Windows Server 2003 como em Windows NT. Sei que este sistema operacional nĂŁo Ă© mais vendido e nem conta mais com o suporte da Microsoft, mas Ă© impressionante ver como ainda encontramos Windows 95 e Windows NT tanto em ambientes corporativos como nas casas de usuĂĄrios finais. Assim temos sempre que ficar nos esquivando das limitaçÔes de cada sistema operacional quando estamos planejando desenvolver um sistema.

    Uma função que recebeu uma nova versĂŁo foi a ExFreePool, sua nova versĂŁo Ă© a ExFreePoolWithTag, que Ă© implementada a partir Windows 2000. Para termos um Ășnico binĂĄrio que seja executado em ambas as versĂ”es, temos que utilizar a mais velha que ainda Ă© suportada pelos sistemas recentes.

    Vamos considerar o fonte abaixo para um driver que realiza a incrĂ­vel tarefa de alocar memĂłria dinamicamente.

    #include 
     
    NTSTATUS DriverEntry(PDRIVER_OBJECT  pDriverObject,
                         PUNICODE_STRING pusRegistryPath)
    {
       PVOID pTemp;
     
       //-f--> TÎ pensando que é fåcil alocar memória.
        if (!(pTemp = ExAllocatePool(NonPagedPool, 10))
           return STATUS_NO_MEMORY;
     
       //-f--> Memory leak é falta de educação.
        ExFreePool(pTemp);
       return STATUS_SUCCESS;
    }

    Utilizando o ambiente de build para Windows XP para compilar este driver, podemos ver que o mesmo binĂĄrio funciona em Windows Server 2003, Windows XP, Windows 2000 e Windows NT. Ou pelo menos deveria. Quando tentamos iniciar nosso maravilhoso driver em Windows NT, obtemos a seguinte mensagem:

    Mas como? Se dermos uma olhada mais de perto em nosso driver, vamos descobrir que ele Ă© um traidor miserĂĄvel. Como uma DLL, drivers sĂŁo mĂłdulos executĂĄveis que utilizam a clĂĄssica estrutura PE para declarar suas dependĂȘncias. Assim, podemos abrir nosso driver no “Dependency Walker” e ver o que ele espera da vida para poder ser carregado. Por encreça que parĂ­vel, ele realmente depende estaticamente das funçÔes novas de alocação de memĂłria. Traidor maldito!

    Isso acontece porque dentro do arquivo ntddk.h existe um conjunto de defines que acabam mudando as chamadas ExFreePool para ExFreePoolWithTag. Assim podemos pegar todos aqueles 7 quilos de cĂłdigo escritos para NT e utilizĂĄ-los em builds para sistemas mais novos sem mudar uma sĂł linha, ou ainda podemos ter cĂłdigo fonte compartilhado entre drivers para Windows NT e para sistemas mais novos.

    #define POOL_TAGGING 1
     
    ...
     
    #ifdef POOL_TAGGING
    #define ExAllocatePool(a,b) ExAllocatePoolWithTag(a,b,' kdD')
    #define ExAllocatePoolWithQuota(a,b) ExAllocatePoolWithQuotaTag(a,b,' kdD')
    #endif

    EntĂŁo vocĂȘ me faz as seguintes perguntas: Mas qual a real diferença entre versĂŁo velha e a nova? Meu driver pode ter problemas se tentar utilizar a versĂŁo velha rodando em um sistema mais novo? Existe vida inteligente fora da terra?

    A versĂŁo nova veio para ajudar a detectar memory leaks. Cada alocação de memĂłria fica associada a uma Tag. Estas Tags podem ser visualizadas utilizando ferramentas de depuração tais como o WinDbg. Por exemplo: Se toda a sua biblioteca de configuração utilizar uma Tag, e a sua biblioteca de comunicação utilizar outra, fica fĂĄcil saber quais delas estĂĄ deixando memĂłria alocada. Desta forma fica mais fĂĄcil saber qual programador do seu time vocĂȘ vai mandar pra rua. As macros no ntddk.h apenas utilizam uma Tag default para as alocaçÔes de memĂłria que nĂŁo tem uma Tag associada.

    Mas o que acontece com os drivers que foram compilados com o DDK do Windows NT e que rodam sobre o sistemas posteriores? Estes drivers de fato utilizam as funçÔes velhas. Se utilizarmos o Depends neles poderemos ver isso. Por uma questão de compatibilidade, as funçÔes velhas ainda são exportadas em sistemas novos para dar suporte aos drivers antigos. Vamos dar uma olhada na implementação da ExAllocatePool do Windows 2000.

    A implementação da função antiga simplesmente encaminha a alocação de memĂłria para a função nova e utiliza a Tag ‘None’. Isso nos assegura que nĂŁo morreremos de cĂąncer se continuarmos utilizando as funçÔes antigas em um sistema novo.

    Bom, o que eståvamos querendo mesmo? Ah sim, construir um driver novo que possa rodar tanto em Windows NT como em sistemas posteriores. Poderíamos simplesmente utilizar o DDK do Windows NT e contar com a compatibilidade reversa para rodar em sistemas mais novos. Isso é possível, mas algumas das funçÔes que vemos no DDK são definidas como macros. A função IoSetCompletionRoutine é um exemplo clåssico disso. Utilizar um DDK antigo significa não ter algumas destas funçÔes definidas e teríamos que ficar remendando definiçÔes do DDK se quiséssemos desfrutar delas. Existem ainda aqueles casos de pessoas perdidas em ilhas desertas apenas com o DDK mais novo.

    #include 
     
    #undef ExAllocatePool
    #undef ExFreePool
     
    NTSTATUS DriverEntry(PDRIVER_OBJECT  pDriverObject,
                         PUNICODE_STRING pusRegistryPath)
    {
     
    ...

    Para resolver isso, logo depois do include para ntddk.h, vamos dar um #undef nas macros que redirecionam as chamadas do ExAllocatePool para a nova versão e prontio, seus pobremas se acabaram-se. Recompilando teremos um driver novo que utiliza as funçÔes antigas de alocação. Desta forma ele pode ser utilizado em sistemas desde o Windows NT.

    A desvantagem deste mĂ©todo Ă© que seus drivers nĂŁo poderĂŁo utilizar as Tags quando rodando em um sistema que as suporte. Na verdade Ă© possĂ­vel ter um Ășnico binĂĄrio que se rodando em Windows NT utilize ExFreePool e se rodando em um sistema mais novo utilize ExFreePoolWithTag, mas isso Ă© outro Post.

  • Debug or Not Debug

    Aqui estå uma coisa muito simples de se fazer. Gerar compilação condicional utilizando o pre-processador juntamente com o símbolo DBG tem que ser uma tarefa simples. Assim como o símbolo _DEBUG é definido pelos projetos escritos em C/C++ no Visual Studio, o símbolo DBG é definido pelo DDK para informar que o build corrente estå em Checked ou Free. Assim podemos exemplificar com um trecho muito simples de código como segue abaixo:

    //-f--> TĂŽ achando que Ă© fĂĄcil...
    #ifdef DBG
       DbgPrintf("Versão Checked\n");
    #else
       DbgPrintf("Versão Free\n");
    #endif

    O cĂłdigo acima exibirĂĄ a mensagem “VersĂŁo Checked” no quando o build for feito em Checked e “VersĂŁo Free” quando o build for feito em Free, certo?

    ERRADO!!! Isso mesmo, errado! O sĂ­mbolo DBG Ă© definido como 1 quando em Checked e definido como 0 quando Free. Assim temos DBG=1 quando Checked e DBG=0 quando Free. Da maneira que estĂĄ, o cĂłdigo acima ainda exibirĂĄ a mensagem “VersĂŁo Checked” mesmo quando compilado como Free. Isso porque o sĂ­mbolo DBG ainda Ă© definido, definido como 0, mas Ă© definido. Assim para testar adequadamente esta condição devemos fazĂȘ-la com #if no lugar de #ifdef como segue abaixo:

    //-f--> Agora sim, exibe a configuração atual do build
    #if DBG
       DbgPrintf("Versão Checked\n");
    #else
       DbgPrintf("Versão Free\n");
    #endif
     
    //-f--> Testando apenas o build Free
    #if !DBG
       DbgPrintf("Versão Free\n");
    #endif

    Para facilitar a depuração, não é difícil ver em alguns drivers o código que quando compilado em Checked interrompe a execução utilizando o seguinte código.

    //-f--> Breakpoint quando em Checked
    #if DBG
       __debugbreak();
    #endif

    Se o teste do build estivesse incorreto aqui, terĂ­amos um bela tela azul caso o cĂłdigo acima fosse para produção. Como vocĂȘs devem saber, a chamada a breakpoints em cĂłdigo lançam exceçÔes que seriam tratadas pelo debugger, mas se o debugger nĂŁo estiver atachado ao sistema, COISAS HORRIVEIS ACONTECEM.

    No arquivo ntddk.h existe a macro abaixo que ajuda a testar condiçÔes de build em Checked.

    #if DBG
        #define IF_DEBUG if (TRUE)
    #else
        #define IF_DEBUG if (FALSE)
    #endif

    Assim, fica bem clara a sintaxe abaixo:

    IF_DEBUG
    {
       __debugbreak();
    }

    JĂĄ que estamos falando de builds condicionais, podemos tambĂ©m podem ter especializaçÔes de build no arquivo SOURCES. A variĂĄvel de ambiente DDKBUILDENV Ă© definida como “che” quando o build Ă© feito em Checked, ou definida como “fre” quando em Free. Assim podemos, por exemplo, utilizar paths para libs diferentes de acordo com a configuração do build atual.

    !if "$(DDKBUILDENV)" == "fre"
    BUILD_CONFIG=Release
    !else
    BUILD_CONFIG=Debug
    !endif
     
    TARGETLIBS=.\Lib\$(BUILD_CONFIG)\HtsCpp.lib
  • KdPrint((“Hello World!!!”));

    Meu primeiro computadorNeste primeiro post, vou aproveitar que ninguém vai ler mesmo para falar sobre mim. Afinal, as pessoas que se interessarem em ler este blog podem querer conhecer meu perfil com um pouco mais de detalhes. Nascido em 1976, eu nem imaginava o que seria da vida quando aos 13 anos ví um computador pela primeira vez. Meu primo tinha um CP200 e eu nem sabia que tipo de video game era aquele. Quando meu primo fez um upgrade para um poderoso MSX, eu comprei o CP200 dele. Eu simplesmente não sabia nada a respeito. Na época os jogos e aplicativos eram gravados em fita K7 e eu não tinha o gravador para desfrutar de toda essa tecnologia. Resultado, eramos eu e o manual de BASIC e foi assim que aprendi a programar.

    NĂŁo precisou de muito tempo pra eu decidir que queria ser programador, mas tinha que ser para programar uma tal de linguagem C. Eu queria fazer o que os outros achavam difĂ­cil, talvez tenha sido por esse motivo que deixei de fazer o tradicional curso de Processamento de Dados para fazer InformĂĄtica Industrial na ETE Jorge Street em SĂŁo Caetano do Sul.

    Apesar de programar Visual Basic, ainda não sabia nada de C para Windows quando fui fazer meu estågio na Provectus. Eu diria que não pude caí em um lugar melhor. Eles trabalhavam com um hardware próprio que era equipado com um processador V40. Utilizando uma biblioteca de Runtime própria, podimamos fazer com que um programa feito para DOS pudesse rodar naquele painel de coleta de dados. Com o tempo veio a primeira DLL, o primeiro serviço, o primeiro aplicativo, o primeiro driver e a tão esperada primeira tela azul. A placa de rede que recebia as mensagens da rede de coletores também era hardware próprio e começei a dar manutenção no código que jå estava pronto. Tivemos que refazer todo o protocolo da rede dos coletores que era todo feito por nós. Assim, enquanto meus amigos faziam formulårios, querys e relatórios, o fato de depurar um protocolo de rede, parte em assembly e parte em C, tanto o driver como o firmware utilizando um osciloscópio me dava a certeza de que era aquilo que eu queria fazer.

    Depois de quatro anos trabalhando com coletores de dados, a vontade de programar exclusivamente para Windows me fez trabalhar em um site financeiro. Construir componentes MTS e COM+ foi bom para ter uma outra visão do negócio. Mas como era de se esperar, o trabalho em C/C++ para estes fins era pouco e logo acabou, assim foram me colocando em projetos de ASP com SQL, e graças a isso fui trabalhar em uma empresa de segurança da informação. Eu simplesmente não aguentava mais chegar no trabalho e ter que abrir o Query analyzer.

    A Scua me abriu as portas para a programação de baixo nível para Windows. Hooks de janela, DLLs de autenticação (GINA), logon no windows com smartcard e biometria, filtros de File Systems, criptografia de arquivos e partiçÔes em tempo real, controle de acesso, SoftICE e WinDBG. Atualmente trabalho na Open Communications Security e sou responsåvel pelo desenvolvimento de baixo nível de softwares contra fraudes eletrÎnicas. Detectar, combater e despistar root-kits, trojans e key-loggers.

    Pretendo neste blog dar algumas dicas de desenvolvimento C/C++ para Windows, incluindo algumas coisas em User-Mode e outras em Kernel-Mode. Espero poder ajudar oferecendo algumas dicas e facilidades para este pequeno grupo de pessoas que desenvolvem drivers para Windows no Brasil.