Archive for the ‘Debug’ Category

Step into Kernel (VMware 7+WinDbg)

4 de September de 2010

A primeira impressão, que alguns leitores mais antigos podem estar tendo agora, é a de que eu estou ficando realmente sem tempo de escrever novos posts, e por isso, esse novo post seria apenas uma repetição do post Step into Kernel (WMware+WinDbg). Na verdade estou tão surpreso quanto alguns de vocês. Neste post vou falar sobre um pequeno detalhe que vai fazer diferença na hora de fazer debug de Kernel utilizando uma máquina virtual criada com a nova versão da VMware.

O Sintoma

Neste último final de semana, apesar do feriado, do sol e da imensa vontade de pôr o pé na estrada,  tive que ficar em casa resolvendo algumas tretas pessoais. Uma delas envolvia fazer debug de Kernel numa máquina virtual. Nada de incomum nisso até agora. Afinal de contas, fazer debug de Kernel em máquinas virtuais é arroz com feijão para quem trabalha desenvolvendo drivers.

Criei uma VM nova e instalei o Windows nela, realizei as configurações TARGET e HOST exatamente como descrevo naquele outro post, mas para minha surpresa, por algum motivo o WinDbg não conseguia se conectar ao sistema TARGET, permanecendo sempre com a mesma mensagem “Waiting to reconnect…”. Mesmo usando o Ctrl+Alt+D no WinDbg para ver informações internas do depurador, tudo o que eu tinha na saída do WinDbg era a mensagem exibida abaixo.

 
Microsoft (R) Windows Debugger Version 6.12.0002.633 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.
 
Opened \\.\pipe\com_1
Waiting to reconnect...
>>>> ReadAll(0x800703E3,1,0) fails.
>>>> ReadAll(0x800703E3,1,0) fails.
>>>> ReadAll(0x800703E3,1,0) fails.
>>>> ReadAll(0x800703E3,1,0) fails.
>>>> ReadAll(0x800703E3,1,0) fails.
>>>> ReadAll(0x800703E3,1,0) fails.
SYNCTARGET: Timeout.
>>>> ReadAll(0x800703E3,1,0) fails.
Throttle 0x10 write to 0x1
>>>> ReadAll(0x800703E3,1,0) fails.
>>>> ReadAll(0x800703E3,1,0) fails.
>>>> ReadAll(0x800703E3,1,0) fails.

Depois de verificar todos os passos minuciosamente várias vezes, eu não conseguia entender o que estava acontecendo.

A Causa

Ao criar uma nova porta serial na máquina virtual, a qual seria redirecionada para um named pipe na máquina real, reparei que seu nome estava fora do comum. Apesar de esta ser supostamente a única porta serial do sistema, a interface de edição de configurações da VMware mostrava seu nome como “Serial Port 2” como mostra a figura abaixo.

Desconfiei disso e tentei recriar a porta serial achando que seria um bug na inteface da VMware, mas o mesmo nome apareceu. Então nada melhor que abrir o arquivo de configuração da VM no bom e velho Notepad. Foi então que descobri que apesar de não haver uma porta serial além da que eu estava criando na interface de edição da VMware, no arquivo de configuração de fato haviam duas portas seriais como mostro abaixo.

.encoding = "windows-1252"
config.version = "8"
virtualHW.version = "7"
scsi0.present = "TRUE"
scsi0.virtualDev = "lsisas1068"
memsize = "1024"
mem.hotadd = "TRUE"
scsi0:0.present = "TRUE"
scsi0:0.fileName = "Windows 7 x64.vmdk"
ide1:0.present = "TRUE"
ide1:0.autodetect = "TRUE"
ide1:0.deviceType = "cdrom-raw"
floppy0.startConnected = "FALSE"
floppy0.fileName = ""
floppy0.autodetect = "TRUE"
ethernet0.present = "TRUE"
ethernet0.connectionType = "nat"
ethernet0.virtualDev = "e1000"
ethernet0.wakeOnPcktRcv = "FALSE"
ethernet0.addressType = "generated"
usb.present = "TRUE"
ehci.present = "TRUE"
sound.present = "TRUE"
sound.fileName = "-1"
sound.autodetect = "TRUE"
mks.enable3d = "TRUE"
serial0.present = "TRUE"
serial0.fileType = "thinprint"
pciBridge0.present = "TRUE"
pciBridge4.present = "TRUE"
pciBridge4.virtualDev = "pcieRootPort"
pciBridge4.functions = "8"
pciBridge5.present = "TRUE"
pciBridge5.virtualDev = "pcieRootPort"
pciBridge5.functions = "8"
pciBridge6.present = "TRUE"
pciBridge6.virtualDev = "pcieRootPort"
pciBridge6.functions = "8"
pciBridge7.present = "TRUE"
pciBridge7.virtualDev = "pcieRootPort"
pciBridge7.functions = "8"
vmci0.present = "TRUE"
roamingVM.exitBehavior = "go"
displayName = "Windows 7 x64"
guestOS = "windows7-64"
nvram = "Windows 7 x64.nvram"
virtualHW.productCompatibility = "hosted"
printers.enabled = "TRUE"
extendedConfigFile = "Windows 7 x64.vmxf"
ide1:0.startConnected = "TRUE"
ethernet0.generatedAddress = "00:0c:29:91:aa:62"
tools.syncTime = "FALSE"
uuid.location = "56 4d a8 8a e8 f9 f0 a8-62 de e8 25 40 91 aa 62"
uuid.bios = "56 4d a8 8a e8 f9 f0 a8-62 de e8 25 40 91 aa 62"
cleanShutdown = "FALSE"
replay.supported = "FALSE"
replay.filename = ""
scsi0:0.redo = ""
pciBridge0.pciSlotNumber = "17"
pciBridge4.pciSlotNumber = "21"
pciBridge5.pciSlotNumber = "22"
pciBridge6.pciSlotNumber = "23"
pciBridge7.pciSlotNumber = "24"
scsi0.pciSlotNumber = "160"
usb.pciSlotNumber = "32"
ethernet0.pciSlotNumber = "33"
sound.pciSlotNumber = "34"
ehci.pciSlotNumber = "35"
vmci0.pciSlotNumber = "36"
scsi0.sasWWID = "50 05 05 6a e8 f9 f0 a0"
vmotion.checkpointFBSize = "134217728"
usb:0.present = "TRUE"
usb:1.present = "TRUE"
ethernet0.generatedAddressOffset = "0"
vmci0.id = "1083288162"
usb:1.deviceType = "hub"
usb:0.deviceType = "mouse"
ide1:0.fileName = "auto detect"
unity.wasCapable = "FALSE"
serial1.yieldOnMsrRead = "TRUE"
serial1.fileName = "\\.\pipe\com_1"
serial1.pipe.endPoint = "server"
 

A propriedade “filetype” com o valor “thiprint” na porta serial zero me fez prestar atenção neste novo hardware que fazia parte das configurações da minha máquina virtual e eu nem tinha reparado.

O fato de essa impressora utilizar a porta COM1 da máquina virtual como interface de hardware, fazia com que qualquer nova porta serial utilizasse a COM2, que seria a próxima porta vaga. Agora ficou simples de resolver.

A Solução

Existem duas maneiras de resolver esse pequeno contratempo. A primeira, que minha opinião é mais simples e tosco, é simplesmente remover a impressora antes de adicionar a porta serial que será utilizada como interface de debug. Isso fará com que a primeira porta serial seja realmente utilizada como porta serial. Caso você necessite utilizar a impressora, então adicione e impressora após a adição da porta serial. Isso fará com que a impressora utilize a primeira porta serial disponível, que no caso é a COM2. A janela de configuração de hardware da VMware deve ficar como exibida na figura abaixo.

Reparem que o nome da porta serial não leva mais o número 2 enquanto que na configuração da impressora diz que a porta utilizada é a serial 2.

A segunda solução, como uma alternativa à primeira, é simplesmente configurar o lado TARGET para que ele utilize a porta serial 2. No exemplo da figura abaixo, eu mudo a configuração de um Windows 7 utilizando a ferramenta BcdEdit. Já comentei dessa ferramenta neste post, que fala sobre como fazer debug de Kernel através de portas USB utilizando o Windows Vista.

Depois de tudo resolvido, o depurador agora conecta ao sistema TARGET e agora vem a parte mais fácil; depurar o problema. 😉

Até mais!

Mapeando Arquivos em Memória

26 de June de 2010

Depois de ilustrar algumas das características do Memory Manager sendo um provedor de serviços ao Cache Manager no post anterior, hoje vou demonstrar que meras aplicações User-Mode também podem utilizar tais serviços. Mapeando arquivos em memória a aplicação ganha um intervalo de endereços virtuais que contém o conteúdo do arquivo. O acesso ao conteúdo do arquivo se dá simplesmente desreferenciando um ponteiro, sem a necessidade de chamar as funções ReadFile() ou WriteFile().

Quer uma necessidade para isso? Imagine que sua aplicação precise fazer a busca por uma determinada string em um arquivo, digamos “DriverEntry”. Em um desevolvimento “arroz com feijão”, o handle do arquivo é obtido através da chamada à função CreateFile(), e um buffer recebe o conteúdo parcial do arquivo, vamos supor 200 bytes. Uma simples função de busca da API poderia fazer tal busca no buffer.

Essa solução seria perfeita se não houvesse a possibilidade de a palavra buscada cair nas extremidades do buffer tal como ilustrado abaixo.

Um algoritmo mais espertinho teria que ser utilizado para identificar o prefixo e continuar a busca na próxima leitura do arquivo.

Este é apenas um simples exemplo, mas que ilustra com clareza uma das vantagens de se mapear arquivos. Se houvesse uma simples função que recebesse o path de um arquivo e nos retornasse um ponteiro para o conteúdo dele,  a busca seria bem simples.

Arquivos mapeados em memória também podem facilitar a escrita em seu conteúdo. Escrevendo no ponteiro recebido por tal mapeamento, o Memory Manager vai se encarregar de fazer o I/O necessário para que este novo conteúdo chegue ao disco.

Uma simples função de mapeamento

Aqui vou exemplificar o uso das rotinas que mapeiam um arquivo em memória. Os comentários seguem na explicação.

/****
***     MapFileToMemory
**
**      Rotina que recebe o path de um arquivo que será
**      mapeado em memória para leitura. Um endereço é
**      retornado à rotina chamadora bem como o tamanho
**      do arquivo.
*/
 
