Category: Uncategorized

  • Step into Kernel (Vista + USB2)

    Já comentei em outro post que é possível fazer Kernel Debugging através de uma porta USB 2.0. Neste post vou demonstrar essa tal conexão USB 2.0 funcionando. Ê laiá coisa boa. Então vamos deixar de conversinha mole e vamos logo ao que interessa. Se você não entende nada sobre Kernel Debugging, você pode ler este post, que tráz os conceitos e passos básicos sobre o assunto, ou você pode se perguntar “Como é que eu fui cair neste site?” e voltar para o ORKUT.

    O barramento USB não permite que tenhamos conexões diretas entre dois computadores. Para que possamos utilizar uma porta USB para este fim, teremos que contar com a ajuda de um hardware adicional. Apesar de receber o nome de Debug Cable, trata-se de um pequeno dispositivo que une os computadores através de portas USB. Não sei se já existem outros fabricantes para o Debug Cable, mas o que vou utilizar neste experimento é o NET20DC.

    Utilizando o Debug Cable

    Utilizar o Debug Cable é um luxo que apenas o Windows Vista ou sistemas posteriores podem desfrutar. Ele precisa ser conectado a uma porta USB 2.0 sem passar por nenhum HUB do lado TARGET. Mas como saber com certeza quais das minhas portas é de fato 2.0?

    Uma maneira fácil de saber isso, principalmente para quem tem o WDK instalado, é compilar o exemplo USBView, que está na pasta “C:\WinDDK\6000\src\usb\usbview” do WDK. Este programa enumera os controladores e os respectivos dispositivos USB conectados. Desta forma, quando conectarmos o Debug Cable à máquina TARGET, o USBView deve informar em qual porta este novo dispositivo está conectado. Repare que nesta janela podemos ver em qual controlador a porta que estamos utilizando pertence.

    O Windows solicitará um driver ao detectar a presença deste novo dispositivo USB. Do lado TARGET nenhum driver precisa ser instalado para utilizar o Debug Cable. Assim você pode selecionar “Não exibir esta mensagem novamente para este dispositivo” na janela da figura abaixo. Não instalar nenhum driver faz todo o sentido do lado TARGET. Lembre-se de quem vai utilizar esta interface de debug do lado TARGET é a BIOS, e vai repassar o controle para o core do sistema operacional. Vale lembrar também que existe um outro requisito para utilizar o Debug Cable é que a BIOS da máquina TARGET implementar este controle de interface de debug. A parte mais infeliz desta história é que para saber se a sua BIOS está preparada ou não, basta tentar. Ou seja, você pode ter o Debug Cable, uma porta USB 2.0 livre, os cabos e ainda assim correr o risco de nada conectar pelo simples fato da BIOS não dar suporte. Fico feliz em saber que meu notebook dá esse suporte e que não gastei essa grana a toa importando o Debug Cable. Ufa!!

    Modificando o Boot.ini

    Hêin? Boot.ini? Isso não te pertence mais. O Windows Vista utiliza uma nova arquitetura chamada Boot Configuration Data (BCD). Isso levou nosso amigo Boot.ini para a casa do chapéu. Para editar as configurações do BCD, utilizamos a ferramenta BCDEdit.exe, uma aplicação console que precisa ser executada com poderes administrativos. A execução desta ferramenta sem qualquer parâmetro nos retorna a seguite saída.

    Para configurar o sistema de forma a termos duas opções de boot, uma com debug habilitado e outra não, siga os passos abaixo descritos. O Boot Manager gerencia Boot Loaders, que podem ser outros sistemas operacionais, mesmo que anteriores ao Windows Vista, como também podem ser conjuntos de configurações do mesmo sistema. Cada um destes ítens é identificado por um GUID. Observe que o primeiro comando faz uma cópia da configuração atual para uma que contenha a string “Windows Vista Cobaia Mode” como descrição. Em resposta a essa cópia, a ferramenta nos retorna o GUID resultante da cópia realizada. Observe também que no segundo comando habilitamos o debug de kernel para a configuração identificada pelo GUID que acabamos de receber. Neste momento, se dermos uma olhada nas configurações teremos o resultado como é exibido na mesma imagem abaixo.

    Beleza, agora temos que configurar o meio de comunicação que será utilizado pelo Kernel Debugger. creio que a maioria dos micros ainda utilizem portas seriais para este fim. Neste caso, a configuração mais comum seria a de utilizar a porta serial COM1 com um baudrate de 115200. Para setar estas configurações, utilize a seguinte linha de comando. Para ver as configurações atuais, basta executar a segunda linha de comanodo como exibida abaixo.

    Se você ainda não sabe como utilizar a porta serial como meio de comunicação para o debug de kernel, este post fala a respeito. Entretando, neste post vou comentar sobre o debug de kernel utilizando uma porta USB 2.0 como meio de comunicação. Neste caso as configurações serão as descritas na linha de comando exibida abaixo. O TARGETNAME pode receber qualquer nome. Utilizei o nome “WINDBG” por motivos óbvios, mas você pode colocar qualquer nome.

    Como vocês podem ter imaginado, as configurações sobre o meio de comunicação de debug são globais, ou seja, não estão vinculadas a esta ou àquela configuração de boot identificada por um GUID.

    Creio que do lado TARGET já está tudo pronto para efetuar o link. Desta forma, quando reiniciarmos o computador vítima teremos o seguinte menu exibido pelo Boot Manager.

    Configurando o HOST

    Do lado HOST desta brincadeira, teremos que adicionar o driver que controla o Debug Cable e permitir que o Windbg possa utilizar este dispositivo. Apesar de eu estar utilizando Windows Vista no lado HOST, nada impede de você utilizar o Windows 2000 ou superior neste lado da conversa. A brincadeira começa quando você pluga o dispositivo e clica em “Localizar e instalar o driver (recomendado)” na janela de “Novo hardware encontrado” já exibida neste post. Neste momento o Windows procura pelo driver no banco de dados do Windows Update.

    A coisa começa a ficar divertida quando o Windows me pede para inserir o disco que veio com o Debug Cable. Apenas duas palavras ficaram em minha mente neste momento: “Que disco?”. Sem muitas opções eu cliquei em “Eu não tenho essa porcaria de disco! Cê tá loco? Me mostre qualquer coisa pelo amor de Deus”. Alguns de vocês já devem até ter esta seqüência de janelas decoradas de tanto não encontrar drivers para todos os dispositivos que você tem.

    Por fim quando a última janela me foi mostrada, me veio uma luz, mais uma daquelas idéias brilhantes que tenho a cada ano bisexto. Foi quando pensei comigo mesmo “Já sei! Vou visitar o site do fabricante e procurar por uma sessão de suporte.”.

    E no site do fabricante…

    E no site da Microsoft…

    Em fim, mandei o tal e-mail para obter qualquer sinal de que eu não tivesse jogado meu dinheiro no lixo. Em menos de uma hora recebo a resposta com uma lista de requisitos e passos a serem seguidos, mas o mais importante só me foi revelado no final.

    Eu sabia que podia contar com a inteligência e a agilidade da Microsoft para resolver isso. Mas fica minha dica para os que estão lendo este post. Quando o Windows solicitar o driver do novo dispositivo, aponte a pasta “C:\Program Files\Debugging Tools for Windows\usb”.

    E é com prazer que vos mostro a janela a seguir:

    Configurando o Windbg

    Agora é brincadeira de criança. Selecione o ítem “Kernel Debug…” do menu “File”. Clique na aba “USB 2.0” e coloque o mesmo TARGETNAME que você escolheu lá na configuração do TARGET com o BCDEdit.exe. No meu caso, o nome é WINDBG como exibido ao lado.

    Clicando em OK, a janela de comandos do WinDbg deve exibir o texto destacado em vermelho a seguir, e ficar esperando a conexão se completar. A saída abaixo foi obtida com um CTRL+Break após o debugger ter conectado.

    Microsoft (R) Windows Debugger Version 6.8.0004.0 AMD64
    Copyright (c) Microsoft Corporation. All rights reserved.
     
    Using USB2 for debugging
    Waiting to reconnect...
    USB2: Write opened
    Connected to Windows Vista 6000 x86 compatible target, ptr64 FALSE
    Kernel Debugger connection established.
    Symbol search path is: SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols
    Executable search path is:
    Windows Vista Kernel Version 6000 MP (1 procs) Free x86 compatible
    Built by: 6000.16584.x86fre.vista_gdr.071023-1545
    Kernel base = 0x82000000 PsLoadedModuleList = 0x82111e10
    System Uptime: not available
    Break instruction exception - code 80000003 (first chance)
    *******************************************************************************
    *                                                                             *
    *   You are seeing this message because you pressed either                    *
    *       CTRL+C (if you run kd.exe) or,                                        *
    *       CTRL+BREAK (if you run WinDBG),                                       *
    *   on your debugger machine's keyboard.                                      *
    *                                                                             *
    *                   THIS IS NOT A BUG OR A SYSTEM CRASH                       *
    *                                                                             *
    * If you did not intend to break into the debugger, press the "g" key, then   *
    * press the "Enter" key now.  This message might immediately reappear.  If it *
    * does, press "g" and "Enter" again.                                          *
    *                                                                             *
    *******************************************************************************
    nt!RtlpBreakWithStatusInstruction:
    820818d0 cc              int     3
    0:kd>

    Agora acabou a parte fácil, os próximos passos já serão referentes a encontrar o bug. Mas isso vai ter que ficar para um outro post.

    Espero ter ajudado.
    T+ 🙂

  • DriverEntry.com.br dá as caras

    Olá pessoal, prometo que este post vai ser curtinho. Essa notícia é para os escovadores de bit de plantão.

    No próximo dia 29 de março, acontecerá o Quarto Encontro de Programadores que é organizado pelo grupo de C/C++ Brasil.

    O evento vai trazer uma série de palestras com verdadeiras autoridades no assunto. Acho que o único ilustre desconhecido serei eu mesmo. Fui convidado para falar um pouco sobre arquitetura e desenvolvimento de drivers para Windows.

    Se você não tem a menor idéia de como os drivers são organizados e de como você pode construí-los, então esta é sua chance de continuar assim, mas com uma boa oportunidade de ver outras excelentes palestras. Já dei alguns treinamentos, mas não posso dizer que tenho grandes experiências com palestras. Acho que a maior palestra que já dei era composta por um público de aproximadamente de três pessoas. Isso contando com a minha mãe que ficava o tempo todo me mandando calar a boca pra ela poder ouvir a novela.

    De qualquer forma vou me esforçar para não fazer feio por lá. Felizmente meu amigo Strauss, que por sinal será um dos palestrantes, me deu uma forcinha publicando este post com dicas para palestrantes.

    Até mais…

  • KeWaitForMultipleObjects, pero no mucho!

    Há um tempo atrás, meu amigo Lesma publicou um post sobre suas aventuras Kernel a dentro em busca do motivo de uma tela azul. O problema foi causado por uma chamada incorreta à função KeWaitForMultipleObjects na tentativa de esperar por 4 objetos. Pois é Wanderley, isso que é dá herdar código de Deus e o mundo. Enfim é aquela coisa toda. Só porque a função tem a palavra “Múltiplo” no nome, não significa que podemos esperar por essa quantidade absurda de 4 objetos. Neste post vou dar um simples exemplo de como utilizar esta função.

    Onde mora o limite?

    A função KeWaitForMultipleObjects utiliza de um array de elementos do tipo KWAIT_BLOCK que contém os dados referentes aos múliplos objetos da espera que será realizada. Em muitos casos, essa quantidade de múltiplos objetos que participam da espera não passa de três objetos. A fim de evitar de sempre ter de construir um array de estruturas de KWAIT_BLOCK para realizar uma espera múltipla, a estrutura que representa uma thread em Kernel mode, a KTHREAD, contém um array de quatro elementos desta estrutura embutida nela, como se pode observar abaixo.

    kd> dt nt!_KTHREAD
       +0x000 Header           : _DISPATCHER_HEADER
       +0x010 MutantListHead   : _LIST_ENTRY
       +0x018 InitialStack     : Ptr32 Void
       +0x01c StackLimit       : Ptr32 Void
       +0x020 Teb              : Ptr32 Void
       +0x024 TlsArray         : Ptr32 Void
       +0x028 KernelStack      : Ptr32 Void
       +0x02c DebugActive      : UChar
       +0x02d State            : UChar
       +0x02e Alerted          : [2] UChar
       +0x030 Iopl             : UChar
       +0x031 NpxState         : UChar
       +0x032 Saturation       : Char
       +0x033 Priority         : Char
       +0x034 ApcState         : _KAPC_STATE
       +0x04c ContextSwitches  : Uint4B
       +0x050 IdleSwapBlock    : UChar
       +0x051 Spare0           : [3] UChar
       +0x054 WaitStatus       : Int4B
       +0x058 WaitIrql         : UChar
       +0x059 WaitMode         : Char
       +0x05a WaitNext         : UChar
       +0x05b WaitReason       : UChar
       +0x05c WaitBlockList    : Ptr32 _KWAIT_BLOCK
       +0x060 WaitListEntry    : _LIST_ENTRY
       +0x060 SwapListEntry    : _SINGLE_LIST_ENTRY
       +0x068 WaitTime         : Uint4B
       +0x06c BasePriority     : Char
       +0x06d DecrementCount   : UChar
       +0x06e PriorityDecrement : Char
       +0x06f Quantum          : Char
       +0x070 WaitBlock        : [4] _KWAIT_BLOCK
       +0x0d0 LegoData         : Ptr32 Void
       +0x0d4 KernelApcDisable : Uint4B
       +0x0d8 UserAffinity     : Uint4B
       +0x0dc SystemAffinityActive : UChar
       +0x0dd PowerState       : UChar
       +0x0de NpxIrql          : UChar
       +0x0df InitialNode      : UChar
       +0x0e0 ServiceTable     : Ptr32 Void
       +0x0e4 Queue            : Ptr32 _KQUEUE
       +0x0e8 ApcQueueLock     : Uint4B
       +0x0f0 Timer            : _KTIMER
       +0x118 QueueListEntry   : _LIST_ENTRY
       +0x120 SoftAffinity     : Uint4B
       +0x124 Affinity         : Uint4B
       +0x128 Preempted        : UChar
       +0x129 ProcessReadyQueue : UChar
       +0x12a KernelStackResident : UChar
       +0x12b NextProcessor    : UChar
       +0x12c CallbackStack    : Ptr32 Void
       +0x130 Win32Thread      : Ptr32 Void
       +0x134 TrapFrame        : Ptr32 _KTRAP_FRAME
       +0x138 ApcStatePointer  : [2] Ptr32 _KAPC_STATE
       +0x140 PreviousMode     : Char
       +0x141 EnableStackSwap  : UChar
       +0x142 LargeStack       : UChar
       +0x143 ResourceIndex    : UChar
       +0x144 KernelTime       : Uint4B
       +0x148 UserTime         : Uint4B
       +0x14c SavedApcState    : _KAPC_STATE
       +0x164 Alertable        : UChar
       +0x165 ApcStateIndex    : UChar
       +0x166 ApcQueueable     : UChar
       +0x167 AutoAlignment    : UChar
       +0x168 StackBase        : Ptr32 Void
       +0x16c SuspendApc       : _KAPC
       +0x19c SuspendSemaphore : _KSEMAPHORE
       +0x1b0 ThreadListEntry  : _LIST_ENTRY
       +0x1b8 FreezeCount      : Char
       +0x1b9 SuspendCount     : Char
       +0x1ba IdealProcessor   : UChar
       +0x1bb DisableBoost     : UChar

    Destes quatro elementos, três são destinados a serem utilizados pela KeWaitForMultipleObjects, enquanto o que quarto elemento é de uso reservado do sistema para implementar a espera com timeout.

    Para esperar até 3 objetos, o código da espera ficaria como segue abaixo:

        //-f--> Array de PVOIDs
        PVOID       pObjects[3];
     
        //-f--> Temos que inicializar um array de ponteiros para
        //      os objetos os quais a espera será baseada.
        pObjects[0] = &kEvent1;
        pObjects[1] = &kEvent2;
        pObjects[2] = &kEvent3;
     
        //-f--> Chamada simples para uma espera múltipla de
        //      3 objetos.
        nts = KeWaitForMultipleObjects(3,
                                       pObjects,
                                       WaitAll,
                                       Executive,
                                       KernelMode,
                                       FALSE,
                                       NULL,
                                       NULL);

    Isso significa que não podemos esperar por mais de 3 objetos por vez? Também não é bem assim. Quando for necessário esperar por mais de três objetos, teremos que alocar um buffer grande o suficiente para manter um array de N elementos da estrutura KWAIT_BLOCK. Onde N é menor ou igual a 64. Se houver mais objetos que isso, teremos um belo MAXIMUM_WAIT_OBJECTS_EXCEEDED.

    “If a buffer is supplied, the Count parameter may not exceed MAXIMUM_WAIT_OBJECTS. If no buffer is supplied, the Count parameter may not exceed THREAD_WAIT_OBJECTS.”

    //-f--> From wdm.h
     
    #define THREAD_WAIT_OBJECTS 3       // Builtin usable wait blocks
     
    #define MAXIMUM_WAIT_OBJECTS 64     // Maximum number of wait objects

    Neste caso, para uma espera de 10 objetos teríamos o seguinte código:

       //-f--> Array de PVOIDs
        PVOID           pObjects[10];
     
        //-f--> Array adicional de KWAIT_BLOCKs
        PKWAIT_BLOCK    pWaitBlocks = NULL;
     
        //-f--> Temos que inicializar um array de ponteiros para
        //      os objetos os quais a espera será baseada.
        pObjects[0] = &kEvent1;
        pObjects[1] = &kEvent2;
        pObjects[2] = &kEvent3;
        pObjects[3] = &kEvent4;
        pObjects[4] = &kEvent5;
     
        //-f--> Por encreça que parível, eu sei usar loops e arrays,
        //      mas fica mais didático mostrar que os objetos não
        //      precisam necessariamente estar em um array, enquanto
        //      que os ponteiros para eles sim.
        pObjects[5] = &kEvent6;
        pObjects[6] = &kEvent7;
        pObjects[7] = &kEvent8;
        pObjects[8] = &kEvent9;
        pObjects[9] = &kEvent10;
     
        //-f--> Não poderemos utilizar o array de KWAIT_BLOCKS
        //      embutido na KTHREAD, teremos que alocar o nosso array.
        pWaitBlocks = (PKWAIT_BLOCK) ExAllocatePool(NonPagedPool,
                                                    sizeof(KWAIT_BLOCK) * 10);
     
        //-f--> Testar alocação não mata ninguém, mas tenta não
        //      testar para ver quantos morrem.
        ASSERT(pWaitBlocks != NULL);
     
        //-f--> Chamada simples para uma espera múltipla de
        //      10 objetos e sem bug-check.
        nts = KeWaitForMultipleObjects(10,
                                       pObjects,
                                       WaitAll,
                                       Executive,
                                       KernelMode,
                                       FALSE,
                                       NULL,
                                       pWaitBlocks);
     
        //-f--> Memory leak é falta de educação
        ExFreePool(pWaitBlocks);

    Resumidamente é isso aí.
    Até mais mais…

  • Seminários OSR em casa

    Se um dos problemas que impediam você de fazer um treinamento especializado em desenvolvimento de device drivers na OSR era o fato de ter medinho de voar, então seus pobremas se acabaram-se. A OSR, depois de anos e anos de experiência ensinando a escrever drivers, agora investe nos Webinars. Essa novidade já está anunciada desde o dia 4 de janeiro, mas ainda causa surpresa quando comento com algumas pessoas. Assim, resolvi colocar esse post e tentar repassar a novidade para mais pessoas de uma só vez.

     

    Mais barato?

    A vantagem financeira está no fato de evitar de comprar as passagens aéreas e pagar a hospedagem de uma semana nos Estados Unidos. Tá, tudo bem, não é você quem vai pagar mesmo, mas pense que nem todos trabalham em empresas multi-nacionais com milhares de dólares destinados a treinamentos de funcionários. Quando fiz o treinamento na OSR, tudo foi pago pela empresa onde eu trabalhava, mas se eu colocar tudo na ponta do lápis, foram cerca de R$13000 de investimento para participar de um seminario de U$2350, que na cotação de hoje seriam traduzidos em aproximadamente R$4143, ou seja, aproximadamente um terço do valor total. Isso pode simplesmente ser o fator que vai impedir você de fazer o treinamento. Então, você já pode ir correndo dizer pro papai e pra mamãe que eles agora têm mais poder aquisitivo para treinar você. Mas não três vezes mais. Pelo que me parece, os Webinars são relativamente mais caros que os seminários convecionais. O seminário que participei teve duração de 32 horas e abordava o desenvolvimento de drivers de File System (um assunto um cadim avançado), enquanto O primeiro webinar oferecido pela OSR fará uma rápida introdução ao WDF durante 20 horas pelo mesmo preço.

    Outras vantagens

    Um outro ponto bastante relevante é o fato de não ter que se ausentar de sua casa ou da empresa que você trabalha durante o seminário. Algumas das pessoas com quem conversei a respeito, me disseram que um grande problema é o fato de ter que sair de casa durante uma semana inteira. Alguns deles têm filhos pequenos ou outros tantos motivos que você pode imaginar, mas creio que o motivo mais comum que pode prender o profissional, são os intermináveis pepinos inadiáveis que só você pode resolver. Bom pelo menos este webinar é composto por 5 aulas de 4 horas. Isso permite que você cuide de sua pequena hortinha de pepinos enquanto participa dos treinamentos.

    Não é apenas um ppt bem caro

    Ao contrário do que se pode imaginar, os seminários serão interativos utilizando conexões de duas vias onde o participante poderá, além de receber todas as instruções ao vivo, fazer perguntas e ainda receber todo o material impresso como nos outros seminários. O interessado no webinar fará o download da ferramenta que estabelece a conexão e participará de uma sessão de 15 minutos para testar o computador que será usado nas sessões, e assim, certificar-se de que o participante não terá qualquer problema durante o treinamento.

    Agora, se sua empresa não quiser mesmo pagar um treinamento pra você, já fica pelo menos mais fácil pagar por você mesmo e poder usar isso como uma nova estrelinha em seu currículo.
    Até mais!

  • CLEANUP e CLOSE

    Já escrevi um post que descreve os passos de como criar um driver bem simples. Este driver simplesmente mantém uma lista de strings que recebe via IRP’s de escrita e as devolve nas IRP’s de leitura. Muito bem, se você ainda não sabe o que é uma IRP, um outro post tenta explicar o que são as IRP’s e até descreve os passos necessários para utilizar o IRP Tracker para observar as IRP’s indo e vindo. Se fizermos um pequeno teste utilizando o IRP Tracker sobre o driver de exemplo que acabei de comentar, teremos uma saída semelhante a figura abaixo.


    Podemos ver as todas as IRP’s que nosso device recebeu desde a IRP_MJ_CREATE até a IRP_MJ_CLOSE. Entre estas IRP’s notamos que algumas delas não foram completadas com sucesso. Como nenhuma rotina foi designada para tratar IRP_MJ_CLEANUP, estas IRP’s são terminam com STATUS_INVALID_DEVICE_REQUEST. Neste post vou comentar um pouco sobre a esta IRP e como seu device interage com os processos que obtiveram um handle para ele.

    Para ver detalhes da IRP, dê um duplo clique sobre a linha da IRP no IRP Tracker para que a janela abaixo seja exibida. Nessa janela é possível ver, entre outros detalhes, o FileObject utilizado na IRP. Isso nos será útil neste post durante os testes.


    Quando o IRP_MJ_CLEANUP ocorre?

    O Object Manager envia uma IRP de CLEANUP a fim de notificar o driver de que o último handle para determinado FileObject foi fechado. Como alguns de vocês devem saber, quando uma aplicação usa o CreateFile para obter um handle para nosso device, isso resulta na criação de um FileObject. As operações subseqüentes utilizando este handle estarão vinculadas a este FileObject. Mais detalhes sobre isso neste post.

    Um FileObject não tem uma relação direta com um handle. Um handle pode ser duplicado ou mesmo herdado do processo pai na criação de um novo processo. O resultado destas ações é ter vários handles que serão traduzidos para o mesmo FileObject. Desta forma, nem sempre quando uma aplicação chama a função CloseHandle, uma IRP de CLEANUP ou CLOSE é enviada ao driver.

    Para acompanhar os passos abaixo, o código fonte do programa de exemplo está disponível para download ao final deste post. Este programa utiliza o driver de exemplo construído em outro post, o código fonte do driver pode ser baixado daqui. A partir dos fontes você pode compilar e instalar o driver de teste. Se você não sabe como fazer isso, este post pode te ajudar. Depois de instalar o driver de teste, seria interessante poder executar as chamadas abaixo com a ajuda de um depurador, e assim, poder observar os resultados no IRP Tracker a cada linha executada.

    É importante ler os comentários do fonte abaixo. Fiquei com preguiça de duplicar essas informações aqui no post.

    /****
    ***     main
    **
    **      Espero que todos saibam que este é o ponto
    **      de entrada de uma aplicação. Caso contrário,
    **      você pode estar se preciptando em ler um blog
    **      de driver para Windows.
    */
    int main(int argc, char* argv[])
    {
        HANDLE  h1, h3, h3;
        CHAR    szBuffer[100];
        DWORD   dwBytes;
     
        //-f--> Aqui estamos abrindo o primeiro handle
        //      para o nosso device. Este é o passo 1
        //      no IRP Tracker. Verifique o valor do
        //      FileObject para poder comparar em operações
        //      futuras.
        h1 = CreateFile("\\\\.\\EchoDevice",
                        GENERIC_ALL,
                        0,
                        NULL,
                        OPEN_EXISTING,
                        0,
                        NULL);
     
        //-f--> Aqui estamos abrindo o segundo handle
        //      para o nosso device. Este é o passo 2
        //      no IRP Tracker. Verifique o valor do
        //      FileObject para poder comparar em operações
        //      futuras.
        h3 = CreateFile("\\\\.\\EchoDevice",
                        GENERIC_ALL,
                        0,
                        NULL,
                        OPEN_EXISTING,
                        0,
                        NULL);
     
        //-f--> Aqui lançaremos uma IRP_MJ_READ para o
        //      primeiro FileObject obtido. Passo 3 no
        //      IRP Tracker. Note que o primeiro FileObject
        //      é utilizado nesta IRP.
        ReadFile(h1,
                 szBuffer,
                 sizeof(szBuffer),
                 &dwBytes,
                 NULL);
     
        //-f--> Aqui lançaremos uma IRP_MJ_READ para o
        //      segundo FileObject obtido. Passo 4 no
        //      IRP Tracker. Note que o segundo FileObject
        //      é utilizado nesta IRP.
        ReadFile(h3,
                 szBuffer,
                 sizeof(szBuffer),
                 &dwBytes,
                 NULL);
     
        //-f--> A duplicação de handle não é notificada
        //      ao driver. Apenas o Object Manager sabe
        //      disso. Não existe nenhum passo correspondente
        //      no IRP Tracker.
        DuplicateHandle(GetCurrentProcess(),
                        h1,
                        GetCurrentProcess(),
                        &h3,
                        0,
                        FALSE,
                        DUPLICATE_SAME_ACCESS);
     
        //-f--> Como o terceiro handle foi obtido atravéz de
        //      uma duplicação do primeiro, o FileObject
        //      correspondente é o mesmo do primeiro handle.
        //      Passo 5 no IRP Tracker. Note que o primeiro
        //      FileObject é utilizado nesta IRP.
        ReadFile(h3,
                 szBuffer,
                 sizeof(szBuffer),
                 &dwBytes,
                 NULL);
     
        //-f--> Como temos dois handles para o primeiro FileObject,
        //      fechar um deles não implicará em nenhuma notificação
        //      ao nosso device. Não existe passo correspondente no
        //      IRP Tracker.
        CloseHandle(h1);
     
        //-f--> Este handle não foi duplicado, assim, quando o fecharmos,
        //      uma IRP_MJ_CLEANUP seguido de uma IRP_MJ_CLOSE serão
        //      enviadas ao driver. Passo 6 no IRP Tracker.
        CloseHandle(h3);
     
        //-f--> Fechando este handle, teremos o mesmo comportamento
        //      observado no fechamento do handle h3. Do ponto de
        //      vista do driver, o primeiro FileObject será destruído
        //      agora. Passo 7 no IRP Tracker.
        CloseHandle(h3);
     
        //-f--> E todos viveram felizes para sempre.
        return 0;
    }

    Obter um handle para um objeto nos assegura que este objeto será válido enquanto não fecharmos o handle. Um objeto só pode ser destruído pelo sistema depois que todos os handles para ele forem fechados. Para isso, o Object Manager mantém dois contadores, o ProcessHandleCount e o SystemHandleCount. O primeiro deles mantém a quantidade de handles abertos para um objeto em um determinado processo. O outro mantém a soma de todos os ProcessHandleCount para o objeto no sistema. Estes contadores são decrementados à medida que os handles são fechados. Quando chegam a zero, uma IRP_MJ_CLEANUP é gerada.

    Normalmente a IRP_MJ_CLEANUP nos serve como evento para cancelar qualquer operação assincrona relativa ao FileObject que está sendo finalizado. IRP’s são ligadas às threads que às lançaram e qualquer IRP assincrona deveria ser devidamente cancelada neste momento.

    Então para quê serve o IRP_MJ_CLOSE?

    Além dos contadores de referência acima citados, existe também o ObjectReferenceCount, que além de ser incrementado quando um novo handle é obtido, também é incrementado quando uma referência de kernel é feita utilizando funções do tipo ObReferenceObject por exemplo. Estas chamadas incrementam o ObjectReferenceCount sem que um novo handle seja gerado. Para quem conhece COM, esta chamada tem o comportamento semelhante ao AddRef. Isso permite que o objeto se mantenha válido para o kernel, mesmo depois que todos os handles para ele foram destruídos. Enfim, quando este contador chegar a zero, então a IRP_MJ_CLOSE é enviada.

    Um driver pode associar estruturas de dados a um determinado FileObject utilizando os pointeiros FsContext e FsContext2 conforme eu já comentei neste outro post. Estas estruturas só poderão ser desalocadas quando recebermos o IRP_MJ_CLOSE.

    Operações após o IRP_MJ_CLEANUP

    Quando recebemos uma IRP_MJ_CLEANUP, não significa que o fim está próximo. Outros componentes do kernel podem lançar novas IRP’s de leitura ou escrita mesmo depois deste evento. Sem falar das IRP_MJ_INTERNAL_DEVICE_CONTROL que podem ser trocadas entre drivers.

    Um cenário muito comum no desenvolvimento de File Systems é justamente quando uma refêrencia de um FileObject que é mantida pelo Cache Manager. Mesmo quando todos os handles forem fechados pelas aplicações, o sistema ainda retém esta refêrencia antecipando que alguma aplicação pode querer abrir o mesmo arquivo novamente. O Cache Manager possui system threads que realizam a então chamada “Escrita retardada”. Este recurso concentra várias escritas a um arquivo em uma única operação posterior a fim de diminuir o número de acessos a disco, assim ganhando performance. É muito comum tais escritas serem realizadas depois que o arquivo teve todos os seus handles fechados por aplicações, e assim temos escritas depois do IRP_MJ_CLEANUP.

    No final de tudo isso, espero mais uma vez ter mais ajudado que atrapalhado. Assuntos referentes a Cache Manager, Memory Manager e File Systems não são muito triviais, mas são interessantes o suficiente para ler a respeito e entender o que o sistema faz antes de falar que tudo é uma porcaria.

    Até a próxima!

    TestCleanup.zip

  • E o pulso ainda pulsa

    Antes de completar três meses sem qualquer sinal de vida, aqui estou a dar uma espiada no que ocorre na blogosfera. Este final de ano foi realmente corrido. Como já devem saber, minha ida para IBM contribuiu um pouco para esta minha ausência. Tudo bem, a faculdade também ajudou bastante, no finalzinho do ano reservei um tempo para mim mesmo e fui dar uma passeada na praia. Mas vamos deixar de conversa mole e ir ao que interessa.

    Neste post, de volta ao mundo dos vivos, vou comentar apenas pequenas coisas e deixar para depois (mas ainda para esta vida) um post mais elaborado, como os que vocês estão acostumados a ver por aqui.

    Debug em Free

    Uma das coisas que tive que aprender a conviver na IBM é a depurar o driver que escrevemos sempre com o Build Free, ou seja, com otimizações e tudo mais que uma compilação release merece. Eu bem que tentei relutar dizendo que é importante que tenhamos uma versão checked para testes e que seria valioso poder rodar nosso driver com todos os ASSERT‘s ligados e verificando qualquer coisa fora do normal que possa ocorrer. Mas acreditem, não é fácil convencer este povo que entre Indianos e Norte-Americanos já estão acostumados com essa situação. Foi procurando boas justificativas para termos uma versão checked utilizável que encontrei este post. Pois é, as justificativas são realmente muito boas, mas ainda defendo que deveriamos ter uma versão checked. Estou preparando um post que fala um pouco sobre isso.

    Por falar em saber inglês

    Alguns posts antes deste, eu falei do MSDN traduzido para algumas linguas, incluindo o português. O recado foi que, dá pra se virar um pouquinho sem saber inglês, mas se você ainda não sabe e pretende desenvolver drivers para viver, então trate de aprender o quanto antes. Me lembrei deste post hoje quando numa pesquisa do Google um link foi especialmente gratificante.

    Dicas do IoGetDeviceObjectPointer

    Bom, se alguém conseguiu pegar alguma dica, por favor me passe. Se um dia eu resolver aprender uma terceira lingua, agora já tenho uma candidata.

    Ano novo cara nova

    Era para ter acontecido antes, mas infelizmente o tempo é curto para muitas pessoas. Coloquei em prova as habilidades de web designer do meu irmão para dar um tapa no layout do meu blog. Está ficando muito bom, mas ainda não tenho nada de concreto para mostrar. Estarei migrando para o WordPress em pouco tempo. Uma boa parte do trabalho já foi feito, mas ainda há muito a ser feito. Minha principal intenção em migrar para o WordPress é poder utilizar um destes plug-ins que permitem que eu tenha o mesmo post tanto em inglês como em português. Bom, um dos poucos que testamos ainda deu problema. Mas vamos chegar lá.

    Até mais mais…

  • IBM, Here we go!

    Depois de alguns meses em um longo processo seletivo, finalmente já posso compartilhar essa boa notícia com vocês. Boa notícia para mim pelo menos. Hoje, dia 15 de outubro 2007 foi meu primeiro dia como IBMista no prédio da rua Tutóia. Não, eu não desisti do Kernel do Windows para programar Java nem vou programar ABAP. O negócio é o de sempre, tela azul mesmo.

    Como você aprendeu isso?

    Essa é uma pergunta que várias pessoas já me fizeram. Parece brincadeira, mas parece que as coisas têm contribuído naturalmente para o meu desenvolvimento profissional como programador Kernel para Windows. Quando eu era estagiário na Provectus, comecei a ler o livro “Desvendando o Windows NT” por puro hobby, sem a menor esperança de um dia poder trabalhar com as coisas abordadas por aquela literatura, afinal de contas, ter o privilégio de trabalhar em uma empresa de desenvolvimento de hardware no Brasil seria como ganhar na loteria. Bom, meu estágio como programador C foi em uma empresa que desenvolvia seu próprio hardware. Sorte talvez. Quando menos esperei, lá estava eu programando serviços para Windows NT, utilizando memória compartilhada e até um device driver apareceu pra eu dar uma olhada. Como eu já sabia alguma coisa de MFC e Win32 API, o que me atraía mesmo era o desenvolvimento em Kernel Mode. Mais uma vez por hobby, comecei a ler a respeito, sem esperanças devo dizer, apenas por prazer mesmo. É, desta vez o resultado foi contrário e fui trabalhar em uma empresa de compra e venda de ações da bolsa de valores para desenvolver compotentes COM+ em c++. Um ano foi o bastante para aprender o suficiente de ASP, SQL e Java para saber que não era aquilo que eu queria para mim. Felizmente o destino me trouxe a SCUA. Eu nem sabia que eles faziam drivers para Windows. Foi durante uma entrevista para fazer parte do time de aplicações que descobri que havia um time de drivers. Enfim, adivinha em qual time fui parar depois de um tempo. Lá tive contato com profissionais da área, muitos livros, listas de discussão e principalmente a oportunidade de por em prática o que eu aprendia nos livros. Depois veio a Tempest e com ela as chances de fazer um treinamento de File System Drivers em uma das maiores autoridades no assunto. Em seguida um amigo me indicou na IBM, e depois de duas entrevistas no prédio da rua Tutóia e uma entrevista técnica por telefone com o time dos Estados Unidos, bem aqui estou eu. Vou trabalhar no time de desenvolvimento do MVFS para Windows.

    Um dos pontos que mais me anima nesta nova empreitada é a oportunidade de trabalhar com pessoas altamente qualificadas no assunto e poder aprender muito com um time que envolve minimamente americanos, indianos, e agora mais um brasileiro.

    Meu primeiro dia de IBM foi cheio de palestras. Inicialmente sobre a história da IBM, e as outras foram mais sobre os vários procedimentos internos que uma empresa deste porte exige.

    Inté mais…

  • Try, Except, Finally and IoWriteLogErrorEntry (Parte 2)

    Na primeira parte deste post, falei um pouco das necessidades e da lógica básica envolvida em criar e enviar mensagens para o log de eventos do sistema. Hoje vou extender um pouco mais falando sobre como enviar parâmetros nestes eventos, não ficando apenas preso às mensagens fixas definidas no arquivo .mc. Mais um driver de exemplo está disponível para download no final deste post.

    Enviando parâmetros nas mensagens

    Até agora vimos como se pode mandar strings fixas para o EventViewer, mas seria muito útil poder mandar strings geradas pelo driver. Seguindo com o assunto de tratamento de exceções, ao longo deste post escreveremos uma função que possa ser chamada do bloco __except e assim logar qual exceção foi lançada e manipulada pelo seu driver, assim o administrador finalmente vai ficar sabendo que algo errado está acontecendo. Se você não sabe do que estou falando, dê uma olhada na primeira parte deste post.

    Como dizia Jack, o estripador: “Vamos por partes”. Primeiro vamos fazer com que o código da exceção possa ser exibido no evento. Para isso teremos que colocar essa informação no pacote alocado. O membro DumpData da estrutura IO_ERROR_LOG_PACKET pode levar dados binários ao EventViewer. Este membro é um array de ULONG e tem seu tamanho variável definido pelo membro DumpDataSize, que deve ser sempre um múltiplo de sizeof(ULONG). Pensando em nosso exemplo, poderíamos colocar na primeira posição deste array o código da exceção que foi manipulada, tal como 0xC0000005 para “Access Violation“. Veja o exemplo abaixo que adiciona essa funcionalidade. Lembre-se que além de setar o membro DumpDataSize adequadamente, os bytes utilizados pelos elementos deste array deveriam ser levados em consideração na hora de determinar o tamanho do pacote a ser alocado.

    /****
    ***     SendHelloEventWithData
    **
    **      Função que aloca e envia um evento que
    **      envia um parâmetro adicional de 32 bits.
    */
     
    VOID SendHelloEventWithData(IN ULONG    ulData)
    {
        PIO_ERROR_LOG_PACKET    pLogPacket;
     
        //-f--> Aloca a entrada para o evento. Devemos somar
        //      os bytes ultilizados no array DumpData. Este
        //      tamanho deve sempre se multiplo de sizeof(ULONG).
     
        pLogPacket = IoAllocateErrorLogEntry(pDriverObj,
                                             sizeof(IO_ERROR_LOG_PACKET) +
                                             sizeof(ULONG));
     
        //-f--> Inicializa toda a estrutura
        RtlZeroMemory(pLogPacket, sizeof(IO_ERROR_LOG_PACKET));
     
        //-f--> Coloca a mensagem desejada
        pLogPacket->ErrorCode = EVT_HELLO_MESSAGE;
     
     
        //-f--> Preenche o dump binário e seu respectivo tamanho
        pLogPacket->DumpData[0] = ulData;
        pLogPacket->DumpDataSize = sizeof(ULONG);
     
        //-f--> Envia a entrada para a lista de eventos
        IoWriteErrorLogEntry(pLogPacket);
    }

    Chamando esta função passando 0x12345678 como parâmetro, poderemos observar o dado enviado como mostra a figura abaixo.

    Inserindo Strings

    Mas onde essas strings vão aparecer no registro de eventos? Teremos que criar novas mensagens, que façam uso dessas strings, em nosso aquivo de mensagens. A partir daqui, construiremos uma rotina que será chamada dentro do bloco __except para logar a exceção que foi manipulada. Completem o arquivo de mensagens com o seguinte texto.

    MessageId = 0x0002
    Facility = DriverEntryLogs
    Severity = Warning
    SymbolicName = EVT_EXCEPTION_HANDLED_MESSAGE
     
    Language = Portuguese
    A casa caiu na rotina %2 com status %3.
    .
    Language = English
    The house fell down at routine %2 with status %3.
    .

    Reparem que nas novas mensagens existem esses esquisitíssimos %2 e %3. Estes serão os pontos onde a primeira e segunda string serão inseridas respectivamente na mensagem. Mas por que a primeira string é indicada por %2 ao invés de %1? O indicador %1 é reservado para mapear uma string com o nome do driver ou do device. Isso vai depender de qual objeto foi passado no primeiro parâmetro da função IoAllocateErrorLogEntry. Embora %1 seja perfeitamente documentado, algumas vezes a string não é substituída na mensagem quando o driver está iniciando ou terminando. Não me perguntem por quê.

    Para deixar nosso exemplo mais completo, seria muito útil se pudéssemos colocar uma string que carregasse o símbolo definido no header ntstatus.h. Assim teríamos STATUS_ACCESS_VIOLATION para a exceção de código 0xC0000005 no texto exibido pelo EventViewer.

    O primeiro problema é transformar o código da exceção, que é um valor numérico, em uma string. Esse deve ter sido a necessidade de vários programadores Kernel, porque no site da OSR Online existe o fonte de uma funçãozinha que faz isso. Para se ter uma idéia, essa funçãozinha é implementada em aproximadamente 2200 linhas de código. Acha muito? Dê uma olhada no início dela e você entenderá.

    PUCHAR OsrNTStatusToString(NTSTATUS Status) {
     
        switch (Status) {
        case STATUS_SUCCESS:
            return "STATUS_SUCCESS";
        case STATUS_WAIT_1:
            return "STATUS_WAIT_1";
        case STATUS_WAIT_2:
            return "STATUS_WAIT_2";
    ...

    Depois disso, teremos que converter esta string de ANSI para unicode, mas está tudo no código de exemplo.

    Para que as strings possam ser exibidas pelo event EventViewer, mesmo quando o driver não estiver carregado, estas precisam ser copiadas para dentro do pacote. Então agora é determinar qual o tamanho do pacote que vai ser alocado pela função IoAllocateErrorLogEntry. O tamanho do pacote deverá ser somado da quantidade de bytes ocupados pela string unicode incluindo o terminador zero. Acho que será melhor vocês acompanharem os comentários no fonte abaixo que já faz tudo.

    /****
    ***     LogExceptionHandled
    **
    **      Recebe um NTSTATUS e envia um registro para
    **      o log de eventos do sistema
    */
     
    VOID LogExceptionHandled(PWSTR     wzFunctionName,
                             NTSTATUS  nts)
    {
        UNICODE_STRING          usNtStatus;
        ANSI_STRING             asNtStatus;
        UCHAR                   ucFinalSize;
        PWSTR                   pwzTarget;
        PIO_ERROR_LOG_PACKET    pLogPacket;
     
        //-f--> Inicializa uma ANSI_STRING com o código do erro
        RtlInitAnsiString(&asNtStatus,
                          OsrNTStatusToString(nts));
     
     
        //-f--> Passa a string para unicode
        RtlAnsiStringToUnicodeString(&usNtStatus,
                                     &asNtStatus,
                                     TRUE);
     
        //-f--> Calcula o tamanho do pacote sendo respectivamente:
        //
        //  Tamanho da estrutura IO_ERROR_LOG_PACKET
        //  Tamanho do elemento que vai no DumpData
        //  Tamanho da string que conterá o código do erro
     
        //      lembrando que devemos reservar espaço paro terminador
        //  Tamanho da string com o nome da função que gerou a
        //      exceção mais o respectivo terminador (/0)
        ucFinalSize = sizeof(IO_ERROR_LOG_PACKET) +
                      sizeof(ULONG) +
                      usNtStatus.Length + sizeof(WCHAR) +
                      (wcslen(wzFunctionName) + 1) * sizeof(WCHAR);
     
     
        //-f--> Aloca a entrada para o evento
        pLogPacket = IoAllocateErrorLogEntry(g_pDriverObj,
                                             ucFinalSize);
     
        //-f--> Inicializa toda a estrutura
        RtlZeroMemory(pLogPacket, sizeof(IO_ERROR_LOG_PACKET));
     
        //-f--> Armazena o código de erro no array DumpData
        //      e indica seu tamanho no DumpDataSize
        pLogPacket->DumpData[0] = nts;
        pLogPacket->DumpDataSize = sizeof(ULONG);
     
     
        //-f--> Informa onde as strings começam e quantas são
        pLogPacket->StringOffset = sizeof(IO_ERROR_LOG_PACKET) +
                                   pLogPacket->DumpDataSize;
        pLogPacket->NumberOfStrings = 2;
     
        //-f--> Copia a primeira string para dentro do pacote
        pwzTarget = (PWSTR) ((PCHAR)pLogPacket +
                                    pLogPacket->StringOffset);
        wcscpy(pwzTarget,
               wzFunctionName);
     
        //-f--> copia a segunda string para dentro do pacote
        pwzTarget += wcslen(wzFunctionName) + 1;
        wcsncpy(pwzTarget,
                usNtStatus.Buffer,
                usNtStatus.Length / sizeof(WCHAR));
     
     
        //-f--> Como a não está escrito na documentação da
        //      RtlAnsiStringToUnicodeString que sempre haverá
        //      um zero ao final da string convertida, não podemos
        //      admitir que este esteja sempre lá.
        pwzTarget += usNtStatus.Length / sizeof(WCHAR);
        *pwzTarget = 0;
     
        //-f--> Coloca a mensagem desejada
        pLogPacket->ErrorCode = EVT_EXCEPTION_HANDLED_MESSAGE;
     
     
        //-f--> Envia a entrada para a lista de eventos
        IoWriteErrorLogEntry(pLogPacket);
     
        //-f--> Libera a string que foi convertida
        RtlFreeUnicodeString(&usNtStatus);
    }

    Dessa forma, o bloco __except da nossa função de exemplo, dada na primeira parte deste post, poderia ficar como mostra abaixo.

        ...
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            //-f--> Oops! Uma exceção foi lançada. Vamos logar isso,
            //      fingir demência e apenas retornar o código da exceção
            nts = GetExceptionCode();
     
            //-f--> Envia um registro para o log de sistema
     
            LogExceptionHandled(__FUNCTIONW__, nts);
            ASSERT(FALSE);
        }
        ...

    Para testar, vamos fazer uma chamada para a função DupString passando parâmetros inválidos, tais como NULL, e obteremos a seguite saída exibida abaixo. Todo o código fonte do exemplo demonstrado está disponível para download no final deste post.

    O fim está próximo

    Como expliquei na primeira parte deste post, o registro do evento não vai para o disco sincronamente quando a função IoWriteErrorLogEntry é chamada, ao invés disso, o registro do evento vai para uma lista ligada antes de ir para disco. Caso a máquina caia e um dump seja gerado, podemos consultar essa lista com o comando !errlog no WinDbg que analiza o dump gerado. Legal, vamos ver se funciona mesmo? Vamos enviar a mensagem abaixo e em seguida deferenciar um ponteiro nulo, só pra… O código que faz isso está escrito na função KillYourSelf() no código de exemplo disponível para download.

    MessageId = 0x0003
    Facility = DriverEntryLogs
    Severity = Error
    SymbolicName = EVT_GOODBYE_MESSAGE
     
    Language = Portuguese
    Adeus mundo cruel!
    .
    Language = English
    Goodbye cruel world!
    .

    A função KillYourSelf somente será chamada quando um determinado símbolo for definido como você pode ver abaixo. Portanto, se quiser fazer o teste e ver os resultados com seus próprios olhos, modifique a definição deste símbolo no início do código fonte e refaça o teste com uma máquina de teste.

     
    #if _WHERE_IS_THE_PENGUIN_
        //-f--> Meu sonho era ser um driver de Linux
        KillYourSelf();
    #endif

    Inevitavelmente uma tela azul ocorrerá, e com ela um dump de memória será gerado. Abrindo este dump para análise usando o WinDbg, devemos chamar o comando !errlog para termos a saída como mostra abaixo.

    Microsoft (R) Windows Debugger  Version 6.7.0005.0
    Copyright (c) Microsoft Corporation. All rights reserved.
     
     
    Loading Dump File [C:\Documents and Settings\froberto\Desktop\MEMORY.DMP]
    Kernel Complete Dump File: Full address space is available
     
    Symbol search path is: srv*c:\symbols*http://msdl.microsoft.com/download/symbols
    Executable search path is: 
    Windows 2000 Kernel Version 2195 UP Free x86 compatible
    Product: WinNt
    Kernel base = 0x80400000 PsLoadedModuleList = 0x8046a4c0
    Debug session time: Thu Oct 11 11:05:56.375 2007 (GMT-3)
    System Uptime: 0 days 0:02:06.875
    Loading Kernel Symbols
    .................................................................................................
    Loading User Symbols
     
    *******************************************************************************
    *                                                                             *
    *                        Bugcheck Analysis                                    *
    *                                                                             *
    *******************************************************************************
     
    Use !analyze -v to get detailed debugging information.
     
     
    BugCheck 1E, {c0000005, be552682, 1, 0}
     
    Probably caused by : EventSender.sys ( EventSender!KillYourSelf+2a )
     
    Followup: MachineOwner
    ---------
     
    kd> !errlog
    PacketAdr  DeviceObj  DriverObj  Function  ErrorCode  UniqueVal  FinalStat
    81551108   00000000   815c1830    0        402a0001   00000000   00000000
            \Driver\EventSender
            DumpData:  12345678 
    815b1f88   00000000   815c1830    0        802a0002   00000000   00000000
            \Driver\EventSender
            DumpData:  c0000005 
    81535248   00000000   815c1830    0        c02a0003   00000000   00000000
            \Driver\EventSender

    Na coluna ErrorCode, temos os códigos que foram definidos pela compilação do arquivo de mensagens e que estão visíveis no arquivo de header que também foi gerado. Observe as mensagens e seus respectivos identificadores no trecho abaixo. Como pode-se notar, nenhuma das três mensagens de teste utilizadas neste exemplo foram para disco. Todos os registros ainda estavam em memória. Desta forma, não espere ver estas mensagens no EventViewer, elas ainda não foram gravadas em disco.

    //
    // MessageId: EVT_HELLO_MESSAGE
    //
    // MessageText:
    //
    //  Ola mundo!
    //
    #define EVT_HELLO_MESSAGE                ((NTSTATUS)0x402A0001L)
     
     
    //
    // MessageId: EVT_EXCEPTION_HANDLED_MESSAGE
    //
    // MessageText:
    //
    //  A casa caiu na rotina %2 com status %3.
    //
    #define EVT_EXCEPTION_HANDLED_MESSAGE    ((NTSTATUS)0x802A0002L)
     
    //
    // MessageId: EVT_GOODBYE_MESSAGE
    //
    // MessageText:
    //
     
    //  Adeus mundo cruel!
    //
    #define EVT_GOODBYE_MESSAGE              ((NTSTATUS)0xC02A0003L)

    Ufa! Pensei que este post não fosse mais terminar. Mais uma vez espero ter ajudado e até mais.

    EventSender.zip

  • Mais contatos

    Tem se tornado mais freqüente, pelo menos pra mim, a procura por desenvolvedores especializados em Kernel do Windows. O que é freqüente para mim? Bom, além das empresas que presto consultoria regularmante e que imagino já estarem cansados de me perguntar se conheço mais programadores de Kernel, duas empresas me pediram indicações somente neste último mês. Uma delas é a empresa que está levando um dos meus amigos, Rodrigo Strauss, para Porto Alegre. Mais difícil que encontrar vagas para este tipo de desenvolvimento, é encontrar desenvolvedores para este tipo de vaga. Normalmente quando alguém me pede uma indicação, acabo dizendo que depois de mais de dez anos de expericênia como programador, posso contar todos que conheço utilizando apenas uma das mãos.

    O fato é que ambos são difíceis de encontrar, assim, estou me dispondo a encaminhar convites e oportunidades da área de Kernel Windows aos leitores deste blog que se interessarem. Os interessados devem entrar em contato por e-mail para que eu possa expandir essa nossa rede de contatos. Tentem evitar deixar endereços de e-mail na área de comentários do post, pois deste modo qualquer um (incluindo spammers) podem ver seus e-mails. Se você ainda não tem meu endereço de e-mail, basta obté-lo no meu pefil do Blogger. Mesmo quem não estiver interessado em novos desafios, pode mandar um olá. Assim vou poder saber quantos de vocês já trabalham com Kernel de Windows e tentar começar a utilizar os dedos da outra mão nessa contagem.

    Uma iniciativa parecida com esta está no site da OSR Online, onde diversas empresas deixam vagas especializadas em Kernel de Windows. Quem sabe daqui a uns cem anos, quando meu blog estiver mundialmente famoso, empresas queiram publicar vagas para programadores de Kernel do Brasil aqui no DriverEntry.com.br.

    Por enquanto gostaria apenas de sabem quantos são e quem são os Kernel Coders brasileiros, mas estarei aberto a sugestões. Tudo vai depender do volume e da quantidade de respostas que vou obter nessa minha tentativa.

    Até mais…

  • Mais ieu num sei Ingrêis

    Foi através deste post que fiquei sabendo que já está disponível para navegação o beta do conteúdo do MSDN (incluindo o WDK) traduzido automáticamente para Português e outras línguas.

    Para os que têm problemas em entender o texto original em Inglês, agora podem desfrutar de todo o conforto de ter os mesmos problemas em entender o texto agora em Português. Isso porque a tradução automática fica aquela beleza, mas já ajuda para quem tem pouco ou nenhum Inglês.

    O resultado é exibido no já conhecido layout do MSDN, mas o texto principal virá com um split que irá separar o texto em Português do seu equivalente em Inglês. Talvez isso fique melhor quando deixar de ser beta, mas de qualquer forma, não deixa de ser uma alternativa.

    Dê uma olhadinha:
    Introdução do kit de drivers do Windows

    Traduções automáticas podem ser bem engraçadas. Já vi traduções nojentas em manuais de produtos importados. É uma pena eu não ter encontrado aquele manual do PenDrive que comprei há pouco tempo para poder compartilhar algumas pérolas.

    Device Driver Programming == Motorista de dispositivo programando

    Be careful!