DWORD MapFileToMemory(LPCTSTR   tzFileName,
                      LPVOID*   ppMemory,
                      LPDWORD   pdwSize)
{
    HANDLE  hFile = NULL,
            hMapping = NULL;
    DWORD   dwError = ERROR_SUCCESS;
 
    __try
    {
        __try
        {
            //-f--> Zera variáveis de saída.
            *pdwSize = NULL;
            *ppMemory = NULL;
 
            //-f--> Aqui abrimos o arquivo a ser mapeado
            hFile = CreateFile(tzFileName,
                               GENERIC_READ,
                               FILE_SHARE_READ | FILE_SHARE_DELETE,
                               NULL,
                               OPEN_EXISTING,
                               FILE_ATTRIBUTE_NORMAL,
                               NULL);
 
            //-f--> Vericamos se o arquivo foi aberto, senão
            //      o homem do saco vem e nos leva.
            if (hFile == INVALID_HANDLE_VALUE)
                RaiseException(GetLastError(),
                               0,
                               0,
                               NULL);
 
            //-f--> Embora o tamanho do arquivo não seja necessário
            //      nesta função, vamos aproveitar que temos o handle
            //      do arquivo em mãos para obter essa informação para
            //      a rotina chamadora que vai precisar dela.
            *pdwSize = GetFileSize(hFile,
                                   NULL);
 
            //-f--> Aqui criamos um mapeamento do arquivo.
            //      Em kernel seria o equivalente a se criar uma
            //      section do arquivo.
            hMapping = CreateFileMapping(hFile,
                                         NULL,
                                         PAGE_READONLY,
                                         0,
                                         0,
                                         NULL);
 
            //-f--> Prevenindo o homem do saco.
            if (!hMapping)
                RaiseException(GetLastError(),
                               0,
                               0,
                               NULL);
 
            //-f--> Aqui sim o mapeamento é feito e ganhamos o
            //      intervalo de endereços que conterá o conteúdo
            //      do arquivo.
            *ppMemory = MapViewOfFile(hMapping,
                                      FILE_MAP_READ,
                                      0,
                                      0,
                                      0);
 
            //-f--> A mesma treta do saco que já comentei.
            if (!*ppMemory)
                RaiseException(GetLastError(),
                               0,
                               0,
                               NULL);
        }
        __finally
        {
            //-f--> Aqui é onde faremos toda a faxina
            //      fechando os handles que foram abertos.
            if (hFile)
                CloseHandle(hFile);
 
            if (hMapping)
                CloseHandle(hMapping);
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        //-f--> Oops! Alguma coisa não saiu como foi ensaiado.
        //      Encontre um culpado e faça de conta que não é com você.
        dwError = GetExceptionCode();
    }
 
    return dwError;
}
 

Este exemplo é realmente bem simples, mas sinta-se à vontade para adicionar parâmetros que tornem essa função mais flexível e complexa.

“Fernando, mesmo que o arquivo tenha sido mapeado com succeso, você fecha o handles do arquivo e do mapeamento. Isso não deveria liberar as refências que este programa tem com o arquivo?”

Na verdade, depois de criarmos o mapeamento de arquivo utilizando a rotina CreateFileMapping() que recebe o handle do arquivo, uma referência extra já foi feita ao arquivo e assim já poderíamos fechar o handle dele se quisessemos. O mesmo acontece com a chamada da rotina MapViewOfFile(), que recebe o handle do mapeamento, e que por sua vez possui uma referência indireta ao arquivo mapeado. Ou seja, depois de tudo mapeado podemos fechar todos os handles e deixar as referências indiretas tomarem conta disso.

No próximo código fonte veremos um exemplo simples de utilização dessa função.

/****
***     _tmain
**
**      Simples utilização da função de mapeamento de arquivo.
**      Só pra não dizer que não fiz tudim tudim...
*/
 
int _tmain(int argc, _TCHAR* argv[])
{
    PBYTE   pBuffer;
    DWORD   dwError, dwSize, i;
 
    //-f--> Passa o nome do arquivo e obtém o ponteiro
    //      com seu conteúdo mapeado. Simples assim...
    dwError = MapFileToMemory(_T("C:\\Temp\\Test.txt"),
                              (LPVOID*)&pBuffer,
                              &dwSize);
 
    //-f--> Testar erro nunca é demais.
    if (dwError == ERROR_SUCCESS)
    {
        //-f--> Momentos de suspense antes de tocar o endereço.
        printf("Hit any key to access the buffer at 0x%p...\n", pBuffer);
        _getch();
 
        //-f--> Imprime cada caractere armzenado no arquivo.
        //      "Olha mamãe! Sem o ReadFile()!"
        for (i=0; i
            printf("%c", pBuffer[i]);
 
        //-f--> Aqui o mapeamento é desfeito.
        UnmapViewOfFile(pBuffer);
    }
 
    return dwError;
}
 

Ao final deste exemplo podemos observar a chamada à rotina UnmapViewOfFile(), que recebe simplesmente o ponteiro base do mapeamento do arquivo. Com essa chamada, todas as referências internas são desfeitas e o arquivo finalmente é fechado.

Testando o brinquedo

Para que possamos fazer um teste besta, crie um arquivo texto utilizando o Notepad.exe.

Rodando a aplicação de teste temos a saída como ilustrada abaixo.

Agora na câmera lenta do replay

Com o WinDbg podemos observar o exato momento em que a aplicação acessa o intervalo de endereços referente ao conteúdo do arquivo. Para isso vamos colocar um breakpoint na rotina de leitura de arquivo do driver Ntfs.sys, desta forma poderemos ver a requisição do Memóry Manager ser atendida. Para que isso aconteça, o arquivo texto não pode estar no cache do sistema, então se você já rodou a aplicação de teste ao menos uma vez, você deverá reiniciar o sistema.

Caso você ainda não tenha utilizado o WinDbg e não sabe como conectá-lo ao sistema, então leia este post para um quick start. Depois de conectar o WinDbg ao Kernel do sistema, vá até o diretório onde está a aplicação de teste e a execute, mas ainda não pressione qualquer tecla deixando-a parada como mostra a seguir:

Depois disso pressione Ctrl+Break no WinDbg para que você adquira o controle sobre o sistema depurado, que neste momento vai permanecer congelado.

Para colocar o tal breakpoint na rotina de leitura de arquivo do Ntfs.sys, precisaremos saber onde está essa rotina dentro do driver. Podemos obter essa informação utilizando a extenção !drvobj do WinDbg como exibido abaixo.

kd> !drvobj \FileSystem\Ntfs 2
Driver object (843fd650) is for:
 \FileSystem\Ntfs
DriverEntry:   828f5b75    Ntfs!GsDriverEntry
DriverStartIo: 00000000    
DriverUnload:  00000000    
AddDevice:     00000000    
 
Dispatch routines:
[00] IRP_MJ_CREATE                      8289400a    Ntfs!NtfsFsdCreate
[01] IRP_MJ_CREATE_NAMED_PIPE           8165a013    nt!IopInvalidDeviceRequest
[02] IRP_MJ_CLOSE                       82896fcf    Ntfs!NtfsFsdClose
[03] IRP_MJ_READ                        82818514    Ntfs!NtfsFsdRead
[04] IRP_MJ_WRITE                       82815638    Ntfs!NtfsFsdWrite
[05] IRP_MJ_QUERY_INFORMATION           82895a88    Ntfs!NtfsFsdDispatchWait
[06] IRP_MJ_SET_INFORMATION             8281e950    Ntfs!NtfsFsdSetInformation
[07] IRP_MJ_QUERY_EA                    82895a88    Ntfs!NtfsFsdDispatchWait
[08] IRP_MJ_SET_EA                      82895a88    Ntfs!NtfsFsdDispatchWait
[09] IRP_MJ_FLUSH_BUFFERS               82884349    Ntfs!NtfsFsdFlushBuffers
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION    828b5fc6    Ntfs!NtfsFsdDispatch
[0b] IRP_MJ_SET_VOLUME_INFORMATION      828b5fc6    Ntfs!NtfsFsdDispatch
[0c] IRP_MJ_DIRECTORY_CONTROL           828b5d41    Ntfs!NtfsFsdDirectoryControl
[0d] IRP_MJ_FILE_SYSTEM_CONTROL         8289970e    Ntfs!NtfsFsdFileSystemControl
[0e] IRP_MJ_DEVICE_CONTROL              82879466    Ntfs!NtfsFsdDeviceControl
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL     8165a013    nt!IopInvalidDeviceRequest
[10] IRP_MJ_SHUTDOWN                    8282b36b    Ntfs!NtfsFsdShutdown
[11] IRP_MJ_LOCK_CONTROL                82823b7a    Ntfs!NtfsFsdLockControl
[12] IRP_MJ_CLEANUP                     828a1d42    Ntfs!NtfsFsdCleanup
[13] IRP_MJ_CREATE_MAILSLOT             8165a013    nt!IopInvalidDeviceRequest
[14] IRP_MJ_QUERY_SECURITY              828b5fc6    Ntfs!NtfsFsdDispatch
[15] IRP_MJ_SET_SECURITY                828b5fc6    Ntfs!NtfsFsdDispatch
[16] IRP_MJ_POWER                       8165a013    nt!IopInvalidDeviceRequest
[17] IRP_MJ_SYSTEM_CONTROL              8165a013    nt!IopInvalidDeviceRequest
[18] IRP_MJ_DEVICE_CHANGE               8165a013    nt!IopInvalidDeviceRequest
[19] IRP_MJ_QUERY_QUOTA                 82895a88    Ntfs!NtfsFsdDispatchWait
[1a] IRP_MJ_SET_QUOTA                   82895a88    Ntfs!NtfsFsdDispatchWait
[1b] IRP_MJ_PNP                         8286137b    Ntfs!NtfsFsdPnp
 
Fast I/O routines:
FastIoCheckIfPossible                   8288187b    Ntfs!NtfsFastIoCheckIfPossible
FastIoRead                              82880c38    Ntfs!NtfsCopyReadA
FastIoWrite                             82881f53    Ntfs!NtfsCopyWriteA
FastIoQueryBasicInfo                    82888c3a    Ntfs!NtfsFastQueryBasicInfo
FastIoQueryStandardInfo                 82888aa6    Ntfs!NtfsFastQueryStdInfo
FastIoLock                              8287bf41    Ntfs!NtfsFastLock
FastIoUnlockSingle                      8287bd75    Ntfs!NtfsFastUnlockSingle
FastIoUnlockAll                         828cd7b3    Ntfs!NtfsFastUnlockAll
FastIoUnlockAllByKey                    828cd958    Ntfs!NtfsFastUnlockAllByKey
ReleaseFileForNtCreateSection           8281e904    Ntfs!NtfsReleaseForCreateSection
FastIoQueryNetworkOpenInfo              8287ad84    Ntfs!NtfsFastQueryNetworkOpenInfo
AcquireForModWrite                      8280c892    Ntfs!NtfsAcquireFileForModWrite
MdlRead                                 828cd0d8    Ntfs!NtfsMdlReadA
MdlReadComplete                         81650af6    nt!FsRtlMdlReadCompleteDev
PrepareMdlWrite                         828cd31f    Ntfs!NtfsPrepareMdlWriteA
MdlWriteComplete                        817f5a9a    nt!FsRtlMdlWriteCompleteDev
FastIoQueryOpen                         82874d03    Ntfs!NtfsNetworkOpenCreate
AcquireForCcFlush                       8281ab35    Ntfs!NtfsAcquireFileForCcFlush
ReleaseForCcFlush                       8281aa9c    Ntfs!NtfsReleaseFileForCcFlush
 

Como você deve estar imaginando, a rotina de leitura do Ntfs é utilizada com muita frequência, o que faria este breakpoint parar muitas vezes sem ter a menor relação com o nosso teste. Para limitar o escopo do breakpoint, vamos fazer com que ele se aplique somente à thread que fará a solicicação que estamos esperando.

Como já expliquei no post anterior, quando o endereço de memória é obtido, o arquivo ainda não foi lido. Quando a aplicação desreferenciar este ponteiro buscando os dados, um page fault será gerado e o Memory Manager vai tomar o controle sobre a thread por meio de um trap de sistema. Essa é a thread que será utilizada para realizar a leitura do arquivo que vai abastecer a página de memória à pedido do Memory Manager. Esta é a razão pela qual nosso programa de teste espera uma tecla ser pressionada antes de acessar o buffer. Isso nos dá a oportunidade de obter a identificação da thread que está aguardando esse evento.

Utilizamos a extenção !process para localizar o nosso programa de teste e também listará suas threads, que em nosso caso é uma única.

kd> !process 0 2 MapFile.exe
PROCESS 84bf6d90  SessionId: 1  Cid: 0b60    Peb: 7ffdf000  ParentCid: 0b40
    DirBase: 1f09b4c0  ObjectTable: 8e77ccd0  HandleCount:   5.
    Image: MapFile.exe
 
        THREAD 84f6cb50  Cid 0b60.0b64  Teb: 7ffde000 Win32Thread: 00000000 WAIT: (WrLpcReply) ...
            84f6cd64  Semaphore Limit 0x1
 
kd> bp /1 /t 84f6cb50 Ntfs!NtfsFsdRead
kd> g
 

Depois de colocado o breakpoint, podemos liberar a execução do sistema e teclar algo na aplicação de teste. Isso fará com que nosso breakpoint interrompa o sistema bem como pretendíamos. Olhando para a pilha de chamadas que temos no momento, podemos evidenciar a execução do trap que foi gerado pela aplicação de teste. Este trap está sendo atendido pelo Memory Manager.

Breakpoint 0 hit
Ntfs!NtfsFsdRead:
82818514 6a40
 
kd> kb
ChildEBP RetAddr  Args to Child
8f395b6c 816f00c3 84438020 84f40290 84f40290 Ntfs!NtfsFsdRead
8f395b84 821a3ba7 84437730 84f40290 00000000 nt!IofCallDriver+0x63
8f395ba8 821a3d64 8f395bc8 84437730 00000000 fltmgr!FltpLegacyProcessingAfterPreCallbacksCompleted+0x251
8f395be0 816f00c3 84437730 84f40290 00000000 fltmgr!FltpDispatch+0xc2
8f395bf8 8167bf2e 84f6cb50 8443a18c 8443a158 nt!IofCallDriver+0x63
8f395c14 816b8d51 00000043 84f6cb50 8443a198 nt!IoPageRead+0x172
8f395cd0 816db03f 00020000 90825810 00000000 nt!MiDispatchFault+0xd18
8f395d4c 8168ebf4 00000000 00020000 00000001 nt!MmAccessFault+0x1fb7
8f395d4c 0018d972 00000000 00020000 00000001 nt!KiTrap0E+0xdc
0015fbc0 0018ec36 00000001 00281a28 00281a78 MapFile!wmain+0x72
0015fc0c 0018eb0f 0015fc20 77554911 7ffdf000 MapFile!__tmainCRTStartup+0x116
0015fc14 77554911 7ffdf000 0015fc60 77ace4b6 MapFile!wmainCRTStartup+0xf
0015fc20 77ace4b6 7ffdf000 77a26775 00000000 kernel32!BaseThreadInitThunk+0xe
0015fc60 77ace489 0018b532 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x23
0015fc78 00000000 0018b532 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b
 

Como conhecemos o protótipo que uma rotina de dispatch precisa ter, sabemos que o segundo parâmetro da rotina NtfsFsdRead() é o endereço da IRP que o driver recebeu. Utilizando a extensão !irp podemos obter detalhes sobre a IRP. Isso nos permite conhecer o FileObject ao qual está destinado essa solicitação.

kd> !irp 84f40290 
Irp is active with 8 stacks 8 is current (= 0x84f403fc)
 Mdl=8443a1d8: No System Buffer: Thread 84f6cb50:  Irp stack trace.  
     cmd  flg cl Device   File     Completion-Context
 [  0, 0]   0  0 00000000 00000000 00000000-00000000    
 
            Args: 00000000 00000000 00000000 00000000
 [  0, 0]   0  0 00000000 00000000 00000000-00000000    
 
            Args: 00000000 00000000 00000000 00000000
 [  0, 0]   0  0 00000000 00000000 00000000-00000000    
 
            Args: 00000000 00000000 00000000 00000000
 [  0, 0]   0  0 00000000 00000000 00000000-00000000    
 
            Args: 00000000 00000000 00000000 00000000
 [  0, 0]   0  0 00000000 00000000 00000000-00000000    
 
            Args: 00000000 00000000 00000000 00000000
 [  0, 0]   0  0 00000000 00000000 00000000-00000000    
 
            Args: 00000000 00000000 00000000 00000000
 [  0, 0]   0  0 00000000 00000000 00000000-00000000    
 
            Args: 00000000 00000000 00000000 00000000
>[  3, 0]   0  0 84438020 84bd0028 00000000-00000000    
           \FileSystem\Ntfs
            Args: 00001000 00000000 00000000 00000000
 

Com o endereço do FileObject em mãos, a extensão !fileobj nos mostrará mais detalhes sobre esse objeto. Assim podemos verificar que de fato o arquivo a ser lido é o nosso arquivo texto que foi mapeado.

kd> !fileobj 84bd0028 
 
\Temp\Test.txt
 
Device Object: 0x84439030   \Driver\volmgr
Vpb: 0x84436e28
Access: Read SharedRead SharedDelete 
 
Flags:  0x44042
    Synchronous IO
    Cache Supported
    Cleanup Complete
    Handle Created
 
FsContext: 0x92a4cd80    FsContext2: 0x92a4ced8
CurrentByteOffset: 0
Cache Data:
  Section Object Pointers: 84d24e74
  Shared Cache Map: 00000000
 

Sabendo que estamos no contexto da thread que fez o acesso, podemos dar uma espiadinha no endereço acessado antes do page fault ser atendido. Esse endereço foi exibido na saída da aplicação de teste antes o breakpoint interromper a execução do sistema.

kd> db 0x00020000
00020000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00020010  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00020020  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00020030  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00020040  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00020050  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00020060  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00020070  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
 

“Fernando, por que aparecem sinais de interrogação? Tudo bem que o conteúdo do arquivo ainda não foi copiado para o buffer da aplicação, mas não deveríamos ver lixo ou mesmo zeros?”

Olha, essa sua pergunta foi realmente muito boa, acho que eu mesmo não poderia ter pensado em uma pergunta melhor. Na verdade a resposta para essa pergunta está vinculada àquela reposta tosca do post anterior. O que acontece é que um intervalo de endereços foi reservado para conter as páginas de memória com o conteúdo do arquivo. Como nenhum acesso ainda foi feito, o esse endereço virtual ainda não aponta para nenhuma página física de memória. Sem essa página física não se pode determinar seus dados. Podemos verificar isso utilizando a extenção !vtop do WinDbg, que faz a tradução de endereços virtuais para endereços físicos.

kd> !vtop 0 0x00020000
X86VtoP: Virt 00020000, pagedir 1f09b4c0
X86VtoP: PAE PDPE 1f09b4c0 - 000000001876d801
X86VtoP: PAE PDE 1876d000 - 0000000018908867
X86VtoP: PAE PTE 18908100 - ffffffff00000420
X86VtoP: Virt ffffffff, pagedir 1f09b4c0
X86VtoP: PAE PDPE 1f09b4d8 - 00000000187b0801
X86VtoP: PAE PDE 187b0ff8 - 0000000000128063
X86VtoP: PAE PTE 128ff8 - 0000000000000000
X86VtoP: PAE zero PTE
Virtual address 20000 translation fails, error 0x8007001E.
 
kd> !error 0x8007001E
Error code: (HRESULT) 0x8007001e (2147942430) - The system cannot read from the specified device.
 

A tentativa de tradução desse endereço virtual resulta em um erro. Vamos tentar fazer essa tradução novamente depois que o page fault for atendito. Vamos liberar a execução do sistema até o endereço de retorno para a rotina MmAccessFault(). Este endereço foi obtido na pilha de chamadas da thread e foi destacado nos resultados do comando kd já ilustrado acima.

kd> ga 8168ebf4 
 
nt!KiTrap0E+0xdc:
8168ebf4 85c0            test    eax,eax
 
kd> !vtop 0 0x00020000
X86VtoP: Virt 00020000, pagedir 1f09b4c0
X86VtoP: PAE PDPE 1f09b4c0 - 000000001876d801
X86VtoP: PAE PDE 1876d000 - 0000000018908867
X86VtoP: PAE PTE 18908100 - 8000000018844025
X86VtoP: PAE Mapped phys 18844000
Virtual address 20000 translates to physical address 18844000.
 

Aqui o page fault já foi antendido e o controle será devolvido à aplicação. Neste ponto o Memory Manager realizou as tarefas necessárias para que esse endereço virtual agora pudesse ser traduzido para uma página física. Repetindo a mesma tentativa de tradução que falhou anteriormente, teremos a seguinte saída.

kd> db 0x00020000
00020000  54 65 73 74 65 20 64 65-20 6d 61 70 65 61 6d 65  Teste de mapeame
00020010  6e 74 6f 20 64 65 20 61-72 71 75 69 76 6f 2e 2e  nto de arquivo..
00020020  2e 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00020030  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00020040  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00020050  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00020060  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00020070  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

Liberando a execução do sistema, a aplicação fará o acesso ao buffer e teremos a mesma saída exibida anteriormente. Vale lembrar que para repetir essa experiência, é necessário reiniciar o sistema, pois o conteúdo do arquivo texto agora está no cache do sistema. Isso significa que o page fault não ocorrerá novamente até que esta página seja descartada pelo Cache Manager. Tal evento depende de muitos fatores e pode não acontecer até que a máquina desligue.

Enfim, este post além de trazer uma simples função de mapeamento de arquivo, também traz o mesmo blá-blá-blá técnico de sempre. Espero que tenham gostado.
Até mais! 😉

MapFile.zip

Levando a tela azul pra casa

18 de June de 2009

Nada melhor que uma bela choraderia para começar este post. Meu rítmo está baixo por conta da universidade estar sugando todas as minhas energias vitais. Se você tem acompanhado meu blog nos últimos posts, já sabe do que estou falando. No meu tempo livre estive correndo com meu projeto, meu estágio e meu emprego. Meu blog também participa dessa lista de tarefas, mas o coitadinho tem menos prioridade aqui. Alguns de vocês devem saber que sou helimodelista por hobby, mas como eu disse ao meu amigo Heldai outro dia: “Hobby é o nome que se dá àquilo que fazemos para ocupar o tempo que temos livre, mas ainda estou para descobrir o nome que daríamos àquilo que gostaríamos de fazer se tivéssemos tempo livre…”. Enfim, como isso não tem nada a ver com o post de hoje, vamos mudar de assunto.

Entre uma coisa e outra, estive tentando pensar em algo simples para um post pequeno. Foi então que a dúvida do leitor Ismael Rocha (Brasília – DF) gerou este post.

“Existe uma maneira de salvar as BSOD’s para posteriormente verificar eventuais problemas?”

Salvar uma tela azul? Salvar o quê? A máquina já morreu meu amigo! Já era! Acabou! O que você ainda pode tentar salvar é seu emprego.

Brincadeiras à parte, existe sim.

O sistema operacional está pré-configurado para reiniciar automagicamente quando uma falha crítica acontece. Falha crítica é a maneira polida de se dizer que a casa caiu, a vaca foi pro brejo, o jacaré te abraçou, o tambor girou, o ferro berrou, o tempo fechou, ficou pequeno pra você… enfim, uma tela azul aconteceu. Não que eu não goste de telas azuis, mas do efeito colateral que ela nos traz. Pela norma mundial dos consumidores de drivers de terceiros, se você é o autor de um driver que estiver instalado em uma máquina no momento da falha, esteja ele rodando ou não, então a culpa da falha é sua até que se prove o contrário. É triste, mas é a realidade. A partir do momento que uma tela azul acontece, você é o culpado padrão e terá que ficar aguentando piadinhas pelo resto da eternidade. Gostaria de aproveitar o contexto para mandar um abraço pro meu amigo Heldai.

Exibindo a tela azul

Na tentativa de salvar sua dignidade, você tenta provar que a culpa não é sua. Dizer que o reset da máquina é uma feature do seu driver e que felizmente funcionou muito bem não vai colar, não na segunda vez. Mas o que você pode fazer se a tela azul é apenas um flash de informações enquanto a máquina não reinicia? Felizmente você pode mudar isso. Clicando com o botão direito do mouse sobre o “Meu computador”, selecionando “Propriedades”. Daí em diante é só dar uma olhada na figura abaixo para descobrir que você pode evitar que a máquina reinicie automaginamente.


Você terá de desmarcar a opção “Reiniciar automaticamente”, e assim ter todo o tempo que for necessário para mostrar a todos que o problema não é seu. Na maioria das vezes o sistema consegue detectar o driver que provavelmente é o causador de toda essa dor de cabeça e exibir o nome do arquivo na tela azul como podemos ver na figura abaixo.

Desmontando uma tela azul


“Nossa! Então o Windows tem um algorítmo de inteligência artificial, que provalvelmente usa nanotecnologia de alguma forma para descobrir o driver culpado?”

Na verdade é um pouco mais simples que isso, o Windows simplesmemte pega a imagem do driver que lançou uma exceção que não foi manipulada ou que voluntariamente derrubou o sistema por detectar alguma incoerência. Por isso, nem sempre o nome do driver exibido é de fato o nome do driver culpado. Se pensarmos no simples exemplo onde o driver MetralhadoraGiratoria.sys escreve onde não deveria corrompendo algum Pool de alocações, esse erro mais tarde pode ser detectado pelo driver Laranja.sys que, na hora de fazer uma alocação de memória, chama uma rotina de sistema que por sua vez chama a rotina KeBugCheckEx() ao detectar tal incoerência. Consegue adivinhar o nome do driver que aparecerá no BO?

Outras informações ainda podem ser obtidas da tela azul. Se é o nome do seu driver que aparece na tela, então você ainda pode obter o endereço da instrução onde a desgraça ocorreu. Em nosso exemplo o endereço é o 0xF8DD8A415 partir daí podemos chegar na função que estava sendo executada no momento da falha se tivermos o arquivo de mapa gerado pelo linker. Também é possível obter a data da imagem do arquivo e tirar aquela dúvida de que realmente era a versão certa que estava sendo executada. A data do arquivo é obtida no campo DateStamp e é expressa em um valor hexadecimal de 32 bits representando a quantidade de segundos deste meia noite de primeiro de Janeiro de 1970. Difícil mesmo é achar alguém com paciência suficiente para calcular isso diante de uma tela azul. Existem meios bem menos trabalhosos de descobrir que a culpa foi sua mesmo.

Na minha opinião, a informação mais relevante que a tela azul oferece é o Stop Code. Como o nome já sugere, Stop Code é um código que vai indicar o motivo da falha do sistema. você pode consultar a lista de Stop Codes neste link ou ainda dar uma olhada no arquivo C:\WinDDK\6001.18002\inc\api\BugCodes.h que vem no WDK.


Stop Codes vêm com até quatro parâmetros que trazem informações adicionais ao código de parada. A interpretação destes valores dependerá do código de falha, que em nosso exemplo é 0x7E. Consultando no link que informei a pouco, teremos a seguinte interpretação para os valores que nos foi apresentado.


Mas existe um jeito de salvar a BSOD ou não?

Tá tá tá… É que começo a escrever e acabo me empolgando. Mas enfim, quando uma falha crítica ocorre, o sistema cria um arquivo conhecido como Crash Dump. Existem três opções de crash dumps que podem ser geradas.

  • Dump Completo: Nesta opção, todo o conteúdo da memória física no momento da falha será copiado em um arquivo. Obviamente o tamanho deste arquivo será a quantidade de memória presente na máquina com um acréssimo de 1MB de header. Essa opção não aparece nas máquinas que possuam mais de 2GB de memória física, mas ainda é possivel configurar o dump completo sem utilizar essa interface gráfica escrevendo diretamente no registro. Esse método é também conhecido como “configurar na unha”. O dump completo é muito útil quando a informação presente em páginas de memória em User Space for relevante para o problema, tal como situações de Dead Locks. Se você não sabe o que significa User Space, este post pode ajudar.

  • Dump de Kernel: Aqui somente as páginas em System Space serão copiadas para disco. O tamanho deste arquivo vai variar dependendo de quantidade de memória física a máquina tem instalada, mas não existe uma proporção exata. Muito do balanceamento de páginas utilizado pelo gerenciador de memória virtual vai determinar o tamanho deste arquivo, mas ele fica pela ordem de 200MB num sistema com 4GB de memória total (já dá pra levar no pen drive). Essa opção é normalmente a mais viável, já que só carrega a informação mais relevante para um crash de sistema.

  • Dump Mínimo: Aqui um arquivo de 64KB será gerado para sistemas 32 bits ( 128KB para sistemas 64 bits). Neste arquivo temos apenas o Stop Code e seus parâmetros, a lista de drivers carregados no momento da falha, informações sobre o processo e thread corrente e o Call Stack da thread que causou a falha.

Na mesma janela onde você configura o reinicio automático do sistema, existem dois outros campos que vão configurar o tipo de dump desejado e o caminho onde este será gerado. Agora você já pode levar sua tela azul no coração e depurar onde você quiser. Em casa, no trabalho, no trêm, no metrô… Você pode ainda pedir que clientes enviem seus crash dumps para que você possa diagnosticar o problema ser ter que se deslocar através de rios e montanhas sob o frio e a chuva.

Tenho o Crash Dump, e agora?

Agora que você é um feliz proprietário de um maravilhoso arquivo de Crash Dump, o que mais você poderia querer da vida? Talvez ser capaz descobrir a causa do problema já seria um bom começo. Para isso vamos utilizar o depurador nativo do sistema operacional. Se você ainda não conhece o WinDbg, então dê uma olhada neste post para que você sabia do que estamos falando aqui.

Admitindo que você tenha Windbg instalado em sua máquina de desenvolvimento, e que este esteja com o servidor de símbolos configurado, tudo que temos a fazer agora é abrir o WinDbg, selecionar o ítem “Open Crash Dump…” no menu “File” e apontar o caminho do arquivo de dump que você copiou da pobre máquina que ousou rodar seu driver. O texto abaixo é o resultado exibido na janela de comandos quando o Crash Dump é aberto.

Microsoft (R) Windows Debugger Version 6.11.0001.404 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.
 
 
Loading Dump File [Z:\Sources\MEMORY.DMP]
Kernel Summary Dump File: Only kernel address space is available
 
Symbol search path is: srv*
Executable search path is: 
Windows XP Kernel Version 2600 (Service Pack 3) UP Free x86 compatible
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 2600.xpsp.080413-2111
Machine Name:
Kernel base = 0x804d7000 PsLoadedModuleList = 0x80553fc0
Debug session time: Thu Jun 18 14:46:24.969 2009 (GMT-3)
System Uptime: 0 days 0:03:20.375
Loading Kernel Symbols
...............................................................
.........................................................
Loading User Symbols
 
Loading unloaded module list
...........
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************
 
Use !analyze -v to get detailed debugging information.
 
BugCheck 7E, {c0000005, f8d9f415, f8af1bb4, f8af18b0}
 
Probably caused by : Useless.sys ( Useless!DriverEntry+5 )
 
Followup: MachineOwner
---------

Agora se simplesmente executarmos o comando sugerido, já teremos uma boa descrição do que aconteceu com máquina que sofreu a falha crítica.

kd> !analyze -v
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************
 
SYSTEM_THREAD_EXCEPTION_NOT_HANDLED (7e)
This is a very common bugcheck.  Usually the exception address pinpoints
the driver/function that caused the problem.  Always note this address
as well as the link date of the driver/image that contains this address.
Arguments:
Arg1: c0000005, The exception code that was not handled
Arg2: f8d9f415, The address that the exception occurred at
Arg3: f8af1bb4, Exception Record Address
Arg4: f8af18b0, Context Record Address
 
Debugging Details:
------------------
 
 
EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%08lx referenced
memory at 0x%08lx. The memory could not be %s.
 
FAULTING_IP: 
Useless!DriverEntry+5 [z:\sources\driverentry\useless\useless.c @ 7]
f8d9f415 c7050000000000000000 mov dword ptr ds:[0],0
 
EXCEPTION_RECORD:  f8af1bb4 -- (.exr 0xfffffffff8af1bb4)
ExceptionAddress: f8d9f415 (Useless!DriverEntry+0x00000005)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000001
   Parameter[1]: 00000000
Attempt to write to address 00000000
 
CONTEXT:  f8af18b0 -- (.cxr 0xfffffffff8af18b0)
eax=07263867 ebx=00000000 ecx=bb40e64e edx=1be10003 esi=e19feea8 edi=81eb41d0
eip=f8d9f415 esp=f8af1c7c ebp=f8af1c7c iopl=0         nv up ei ng nz na po nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010282
Useless!DriverEntry+0x5:
f8d9f415 c7050000000000000000 mov dword ptr ds:[0],0  ds:0023:00000000=????????
Resetting default scope
 
PROCESS_NAME:  System
 
ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%08lx referenced memory
at 0x%08lx. The memory could not be %s.
 
EXCEPTION_PARAMETER1:  00000001
 
EXCEPTION_PARAMETER2:  00000000
 
WRITE_ADDRESS:  00000000 
 
FOLLOWUP_IP: 
Useless!DriverEntry+5 [z:\sources\driverentry\useless\useless.c @ 7]
f8d9f415 c7050000000000000000 mov dword ptr ds:[0],0
 
BUGCHECK_STR:  0x7E
 
DEFAULT_BUCKET_ID:  NULL_DEREFERENCE
 
LAST_CONTROL_TRANSFER:  from 8057677f to f8d9f415
 
STACK_TEXT:  
f8af1c7c 8057677f 81eb41d0 81d46000 00000000 Useless!DriverEntry+0x5
 [z:\sources\driverentry\useless\useless.c @ 7]
f8af1d4c 8057688f 80000360 00000001 00000000 nt!IopLoadDriver+0x66d
f8af1d74 80534c02 80000360 00000000 823c68b8 nt!IopLoadUnloadDriver+0x45
f8af1dac 805c6160 b29accf4 00000000 00000000 nt!ExpWorkerThread+0x100
f8af1ddc 80541dd2 80534b02 00000001 00000000 nt!PspSystemThreadStartup+0x34
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16
 
 
FAULTING_SOURCE_CODE:  
     3: NTSTATUS DriverEntry(IN PDRIVER_OBJECT  pDriverObject,
     4:                      IN PUNICODE_STRING pusRegistryPath)
     5: {
     6:     //-f--> Diga olá à BSOD e vá se acostumando com ela...
>    7:     *(PVOID*)0x00000000 = 0;
     8:
     9:     //-f--> Não vamos viver para ver isso.
    10:     return STATUS_SUCCESS;
    11: }
 
 
SYMBOL_STACK_INDEX:  0
 
SYMBOL_NAME:  Useless!DriverEntry+5
 
FOLLOWUP_NAME:  MachineOwner
 
MODULE_NAME: Useless
 
IMAGE_NAME:  Useless.sys
 
DEBUG_FLR_IMAGE_TIMESTAMP:  4a3844ef
 
STACK_COMMAND:  .cxr 0xfffffffff8af18b0 ; kb
 
FAILURE_BUCKET_ID:  0x7E_Useless!DriverEntry+5
 
BUCKET_ID:  0x7E_Useless!DriverEntry+5
 
Followup: MachineOwner
---------

Se a máquina que está abrindo o arquivo de dump for a máquina de desenvolvimento do seu driver, o Windbg será capaz de automagicamente achar os fontes do seu driver e apontar a causa da falha com grandes detalhes. Então certifique-se que seu gerente não esteja por perto nesse momento. Isso já é muito mais informação do que você poderia obter simplesmente olhando para a tela azul do computador. Neste exemplo utilizei o driver de exemplo do post Getting Started para reproduzir a tela azul. Mas não se preocupe com isso, mesmo sendo um programador novato em drivers, uma das primeiras coisas que você aprenderá é como gerar telas azuis.

Mais uma vez espero ter ajudado.
Have fun!

Step into Kernel (Firewire)

2 de July de 2008

Já sei! Seu computador de teste não tem porta serial e você precisa fazer debug de Kernel nele. Creio que depois da porta serial, a maneira mais utilizada para depurar o Kernel do Windows seja utilizando uma interface firewire. Ainda existe a opção de se fazer o debug de Kernel utilizando USB 2.0, mas isso ainda é para poucos, já que além de apenas ser suportado pelo Windows Vista, ainda é necessário ter um cabo especial. Os detalhes sobre debug de Kernel pela porta USB podem ser encontrados neste post. Hoje a história é outra.

Mas eu não tenho porta firewire

Larga a mão de ser chorão. O que importa aqui não é o fato de você ter ou não uma porta firewire em seu micro de desenvolvimento, mas sim o fato da máquina do cliente ter uma porta firewire. Você sabe muito bem que pela Lei de Murphy, aquele problema que você nem sabia que existia só acontece naquela máquina que não tem portas seriais. Então melhor você estar preparado para encontrar esse tipo de coisa. Elas realmente acontecem. Sua reclamação poderia ainda ser diferente: “Mas eu não tenho porta serial”. Alguns notebooks que não possuem portas seriais oferecem portas firewire, mas idependente disso, existem placas tanto PCI quanto PCMCIA capazes que disponibilizar a interface IEEE 1394. Desta forma, seja sua máquina um desktop ou um notebook, existem meios delas ganharem portas firewire.

Configurando o lado TARGET

Se você ainda não sabe o que significa lado HOST/TARGET e está completamente perdido sobre o assunto, leia este post introdutório antes de continuar. Configurar o lado TARGET não é muito diferente do que já vimos em outros posts desta série. Podemos editar o aquivo boot.ini, como já vimos neste post para adicionar as seguintes configurações de debug.

[boot loader]
timeout=10
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)<<...>>/fastdetect /debugport=1394 /channel=44
multi(0)disk(0)<<...>>/fastdetect

O número do canal a ser utilizado pode ser qualquer um, mas o valor deve ser o mesmo em ambos os lados TARGET e HOST. Caso você esteja querendo depurar um Windows Vista, o método de configurar as mesmas coisas mudaram um pouco como vimos neste outro post. A figura a seguir mostra os passos para setar o modo de configuração do sistema para interface IEEE 1394.

Lembre-se que aqui estamos apenas configurando a maneira com a qual o sistema seria depurado caso exista uma entrada de debug na lista de boot da máquina. Este post mostra os detalhes de como criar uma entrada adicional nesta lista e configurá-la para debug.


Só isso? Nem doeu!

Para se fazer Kernel Debug utilizando um cabo firewire, ambos os micros devem estar rodando Windows XP ou superior, não necessariamente a mesma versão em ambos os lados. Existe uma particularidade quanto utilizar sistemas anteriores ao Windows XP SP2 ou Windows 2003 Server sem service pack no lado TARGET da história, conforme informa esta página. Para estes sistemas, deve-se desabilitar a controladora do barramento 1394. Isso é necessário porque o Windows, que está sendo depurado inconscientemente, pode querer tentar conversar com a interface Firewire durante o debug, e isso pode fazer com que a conexão com o depurador caia. Para desabilitar essa interface nos sistemas acima citados, você deve simplesmente selecionar o ítem “Disable” no menu de contexto que aparece quando você clica com o botão direito do mouse sobre a controladora firewire.

Posso desabilitar a controladora Firewire independente da versão do Windows? Não, se você desabilitar a controladora em sistemas posteriores aos acima citados, você poderá não conseguir depurar o sistema quando ele mudar entre os estados de energia do sistema. Estados de energia? Você está falando da aura do computador? Supondo que você está querendo depurar seu driver durante as transições energia do sistema que são gerenciadas pelo Power Manager. O Power Manager determina qual barramento pode ser desligado para economizar energia. Assim, o sistema pode decidir desligar a interface firewire durante seu debug de um driver qualquer, e dessa forma, você não vai conseguir acompanhar as IRPs de gerenciamento de energia chegando ao seu driver.

Configurando o lado HOST

Normalmente, para fazer iniciar uma sessão de debug do lado HOST, basta abrir o WinDbg, selecionar o ítem “Kernel Debug…” do menu File, clicar na aba 1394, preencher o número do canal que se deseja utilizar, clicar OK como mostra a figura abaixo e correr para o abraço.


Tudo isso que acabei de descrever continua valendo, mas para que seja possível utilizar firewire do lado HOST, o WinDbg precisa instalar os drivers virtuais de acesso ao barramento IEEE 1394, como mostra nesta página. O WinDbg faz isso automagicamente quando você seleciona as opções acima, mas os drivers só poderão ser instalados se você estiver logado como administrador do sistema. Caso contrário você receberá a seguinte mensagem.


Pô Fernando, sou desenvolvedor de driver! Você acha mesmo que não sou administrador da minha máquina? Tudo bem, você pode até ser, mas mesmo sendo um administrador no Windows Vista você precisará executar o WinDbg clicando com o botão direito do mouse sobre o ícone do WinDbg e selecionar o ítem “Run as Administrator”. Aí sim, você repete o procedimento descrito acima para que os drivers virtuais sejam instalados. Esse procedimento é necessário somente na primeira vêz que você utiliza a porta firewire para debug, nas próximas vezes, os drivers já estarão instalados. Para quem estiver utilizando um sistema anterior ao Vista e já estiver utilizando uma conta admininstrativa, é como meu amigo Thiago diz: “Sai na urina”. Ou seja, o driver virtual será instalado e você terá a saída como mostra a figura abaixo, que demonstra as mesmas operações realizadas no Windows Vista rodando o WinDbg como Administrador.


Daí em diante é só debug mesmo.

Dump Racing

Aproveitando que estamos todos aqui reunidos, vamos fazer um teste e verificar se a velocidade do firewire ajuda mesmo com relacão à fazer Kernel Debug. Vamos imaginar a situação onde você esteja em visita a um cliente onde obviamente seu driver não está funcionando adequadamente. Lembra daquele bug que você nem sabia que existia? Pois bem, se trata de um deadlock. Deadlock são especialmente queridos na hora de fazer debug, porque você está lá quando o problema acontece, mas tela azul que é bom nada. Nessa situação, você pode gerar um arquivo de dump da máquina e deixar para analizar o problema em casa, afinal de contas, roupa suja se lava em casa, e assim poder liberar a máquina do cliente para uso, já que normalmente nessas situações, ficam umas três pessoas em cima de você perguntando “E aí? Descobriu o problema?” a cada 3 minutos. Configurei meu desktop aqui de casa para fazer debug do Windows Vista por uma porta serial. Esta máquina tem 2GB de memória RAM. Um arquivo de dump full é uma cópia de tudo que esta na memória do computador naquele instante, por isso, nada mais justo que este arquivo tenha aproximadamente 2GB de tamanho. Este arquivo pode ser gerado na máquina HOST durante uma sessão de debug utilizando o comando .dump. Segue abaixo a saída deste comando quando utilizado sobre uma conexão serial.

0: kd> .dump /f c:\Temp\SERIAL.DMP
Creating a full kernel dump over the COM port is a VERY VERY slow operation.
This command may take many HOURS to complete.  Ctrl-C if you want to terminate the command.
Creating c:\Temp\SERIAL.DMP - Full kernel dump
Percent written 0
Percent written 1
Percent written 2
        :
        :

Vocês repararam na mensagem ameaçadora que nos foi exibida? Particularmente penso que estes engenheiros de software são todos uns desesperados, provavelmente por causa quantidade de café que eles consomem por dia. Já posso até imaginar quantos nem esperam o dump começar para já pressionar CTRL+C e interromper o processo. São uns covardes mesmo. Bom, já que isso vai me custar algum tempo, vou aproveitar para dar uma mijada.

Muito, mas muito tempo mesmo depois…

        :
        :
Percent written 97
Percent written 98
Percent written 99
Dump successfully written
0: kd>

OK, tudo bem até aqui. Agora vamos repetir o processo em uma sessão de debug utilizando o cabo firewire. A mesma máquina com a mesma quantidade de memória e até o mesmo comando.

1: kd> .dump /f c:\Temp\FIREWIRE.DMP
Creating c:\Temp\FIREWIRE.DMP - Full kernel dump
Percent written 0
Percent written 5
Percent written 10
        :
        :
Percent written 90
Percent written 95
Dump successfully written
1: kd>

Tá, tudo bem, a contagem vai de cinco em cinco ao invés de um em um como foi com o cabo serial, grande coisa. Não é a toa que demore mais pela porta serial, eles gastam processamento com tudo. Agora podemos comparar as datas de criação e modificação nos atributos de cada arquivo para poder determinar quanto tempo levou para poder gerá-los.


Lembrando que o mês de junho termina no dia 30, podemos concluir que o dump pela porta serial levou aproximadamente 2 dias, 5 horas e 12 minutos. É uma pena essa janela não mostrar os segundos para termos mais precisão aqui. De qualquer forma, lembrando também que cada minuto tem 60 segundos, o mesmo dump gerado pela porta firewire levou aproximadamente 7 minutos. Nossa, foi quase! Se não fosse por essa pequena vantagem de 3187 minutos.

Até mais…

Step into Kernel (Vista + USB2)

6 de March de 2008

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+ 🙂

Como assim eu não gosto de tela azul?

3 de September de 2007

Não que eu morra de alegria toda vez que vejo uma. Tela azul é um sinal de que algo errado aconteceu, mas melhor você tê-la visto que um cliente seu ligar dizendo que viu uma. Por isso, precisamos fazer a maior força para que elas aconteçam. Já vi programadores fugirem da tela azul tentando se esconder atrás de um manipulador de exceções. Acho que não preciso dizer que isso é tapar o sol com a peneira. Desta forma você apenas deixa de encontrar agora os erros que você ou alguém vai encontrar daqui a algum tempo, até lá, os micros que estão utilizando seus drivers podem ter uma reinicialização repentina aqui ou alí. Mas telas azuis acontecem mesmo, é tudo uma questão de sorte, só que seus clientes podem perceber que usar os seus drivers dá o maior azar. Neste post, vou dar algumas dicas de como podemos ver mais telas azuis em nossos testes de drivers.

Uma passada de F8

Não entenda isso como “Teste você mesmo”, mas entenda por testar todos os códigos de retorno que você puder. Lembre-se que se algo der errado, não será apenas mais um MessageBox que vai aparecer, mas normalmente quando as coisas tomam esse rumo, tudo termina em azul, mais cedo ou mais tarde. Vamos torcer pelo mais cedo. Costumo dizer que todo código merece pelo menos uma passada de F8, que é a minha tecla de Step into. É, eu sei que é diferente do normal, mas quando comecei a utilizar o Visual C/C++ 1.52, o layout do teclado era esse. Mas voltando ao ponto, certa vez eu fiz uma função que deveria simplesmente ler um arquivo, e na minha passada de F8 recebi um código de retorno que não era STATUS_SUCCESS, mas passou na macro NT_SUCCESS(). A pressa me fez ignorar aquilo, afinal não era um erro, era apenas um aviso. Semanas depois, a equipe de teste me disse que por algum motivo o driver estava retornando lixo na leitura do arquivo. Um pouco de debug me voltou a mostrar aquele mesmo código de retorno. Foi só depois de olhar a sua definição no arquivo ntstatus.h que entendi tudo.

//
// MessageId: STATUS_PENDING
//
// MessageText:
//
//  The operation that was requested is pending completion.
//
#define STATUS_PENDING                   ((NTSTATUS)0x00000103L)    // winnt

Enquanto depurando, o sistema tem tempo suficiente para fazer o I/O que ficou para depois, mas quando botamos o bixo pra correr a história é outra.

Um código que passou no seu teste de F8 rodará nos mais diversos ambientes existentes e imagináveis. Logo, essa primeira fase serve apenas para retirar os erros mais grosseiros. Depois disso, é a equipe de testes que deveria surrar a vítima. Existem pessoas que realmente tem o dom de testar software. Trabalhei em uma empresa em que a pessoa que testava os produtos devia ter algum problema pessoal com o software que produzíamos (ou com nós, vai saber). Eu podia testar por dias, mas quando eu entregava o software para teste, não demorava meia hora para ele me ligar de volta dizendo aquela frase que já virou sua marca registrada: “Muito ruim!!!”. Não tinha explicação. Costumávamos dizer que o micro dele tinha sido formatado em cima de um cemitério indígena. Não é à toa que teste de desenvolvedor é tão mal visto.

Acerte no ASSERT

Imagino que a maioria de vocês já tenha ouvido falar na macro ASSERT. Esta macro tem a finalidade de assegurar que determinada condição seja verdadeira. Caso a condição seja falsa, uma tela azul nos salta aos olhos. Nossa, que macro incrível, ajudou muito mesmo. É, eu sei, não é bem assim. Na verdade este é o comportamente que teríamos se o depurador não estivesse atachado ao sistema. Então seria perfeito utilizar esta macro somente em Checked. Devo lembrá-los que Checked significa Debug e Free significa Release? Bom, já foi. Se dermos uma olhada em sua definição, veremos que o óbvio já foi pensado.

#if DBG
 
#define ASSERT( exp ) \
    ((!(exp)) ? \
        (RtlAssert( #exp, __FILE__, __LINE__, NULL ),FALSE) : \
        TRUE)
...
 
#else
 
#define ASSERT( exp )         ((void) 0)
 
...
 
#endif // DBG

Mas o que acontece se estivermos com o depurador atachado? Boa pergunta, essa é realmente uma pergunta interessante, fico feliz que você a tenha feito. Alguém já lhe disse que você leva jeito para programar? Enfim, eu alterei um dos nossos exemplos para forçar esta condição.

extern "C"
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObj,
                     IN PUNICODE_STRING pusRegistryPath)
{
    //-f--> Eu acho que tenho certeza de que esta conta está certa.
    ASSERT(1 + 1 == 3);

Caso a condição falhe e estivermos com um debugger de Kernel atachado ao sistema, este exibirá a condição que falhou na janela de saída e nos solitará que entremos com uma das quatro alternativas como mostra abaixo.


Como vocês podem ver, o ASSERT é prático, fácil e não engorda. Ele só requer um pouquinho assim de cérebro. Digo isso porque todos nós já tivemos dias difíceis, e depois das 23h nenhum programador deveria responder por qualquer código produzido. Certa vez custou-me descobrir por que o código abaixo não funcionava adequadamente. Apesar de tudo funcionar perfeitamente bem quando compilado em Checked, parecia simplesmente que uma certa função não estava sendo chamada em Free. Se derem mais uma olhada na definição desta macro, verão que a condição desaparece quando compilada em Free, e neste caso, a chamada também.

    //-f--> Não basta querer programar, tem que pensar.
    //      << Não copie isso >>
    ASSERT(DoSomeThing() == STATUS_SUCCESS);

Um sistema em Checked

Não seria bom ter um sistema operacional inteiro repleto de ASSERTs e testes para detectar o menor sinal de problemas, e ao encontrá-lo, nos presentearia com uma bela tela azul? Bem, você provavelmente já deve ter ouvido falar das versões Checked Build. Elas são exatamente isso que acabei de escrever. Um sistema operacional inteiro compilado em Checked. Isso significa que todos os ASSERTs que estão nos fontes foram incluidos nos binários finais e estão verificando o sistema para você. Pode parecer besteira ter que instalar seu driver em um desses, mas acredite, vale a pena. Já tive drivers que funcionaram muito bem durante meses, até que meu gerente sugeriu que fossem testados em versões Checked. No topo da minha arrogância, eu pensei comigo mesmo: “Até parece que vai pegar alguma coisa”. É, vivendo e aprendendo. A máquina nem iniciava com meus drivers, era uma tela azul atrás da outra. As versões de Checked Build têm verificações até de deadlock. Você seria capaz de adivinhar o que aconteceria se algum spinlock fosse mantido por mais de um determinado tempo? Aposto que sim.

Já que as versões Ckecked Build são à prova de falhas, posso usá-las como meu sistema padrão? Poder pode, mas tudo é muito mais lento, milhares de verificações estão sendo feitas todo o tempo e o código não tem qualquer otimização aplicada. Em uma instalação do zero, sem instalar qualquer software adicional, você pode encontrar ASSERTs do Internet Explorer ou de outros programas. Isso mesmo, não é só o Kernel que está em Checked, mas todo o sistema. Você teria sempre que deixar um depurador de Kernel atachado ao seu sistema, pois o menor sinal de fumaça seria motivo mais que suficiente para mais uma tela azul.

Se passou pelo Checked Build então o driver está perfeito? Sinto desapontá-lo. Lembre-se que o impacto de performance causado por tantos testes pode ocultar problemas como race conditions. O ideal é testar seus drivers em ambas as versões. Uma configuração permite que tenhamos apenas a imagem do sistema e a HAL em Checked enquanto que o restante do sistema fique em Free. Isso permitiria ter testes adicionais no Kernel enquanto o restante do sistema roda a versão mais leve. Este link explica como fazer isso.

Agora sim meu driver tá lindio?

Na verdade, isso ainda é só o mínimo que você deveria fazer. Uma outra excelente ferramenta geradora de telas azuis é o Driver Verifier. Este aplicativo trabalha em conjunto com o sistema para monitorar ações de uma lista de drivers composta por você. A partir do Windows 2000, o Driver Verifier já vem instalado. Experimente agora mesmo! Não requer prática e tão pouco habilidade. Na janela de “Run…” do Windows, digite “Verifier” e um simples Wizard será iniciado. Como pretendo terminar este post ainda nesta vida, não vou descrever passo a passo como utilizar esta ferramenta, mas existem vários links na página do produto que podem te ajudar nisso.

Vai gostar de tela azul assim no inferno!

Para se ter uma idéia de como a busca por telas azuis é importante para uma companhia de software, a Microsoft promove o IFS Plug Fest com o intuito de testar a interoperabilidade de diversos produtos que implementam File Systems ou filtros para eles. O evento é gratuito, mas cada compania deve arcar com as despesas de viagem e estadia de seus representantes. Nestes encontros, os profissionais do mundo todo reunem-se para fazer os testes entre si além de poder ter o contato e tirar possíveis dúvidas a respeito deste complexo modelo de desenvolvimento. São também promovidos seminários que discutem os problemas comuns enfrentados por esta comunidade. Ainda não estive em um desses, mas um dia quem sabe.

Calma, ainda tem mais

Além das ferramentas aqui citadas, existem outras que eu não poderia deixar de citar. Mas não vou me prolongar em explicar como cada uma delas funciona.

  • Prefast – Ferramenta que faz análize dos fontes a serem compilados pelo DDK em busca de erros comuns de programação para drivers de Kernel.
  • Static Driver Verifier – Esta ferramenta faz uma análise estática do binário já compilado. O teste é bem mais demorado, mas obtém mais resultados que os obtidos no Prefast.
  • Hardware Compatibility Test – Conjunto de softwares que aplicam o testes para a obtenção da certificação de drivers da Microsoft. Descontinuada há pouco tempo.
  • Driver Test Manager – Este é o software sucessor do HCT para testes de certificação de drivers.

Depois de tanta tela azul, só café mesmo pra ajudar.
Até mais!

Debug ou falha de segurança?

21 de August de 2007

A um tempo atrás, escrevi um post que fala sobre como substituir drivers defeituosos utilizando o WinDbg. Uma técnica excelente para substituir drivers, que por algum problema, impedem a máquina de iniciar. Mas para conseguir desfrutar de toda essa facilidade, é necessário que haja uma conexão do sistema com o Kernel Debugging. Isso nos leva ao meu último post, que fala justamente sobre como habilitar este recurso bastando apenas estar em frente à máquina, e no pior caso, ter uma porta serial. Tudo isso é muito cômodo, afinal, nunca sabemos onde um problema desconhecido pode acontecer, e muito menos, em qual máquina vai acontecer. Mas essa praticidade toda tem um preço. O efeito colateral disso é que qualquer um pode depurar o Kernel de uma máquina sem ao menos ter qualquer conta válida nela. Na verdade, um notebook, um cabo e uma porta serial já são armas mais que suficientes para desmontar a sengurança do sistema. Sem senha, sem cabo de rede, sem brute-force. Apenas Kernel Debugging.

Instalando um driver pela serial

Drivers são um tipo de software que precisa ser confiável e muito bem escrito. Afinal de contas, eles rodam no nível mais baixo do sistema. Com drivers fica realmente muito fácil enganar o sistema, já que boa parte do sistema roda sobre serviço oferecidos pelo Kernel. Os posts acima me motivaram a escrever uma pequena prova de conceito que tem por finalidade injetar um driver que se instala no sistema, este driver, por sua vez, extrai e instala um programa no Windows que será executado a cada logon no sistema. Para tanto, precisaremos apenas de uma máquina que possa ser depurada. Meu último post mostra que essa condição não é tão difícil de ser encontrada. Quer testar? Qual a percentagem de computadores à sua volta que possuem portas seriais?

Passo a passo

Este é mais um daqueles posts que vêm com código disponível para download. Todo o procedimento demonstrado aqui pode ser reproduzido e modificado pelos leitores deste blog. Este não é um blog especializado em ataques ao Windows e meu objetivo aqui é puramente didático. Não vou explicar cada linha do código de exemplo, caso contrário, esse post ficaria muito extenso. Entretanto, me disponho a responder quaisquer dúvidas a respeito. A figura abaixo enumera os passos a serem tomados para atingir nosso objetivo.

  1. O primeiro passo acontece durante a compilação do driver de exemplo. O solution disponível possui três projetos. O primeiro projeto é o da aplicação a ser injetada. Neste caso, uma aplicação Win32 que apenas mostra uma mensagem e termina. Vamos nos referir a esta aplicação como MyFile.exe. A Run Time do C/C++ foi removida para obtermos a menor aplicação possível. Isso vai contar bastante quando você tiver que transferir tudo por um cabo serial a 19200. O segundo projeto é apenas uma ferramenta. A partir do arquivo MyFile.exe já compilado, esta ferramenta gera um arquivo de header que cria uma variável do tipo array de caracteres com o corpo do arquivo passado como linha de comando. Este não é o método mais avançado de se fazer um Self-Extractor, mas é suficiente para nosso objetivo aqui. Vamos chamar esta ferramenta de HeaderFile.exe. Quando o driver é compilado, este inclui o arquivo de header e ganha uma variável com a aplicação a ser injetada. Vamos chamar nosso driver de MapInject.sys. O driver neste exemplo é compilado utilizando o ddkbuild.cmd com Visual Studio 2005. Mais detalhes a respeito neste post.

    /****
    ***     WinMain
    **
    **      Não faz quase nada e não usa a Run Time do C/C++
    **      para evitar código longo.
    */
     
    int WINAPI WinMain(HINSTANCE   hInstance,
                       HINSTANCE   hPrevInstance,
                       LPSTR       lpCmdLine,
                       int         nShowCmd)
    {
        //-f--> É só isso mesmo...
        MessageBox(NULL,
                   L"A serial port was found",
                   L"DriverEntry.com.br",
                   MB_OK | MB_ICONEXCLAMATION);
     
        return 0;
    }
  2. Utilizando as técnicas de mapeamento de drivers demostradas neste post, podemos fazer com que nosso driver possa ser carregado no lugar de qualquer driver pré-existente no sistema. Obviamente nosso driver não vai realizar todos os passos que o driver sobrescrito deveria realizar, deste modo, não vamos escolher um driver que realize tarefas essenciais à carga do sistema, tais como drivers de disco, de vídeo ou algo assim. Particularmente escolhi utilizar o driver da porta paralela Partport.sys, que é um driver que existirá em qualquer instalação básica de Windows e não vai nos fazer tanta falta enquanto brincamos por aqui. Este também não é nenhum driver do tipo Boot, que no caso do Windows XP, seria necessário ter um Loader especial para poder ser mapeado pelo Debugger.

    map 
    \Windows\System32\drivers\parport.sys 
    Z:\MapInject\Driver\objfre_wnet_x86\i386\MapInject.sys
  3. Com o driver mapeado e carregado pelo sistema, basta um pouco de boa vontade para poder pintar e bordar com o sistema. Quando nosso driver é carregado pela primeira vez, este é carregado no lugar do Parport.sys. Nesta condição, nos instalamos no sistema. Certas coisas nunca mudam. Para instalar um driver no Windows plataforma NT basta criar uma chave e poucos valores no registro do sistema como descrito neste post. Isso ainda funciona no Windows Vista. Nosso driver faz isso neste passo.

  4. Feitas as modificações no registro, agora temos que renomear nosso driver, que pelo fato de ter sido mapeado, ganhou o nome do driver nativo do sistema. Assim, uma função troca o nome do driver de Parport.sys para MapInject.sys. Poderiamos permanecer instalados no sistema da maneira que fomos mapeados se quizéssemos, mas perderiamos nosso lugar caso o verdadeiro driver fosse restaurado.

  5. Como sabem, nosso driver possui uma variável que contém todo o corpo de nossa aplicação. Assim, fica muito simples extrair a aplicação e salvá-la em disco. A pasta System32 vem bem a calhar como destino para nossa aplicação. Como estamos rodando em conta de sistema neste momento, não teremos problemas em fazer isso.

  6. Agora que nossa aplicação foi extraída, temos que fazer com que ela seja executada automagicamente. Alguns de vocês devem saber que não existem meios documentados ou mesmo suficientemente confiáveis de se criar um processo a partir do Kernel, mas no registro existem vários lugares onde poderíamos escrever e fazer com que um processo já existente no Windows execute a nossa aplicação. Uma das mais clássicas chaves do registro que permite isso é a Run do Windows Explorer, mas como mostra este aplicativo, ainda existem outras tantas chaves a serem escolhidas. Quando o Logon é realizando no sistema, o Shell enumera esta chave e executa cada linha na conta do usuário que fez o logon. Isso vai fazer nosso programa ser chamado.

Colocando para funcionar

Mais uma vez, uma máquina virtual nos cai como uma luva para este tipo de brincadeira. Se você não sabe como fazer Kernel Debugging utilizando uma máquina vitual, este post poderá te ajudar. Tudo é muito simples depois que o driver já está codificado. A única coisa necessária para realizar o teste é configurar um mapeamento que substitua o driver Parport.sys pelo MapInject.sys como mostra o arquivo de map acima. Depois disso, é só esperar o sistema subir e pronto. Qualquer usuário que efetuar o logon no sistema verá a mensagem do nosso programa. Um ponto a ser notado é que mesmo se o usuário for esperto o suficiente para encontrar o arquivo MyFile.exe, sua chamada no registro e apague ambos, os mesmos serão recriados quando o driver MapInject for iniciado a cada Boot do sistema. Para interromper esse processo, basta remover o driver MapInject do sistema.

Senha? Pra quê?

O Windows protege seus mais importantes recursos da maioria dos usuários, permitindo apenas que administradores da máquina tenham acesso às configurações que podem comprometer a estabilidade do sistema. Essa frase até que é “bunitinha” e chega até a gerar um certo conforto aos adminitradores de muitas e muitas máquinas. Mas a vida é uma caixinha de surpresas, e numa bela manhã de sol, um post é publicado em um site de RootKits dando dicas de como logar em um sistema em Debug sem qualquer senha. Recentemente, um outro post surgiu a respeito e não vou repetir tudo aqui. Mas posso afirmar que esta mesma técnica ainda funciona com o Windows Vista. De fato, se você puder logar no sistema sem utilizar qualquer senha, não será necessário mapear um driver para fazê-lo rodar nesta máquina. Basta logar como administrador, instalá-lo e pronto. Mas confesso que foi no mínimo divertido escrever esta prova de conceito, e além do mais, os fontes ainda poderão servir de exemplo para algumas tarefas que você queira fazer em Kernel Mode.

Uma possível solução? Talvez deixar sua porta serial desabilidata na BIOS, que deveria estar protegida com senha, seja alguma coisa a ser feita. Mas não há muito que se possa fazer quando se tem acesso físico à maquina.

Chega, já escrevi demais.
[]s.

MapInject.zip

Debug de Emergência

15 de August de 2007

De repente tela azul. Isso mesmo, aquele seu driver que você já não via o fonte a muito tempo, que era o seu modelo de driver estável, quando foi instalado na máquina do presidente da empresa que você trabalha, resolveu se vingar e dar uma tela azul logo na inicialização do sistema. Ou seja, a máquina liga e reseta e reseta e reseta em um loop infinito que roda em paralelo com a impressão da sua carta de demissão, que por sinal, está sendo impressa em outro micro. Para depurar essa máquina e ver o que está acontecendo, e assim salvar seu projeto, seu emprego, sua dignidade, seu casamento, e por que não dizer, sua vida, você deve configurar seu sistema para entrar em Modo Debug para que o nosso amigo inseparável WinDbg possa entrar em ação. Mas como configurar para Debug uma máquina que não está nem iniciando? Hoje vou falar sobre o suporte que o Windows oferece para evitar esse tipo de situação constrangedora. Como conectar um Debugger em um micro que nunca teve suas configurações de Kernel Debugging habilitadas, seja pelo BOOT.INI ou pelo BdcEdit.

Este post admite que você já sabe como fazer Kernel Debugging e utiliza alguns termos próprios desta prática. A partir do Windows 2000, se você pressionar a tecla F8 enquanto máquina está rodando o NT Loader, mais precisamente, 2 segundos antes de o sistema começar a ser carregado, o sistema paraliza e exibe o menu como mostra abaixo.

Selecione o ítem “Debugging Mode”, isso nos vai levar à lista de sistemas operacionais instalados. Selecione o sistema e o resto fica por conta do WinDbg. Nessas condições, a configuração da conexão serial aplicada seria um Baud rate de 19200 na porta serial mais alta existente em sua máquina. Isto é, se seu micro tem duas portas seriais, então a conexão seria feita a 19200 na COM2. Pelo menos isso é o que a referência nos diz.

Mas na vida real…

Se você fizer alguns testes, também vai concluir que não podemos acreditar em tudo que lemos. Tanto o Windows 2000 como o Windows XP, utilizam sempre a porta COM2 a 19200.

Meu computador é moderno e só tem uma porta serial. Posso me matar? Pois é, mesmo nesses casos, o sistema tenta utilizar a porta COM2, mesmo que ela não exista. Mas não se preocupe, a vida é bela e ainda há uma esperança. Algumas BIOS permitem que você configure o endereço base e a interrupção da única porta serial existente. Isso vai nos ajudar muito, afinal de contas, o NT Loader não é uma aplicação Win32 que solicita um handle para o device COM1 ou COM2 através do Object Manager. Ele simplesmente segue os endereços adotados como padrões.

  • COM1: Address 3F8h, IRQ 4
  • COM2: Address 2F8h, IRQ 3

Desta forma, você poderá configurar a única porta (a COM1) para responder pelos endereços que normalmente são utilizados pela COM2. A BIOS normalmente tem uma lista dos recursos normalmente utilizados pelas portas seriais. Assim, qualquer chinpanzé autista poderia fazer essa troca.

Mas meu computador é mais moderno ainda e não tem nenhuma porta serial. Posso me matar agora? A esperança é a última que morre. Você pode ainda instalar uma placa expansora PCI (ou PCMCIA em caso de notebooks) que nos forneça as portas seriais de que precisamos. É importante notar que esta placa expansora deve criar as portas COM1 e COM2 nos endereços padrões acima descritos.

Você não está entendendo, sou um lazarento que comprou um MacBook 13″, que além de não ter portas seriais, também não oferece nenhum slot PCMCIA ou ExpressCard. Posso me matar agora? Como já comentei em outro post, portas seriais criadas a partir de portas USB não são uma opção para serem utilizadas em uma máquina TARGET durante o Kernel Debugging. Esta conexão é feita pelo NT Loader e as portas seriais que ele conhece são apenas aquelas que respondem pelos endereços acima citados. Agora sim, pode se matar.

Alguma melhoria em Vista?

Já o Windows Vista, este sim ignora a documentação sem dó. A configuração padrão para Kernel Debugging quando pressionamos F8 durante o Boot do Windows Vista é utilizar a porta COM1 a 115200. Mas se você utilizou o aplicativo BcdEdit com o parâmetro /DbgSettings para modificar as configurações de conexão para Kernel Debugging, então serão estas mesmas configurações a serem utilizadas quando F8 for pressionado. Ou seja, se você é um lazarento que, como eu, configurou a porta IEEE 1397 para fazer Debug em seu MacBook, então estas serão as configurações utilizadas quando você pressionar F8.

Pode parecer brincadeira, mas é realmente frustrante ter que ir até à máquina do cliente onde seu driver está dando trabalho, armado dos seus vários aparatos tecnológicos, tais como notebook, porta FireWire, cabo serial e assim por diante, e não poder fazer nada simplesmente porque você não consegue conectar o depurador.

Mais uma vez, espero ter ajudado.
Até mais!

Bug em meu driver de Boot. Já posso formatar?

16 de July de 2007

Escrever drivers é uma tarefa que deve ser feita com um pouco de cuidado. Afinal, qualquer situação mal resolvida entre o seu código e o sistema operacional resultará em uma bela tela azul. Mas para tudo tem remédio nessa vida e felizmente Deus criou o depurador para enfrentar essas situações. Encontrando o problema, é só trocar o arquivo .sys e pronto. Só trocar o arquivo significa esperar que a máquina fique em um estado estável, substituir ao arquivo no sistema e reiniciar a máquina. Mas a vida é uma caixinha de surpresas e por encreça que parível, a maioria dos drivers que escrevemos é iniciado automagicamente. Bom, nesse caso é só torcer então para que o erro não aconteça até que o driver seja substituído. Caso contrário teríamos que utilizar recursos como colocar a HD da máquina vítima em outro sistema e substituir o driver doente, ou na falta de outro sistema, utilizar o console de recuperação do XP para evitar do seu driver subir. Então é só torcer para que ele não seja um filtro para algo importante como disco, vídeo ou File System. Sabe como é, se o filtro não sobe, o driver principal também não. Resumindo, existe uma série de malabarismos que você pode inventar para substituir um driver de carga automática que esteja com bug. Mas será que não existe algo que não dependa tanto da sorte ou mesmo da criatividade de cada um? Hoje vou falar um pouco sobre o sistema de substituição de drivers com WinDbg.

Mapeando arquivos de drivers

Você pode mapear os arquivos de drivers para que o WinDbg substitua o driver no momento em que este for carregado. Isso não é lindo? Para que isso ocorra, você precisa utilizar o comando .kdfiles. Com este comando, você faz um vínculo entre o driver que roda na máquina Target e o driver que foi corrigido. Uma das maneiras de fazer isso é inicialmente criando um arquivo que relacione estes dois drivers. Este arquivo deve ser um arquivo texto simples onde a sintaxe é a mostrada abaixo. Este arquivo pode ter qualquer nome e extensão.

map 
\??\C:\Windows\System32\drivers\MeuDriver.sys 
C:\Pasta_Do_Driver_Novo\MeuDriver.sys

A palavra map marca o início de um mapeamento, isso não muda. A linha seguinte é o caminho do arquivo do driver que deve ser substituído. Esta linha deve ter o mesmo formato do utilizado no valor ImagePath na chave do driver que fica no registro. A última linha é o caminho do driver novo. Este caminho pode ser na própria máquina Host ou mesmo na rede.

Este mapeamento funciona apenas no Windows XP ou superior, na máquina Target obviamente. Se você ainda não está acostumado com termos Host e Target, dê uma olhada neste post.

Depois de criado o arquivo, você utiliza o WinDbg para lançar o seguinte comando:

kd>.kdfiles C:\Pasta\MeuMap.txt

A partir desse momento, toda vez que o driver for carregado pelo sistema, o Kernel verifica se este arquivo está mapeado no depurador, e se estiver, o WinDbg envia o novo driver pelo meio de conexão entre eles, seja serial, firewire ou USB. Para arquivos grandes, recomenda-se utilizar firewire. Entenda que o arquivo é substituído no disco da máquina Target pela versão nova. Isso significa que nas próximas inicializações nosso driver novo ainda será carregado, mesmo que não tenhamos o debugger atachado ao sistema.

Será que funciona mesmo?

Para tornar as coisas um pouco mais claras, vamos ver um exemplo prático. Para isso vamos precisar de um fonte de driver bem simples, para não dizer inútil, que pode ser encontrado aqui. Vamos alterar a função DriverEntry de forma que fique como abaixo:

NTSTATUS DriverEntry(IN PDRIVER_OBJECT  pDriverObject,
                     IN PUNICODE_STRING pusRegistryPath)
{
    //-f--> Vamos utilizar __DATE__ e __TIME__ para que tenhamos
    //      uma versão nova de driver a cada rebuild
    DbgPrint("Este driver foi compilado em "__DATE__" "__TIME__"\n");
 
...

Compilamos uma versão inicial e o instalamos na maquina Target da maneira que você achar melhor, mas vamos admitir que depois de instalado teremos o registro como na figura abaixo.

Se iniciarmos o driver, teremos algo parecido com a string abaixo na saída do depurador:

Este driver foi compilado em Jul 16 2007 00:04:03

Agora vamos criar nosso arquivo de mapeamento. Aqui vou salvá-lo como Z:\Sources\DriverEntry\Useless\map.txt. Seguindo o formado de como o driver foi registrado no registro, nosso arquivo de mapeamento deveria ter o seguinte conteúdo:

map
\??\C:\Windows\System32\drivers\Useless.sys
Z:\Sources\DriverEntry\Useless\objchk_wxp_x86\i386\Useless.sys

Notem que a pasta onde salvei o meu arquivo map.txt é o mesmo onde os fontes do driver estão. Isso é apenas por uma questão de organização. O arquivo de mapeamento poderia estar em qualquer pasta. Em seguida utilizamos o comando .kdfiles como mostra abaixo. Notem que listamos os mapeamentos existentes simplesmente utilizando o mesmo comando sem qualquer parâmetro.

kd> .kdfiles Z:\Sources\DriverEntry\Useless\map.txt
KD file assocations loaded from 'Z:\Sources\DriverEntry\Useless\map.txt'
 
kd> .kdfiles
KD file assocations loaded from 'Z:\Sources\DriverEntry\Useless\map.txt'
\??\C:\Windows\System32\drivers\Useless.sys ->
Z:\Sources\DriverEntry\Useless\objchk_wxp_x86\i386\Useless.sys

Na seqüência, vamos dar um Rebuild no driver (isso deveria mudar aquele time stamp) e vamos reiniciar o driver. Se tudo estiver certo aí do seu lado, você deveria ter uma saída com um time stamp diferente do que tínhamos antes.

kd> g
Mas já? Eu nem fiz nada...
KD: Accessing 'Z:\Sources\DriverEntry\Useless\objchk_wxp_x86\i386\Useless.sys'
 (\??\C:\Windows\system32\drivers\Useless.sys)
  File size 2K.
MmLoadSystemImage: Pulled \??\C:\Windows\system32\drivers\Useless.sys from kd
Este driver foi compilado em Jul 16 2007 00:17:04

Bom, se isso funciona em um driver com start manual, então com o automático também deveria funcionar. Para vermos isso, mude o start do seu driver para System ou Automatic, dê um Rebuild mais uma vêz no driver, e por fim vamos reiniciar a máquina. Quando o driver for carregado, deveremos ter a substituição automática de sua imagem na sua carga. No WinDbg teremos o seguinte:

Connected to Windows XP 2600 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 XP Kernel Version 2600 UP Free x86 compatible
Built by: 2600.xpsp_sp2_gdr.070227-2254
Kernel base = 0x804d7000 PsLoadedModuleList = 0x805533a0
System Uptime: not available
KD: Accessing 'Z:\Sources\DriverEntry\Useless\objchk_wxp_x86\i386\Useless.sys'
 (\??\C:\Windows\system32\drivers\Useless.sys)
  File size 2K.
MmLoadSystemImage: Pulled \??\C:\Windows\system32\drivers\Useless.sys from kd
Este driver foi compilado em Jul 16 2007 00:25:38

E não é que funciona mesmo! Mas e se meu driver for um driver com start do tipo Boot? Isso significa que a imagem do driver vai ser carregada antes mesmo da conexão com o WinDbg ser estabelecida. Vocês já ouviram falar que o que não tem remédio, remediado está? Felizmente isso não se aplica aqui ainda. Existe uma maneira de fazer com que mesmo os drivers de Boot sejam mapeados e substituídos.

Mapeando drivers de Boot

Para estabelecer uma conexão com o WinDbg, precisaremos substituir o loader do sistema por uma versão especial. Esta versão faz esta conexão com o Kernel Debugger antes mesmo que arquivo Boot.ini seja lido. Por esta razão, os parâmetros de conexão são fixos com porta COM1 e baudrate de 115200. Esta versão do loader é encontrada no diretório C:\WinDDk\3790\debug do DDK com o nome de ntldr_dbg. Este arquivo deve substituir a versão original do loader do sistema que fica no raiz do drive de boot com o nome ntldr. A versão debug deve ficar com o mesmo nome do loader original.

Antes de reiniciar o sistema, devemos mudar o start do driver para Boot e remover o valor ImagePath do registro. Como vocês devem saber, drivers de boot não têm esse luxo de utilizar caminhos que tenham letras de unidades e outras frescuras. Ao final das alterações, deveríamos ter o registro como mostra abaixo.


Como falei anteriormente, o formato do caminho do arquivo do driver deve ser o mesmo de como está no registro, mas sabendo que agora não há mais caminho no registro, devemos adotar o mesmo formado adotado pelo sistema. Ah tá! O mesmo formato. E qual seria? Para ver este formato basta reiniciar o sistema com o loader debug, o que deve nos dar a seguinte tela no boot do sistema.

Este é o momento de conectar com o Windbg utilizando os parâmetros pré-determinados de conexão. Isso deveria nos resultar a seguinte saída no depurador:

Microsoft (R) Windows Debugger  Version 6.7.0005.1
Copyright (c) Microsoft Corporation. All rights reserved.
 
Opened \\.\pipe\com_1
Waiting to reconnect...
BD: Boot Debugger Initialized
BD: osloader.exe base address 00400000
Connected to Windows Boot Debugger 3790 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 Boot Debugger Kernel Version 3790 UP Checked x86 compatible
Primary image base = 0x00000000 Loaded module list = 0x00000000
System Uptime: not available
The call to LoadLibrary(bootext) failed, Win32 error 0n2
    "The system cannot find the file specified."
Please check your debugger configuration and/or network access.

Agora é apresentado o menu de seleção do Boot.ini. Selecione sua opção e continue carregando o sistema. Deve ser apresentada a lista de drivers de Boot, que são os drivers que são carregados antes do primeiro breakpoint que o debugger pode parar.

BD: \WINDOWS\system32\ntoskrnl.exe base address 804EA000
BD: \WINDOWS\system32\hal.dll base address 806FF000
BD: \WINDOWS\system32\KDCOM.DLL base address 80720000
BD: \WINDOWS\system32\BOOTVID.dll base address 80010000
BD: \WINDOWS\system32\DRIVERS\ACPI.sys base address 80124000
BD: \WINDOWS\system32\DRIVERS\WMILIB.SYS base address 80001000
BD: \WINDOWS\system32\DRIVERS\pci.sys base address 80062000
BD: \WINDOWS\system32\DRIVERS\isapnp.sys base address 80003000
BD: \WINDOWS\system32\DRIVERS\compbatt.sys base address 8000C000
BD: \WINDOWS\system32\DRIVERS\BATTC.SYS base address 80013000
BD: \WINDOWS\system32\DRIVERS\intelide.sys base address 80017000
BD: \WINDOWS\system32\DRIVERS\PCIIDEX.SYS base address 80019000
BD: \WINDOWS\System32\Drivers\MountMgr.sys base address 80152000
BD: \WINDOWS\system32\DRIVERS\ftdisk.sys base address 8015D000
BD: \WINDOWS\System32\drivers\dmload.sys base address 80073000
BD: \WINDOWS\System32\drivers\dmio.sys base address 8017C000
BD: \WINDOWS\System32\Drivers\PartMgr.sys base address 801A2000
BD: \WINDOWS\System32\Drivers\VolSnap.sys base address 801A7000
BD: \WINDOWS\system32\DRIVERS\atapi.sys base address 801B4000
BD: \WINDOWS\system32\DRIVERS\vmscsi.sys base address 801CC000
BD: \WINDOWS\system32\DRIVERS\SCSIPORT.SYS base address 801CF000
BD: \WINDOWS\system32\DRIVERS\disk.sys base address 801E7000
BD: \WINDOWS\system32\DRIVERS\CLASSPNP.SYS base address 801F0000
BD: \WINDOWS\system32\DRIVERS\fltMgr.sys base address 801FD000
BD: \WINDOWS\system32\DRIVERS\sr.sys base address 8021D000
BD: \WINDOWS\System32\Drivers\KSecDD.sys base address 8022F000
BD: \WINDOWS\System32\Drivers\Ntfs.sys base address 80246000
BD: \WINDOWS\System32\Drivers\NDIS.sys base address 802D3000
BD: \WINDOWS\System32\Drivers\Useless.sys base address 8000F000
BD: \WINDOWS\System32\Drivers\Mup.sys base address 80300000
BD: \WINDOWS\system32\DRIVERS\agp440.sys base address 8031B000
Shutdown occurred...unloading all symbol tables.
Waiting to reconnect...

Aqui a conexão com o loader é encerrada. Uma nova conexão seria estabelecida caso você tivesse selecionado a entrada de Debug do Boot.ini, mas o que temos que notar aqui é o formato utilizado para a carga dos drivers de boot pelo sistema. Notem que entre os drivers da lista acima está o nosso driver de teste. Vamos adotar este mesmo formato em nosso arquivo de mapeamento.

map
\WINDOWS\System32\Drivers\Useless.sys
Z:\Sources\DriverEntry\Useless\objchk_wxp_x86\i386\Useless.sys

Depois de modificar o arquivo de mapeamento e salvar seu conteúdo em disco, devemos atualizar o WinDbg para que ele pegue esta mudança. Em seguida vamos reiniciar o sistema.

kd> .kdfiles Z:\Sources\DriverEntry\Useless\map.txt
KD file assocations loaded from 'Z:\Sources\DriverEntry\Useless\map.txt'
 
kd> .reboot
Shutdown occurred...unloading all symbol tables.
Waiting to reconnect...
BD: Boot Debugger Initialized
Connected to Windows Boot Debugger 3790 x86 compatible target, ptr64 FALSE
Kernel Debugger connection established.  (Initial Breakpoint requested)
Symbol search path is: SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols
Executable search path is: 
Module List address is NULL - debugger not initialized properly.
WARNING: .reload failed, module list may be incomplete
KdDebuggerData.KernBase < SystemRangeStart
Windows Boot Debugger Kernel Version 3790 UP Checked x86 compatible
Primary image base = 0x00000000 Loaded module list = 0x00000000
System Uptime: not available
The call to LoadLibrary(bootext) failed, Win32 error 0n2
    "The system cannot find the file specified."
Please check your debugger configuration and/or network access.

A mesma seqüência de mensagens acontece, mas desta vez, o mapeamento está feito como se deve e teremos a seguinte saída quando os drivers de boot forem carregados.

BD: osloader.exe base address 00400000
BD: \WINDOWS\system32\ntoskrnl.exe base address 804EA000
BD: \WINDOWS\system32\hal.dll base address 806FF000
BD: \WINDOWS\system32\KDCOM.DLL base address 80720000
BD: \WINDOWS\system32\BOOTVID.dll base address 80010000
BD: \WINDOWS\system32\DRIVERS\ACPI.sys base address 80124000
BD: \WINDOWS\system32\DRIVERS\WMILIB.SYS base address 80001000
BD: \WINDOWS\system32\DRIVERS\pci.sys base address 80062000
BD: \WINDOWS\system32\DRIVERS\isapnp.sys base address 80003000
BD: \WINDOWS\system32\DRIVERS\compbatt.sys base address 8000C000
BD: \WINDOWS\system32\DRIVERS\BATTC.SYS base address 80013000
BD: \WINDOWS\system32\DRIVERS\intelide.sys base address 80017000
BD: \WINDOWS\system32\DRIVERS\PCIIDEX.SYS base address 80019000
BD: \WINDOWS\System32\Drivers\MountMgr.sys base address 80152000
BD: \WINDOWS\system32\DRIVERS\ftdisk.sys base address 8015D000
BD: \WINDOWS\System32\drivers\dmload.sys base address 80073000
BD: \WINDOWS\System32\drivers\dmio.sys base address 8017C000
BD: \WINDOWS\System32\Drivers\PartMgr.sys base address 801A2000
BD: \WINDOWS\System32\Drivers\VolSnap.sys base address 801A7000
BD: \WINDOWS\system32\DRIVERS\atapi.sys base address 801B4000
BD: \WINDOWS\system32\DRIVERS\vmscsi.sys base address 801CC000
BD: \WINDOWS\system32\DRIVERS\SCSIPORT.SYS base address 801CF000
BD: \WINDOWS\system32\DRIVERS\disk.sys base address 801E7000
BD: \WINDOWS\system32\DRIVERS\CLASSPNP.SYS base address 801F0000
BD: \WINDOWS\system32\DRIVERS\fltMgr.sys base address 801FD000
BD: \WINDOWS\system32\DRIVERS\sr.sys base address 8021D000
BD: \WINDOWS\System32\Drivers\KSecDD.sys base address 8022F000
BD: \WINDOWS\System32\Drivers\Ntfs.sys base address 80246000
BD: \WINDOWS\System32\Drivers\NDIS.sys base address 802D3000
KD: Accessing 'Z:\Sources\DriverEntry\Useless\objchk_wxp_x86\i386\Useless.sys'
 (\WINDOWS\System32\Drivers\Useless.sys)
  File size 2K.BD: Loaded remote file \WINDOWS\System32\Drivers\Useless.sys
 
BlLoadImageEx: Pulled \WINDOWS\System32\Drivers\Useless.sys from Kernel Debugger
BD: \WINDOWS\System32\Drivers\Useless.sys base address 8000F000
BD: \WINDOWS\System32\Drivers\Mup.sys base address 80300000
BD: \WINDOWS\system32\DRIVERS\agp440.sys base address 8031B000
Shutdown occurred...unloading all symbol tables.
Waiting to reconnect...

E mais tarde, nossa prova de que o arquivo foi substituído com sucesso.

Connected to Windows XP 2600 x86 compatible target, ptr64 FALSE
Kernel Debugger connection established.  (Initial Breakpoint requested)
Symbol search path is: SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols
Executable search path is: 
Windows XP Kernel Version 2600 UP Free x86 compatible
Built by: 2600.xpsp_sp2_gdr.070227-2254
Kernel base = 0x804ea000 PsLoadedModuleList = 0x8056d620
System Uptime: not available
Este driver foi compilado em Jul 16 2007 01:18:53

Eu lhe garanto que isso ainda poderia salvar sua vida se você tivesse um filtro de File System com um bug na DriverEntry em uma máquina de cliente. A expressão de pânico do cliente quando vê a máquina dele reiniciando em um loop infinito é interessante, mas manter seu emprego é um pouco mais interessante.

Até mais,
[]s.

Step into Kernel (VmWare+WinDbg)

31 de May de 2007

Agora chega de papinho furado e vamos ao que realmente interessa. Falando sobre Kernel Debugging, já escrevi um post descrevendo os passos necessários para fazer Kernel Debugging com o WinDbg utilizando duas máquinas e um cabo serial. Mas ter duas máquinas dedicadas a essa prática é um luxo que nem todos têm. Assim, em outro post, comentei sobre como instalar e utilizar o SoftIce para depurar drivers em uma única máquina dedicada para o debug. Digo máquina dedicada para debug porquê utilizar sua máquina de desenvolvimento para depurar drivers pode não ser uma das suas melhores idéias. Os mais corajosos e confiantes em seu próprio código ainda se arriscam nessa prática. Bom, eu não posso falar muito, já fiz isso em tempos de vacas magras. De qualquer forma, ainda não fugimos da necessidade de ter duas máquinas para termos um ambiente mínimo de desenvolvimento e teste de drivers. Uma alternativa a esses cenários é a utilização de uma única máquina, mas que tenha memória e CPU suficientes para rodar uma máquina virtual a fim de fazer o debug de Kernel. Neste post vou dar os passos para utilizar uma máquina virtual como cobaia para testar e depurar drivers.

Configurando a máquina virtual

O meio de comunicação natural entre máquinas reais que utilizam o WinDbg para fazer o Kernel Debugging é uma porta serial. Para seguir os mesmos passos, precisamos fazer com que a sua máquina virtual ganhe uma porta serial para fazer esta comunicação possível. Com sua máquina virtual desligada, clique sobre a opção Edit vitual machine settings.


Aparecerá uma janela com a lista dos dispositivos já instalados em sua máquina no tab Hardware. Clique em Add… de forma a adicionar um novo dispositivo e selecione Serial Port na lista de dispositivos que será apresentada na tela a seguir.


Depois de clicar em Next, selecione a opção Output named pipe. Isso fará com que a porta serial na máquina virtual se comunique com a máquina real através de um pipe nomeado. Clicando em OK, serão apresentadas as configurações específicas do pipe nomeado. O nome do pipe será utilizado mais tarde em uma das configurações do WinDbg, então caso você queira utilizar um outro nome que não o sugerido aqui, trate de se lembrar deste mesmo nome mais tarde. Em seguida, mude o valor do segundo combo de forma a indicar que o a outra ponta desta comunicação será uma aplicação, no caso o WinDbg. Depois disso, é só clicar em Finish.


Ao final destas configurações, marque a opção Yield CPU on poll conforme mostra a figura abaixo.

Configurando a máquina TARGET

Feito todo esse “Ziriguidum”, agora teremos que configurar o sistema da máquina TARGET para que ela esteja apta a fazer o Kernel Debugging. Lembre-se que para o Windows que roda dentro da máquina virtual, não existe pipe nomeado, apenas uma porta serial. Se você ainda não sabe o que é uma máquina TARGET nem como configurar uma, então dê um pulo neste post e siga os passos descritos na parte onde uma máquina TARGET é configurada.

Configurando a máquina HOST

Admitindo que sua máquina TARGET já foi configurada, agora teremos que configurar o WinDbg de forma que ele se conecte a uma máquina utilizando o pipe nomeado no lugar de uma porta serial. Para isso, costumo criar um arquivo de batch que contenha a seguinte linha de comando:

start C:\Arquiv~1\Debugg~1\windbg.exe -b -k com:pipe,port=\\.\pipe\com_1,resets=0

Criar um arquivo de batch não é um passo obrigatório, talvez você prefira redigitar tudo na janela Run… a cada vez que o debug for iniciado, mas é tudo uma questão de gosto. Repare que o nome do pipe aparece aqui. Espero que você ainda se lembre daquele nominho que você escolheu.

Conectando…

Agora que temos tudo já configurado, é só conectar e depurar. Nessa história, a máquina virtual é quem cria o pipe nomeado que será aberto pelo WinDbg. Sendo assim se você iniciar o WinDbg com os parâmetros demodntrados antes de iniciar a máquina virtual, você verá a janela abaixo informando que o pipe não foi encontrado.


Por isso a seqüência é primeiro ligar a máquina virtual, selecionar a opção debug no Boot como mostrado abaixo, e somente depois disso é que você deveria inciar o WinDbg com os parâmetros acima descritos.


Quando o WinDbg finalmente conecta na máquina virtual via o pipe nomeado, temos as seguintes mensagens em nosso Command Window dentro do WinDbg.


Daí em diante você já sabe… É pegar os paus mesmo (no bom sentido).

Mais uma vez espero ter ajudado.
Até a próxima. 🙂