WinDBG: Introdução ao Debugging (pt-BR)

Eu sou suspeito para falar, mas eu simplesmente sou fascinado por Debugging.
Seja no momento em que está codificando, ou depois que a aplicação já está em produção (o chamado postmortem debugging), é com debugging que os profissionais se irritam, ficam noites estudando, analisando, executando comandos e se superando. Descobrir a razão do erro ou da exceção e conseguir explicar até sua origem mais primitiva, dá uma sensação de ter o mais alto conhecimento do mundo da tecnologia.
Mas não se animem: isso dura só até o começo do próximo bug :(

Nesta série de artigos eu vou explicar a origem do debug, o significado e como realizar o debug utilizando ferramentas que não o Visual Studio 200X. O foco aqui é utilizar ferramentas mais poderosas, que possam levar o usuário até o stack, o heap, analisar as threads e leaks.
Além disso, vou apresentar também técnicas de análise de bugs, métodos para resolução de problemas, entre outras coisas não tão interessantes.

Afinal, o que é Debugging?

Debugging é o termo que tem a definição mais simples do mundo da tecnologia: remover bugs!
Não existe segredo. Debug serve para remover bugs, mais nada.
A questão é: quais são as melhores práticas para se conseguir isso? E em menor tempo? Como resolver tudo em menos de 1 hora? Alias, 15 minutos? Desculpa, 5 minutos!

Este é o cenário em que iremos trabalhar nestes posts.
Para resolver problemas complexos em pouco tempo, temos que conhecer ao máximo a estrutura da plataforma em que estamos trabalhando, a arquitetura do sistema, o que são processos, threads, handles, exceptions, first chance, ahhhh!

Debugging Tools for Windows

Nesta série de posts iremos utilizar o WinDBG, contido no Debugging Tools for Windows, disponibilizado gratuitamente pela Microsoft em http://www.microsoft.com/whdc/DevTools/Debugging/default.mspx. No momento em que estou escrevendo este post, a última versão disponível é a 6.9.3.113. O que não faz a menor diferença se você se deparar com uma versão mais nova no link que passei. Todas as versões do WinDBG mantém a compatibilidade com versões mais antigas, e normalmente só adicionam recursos.

Para começar, faça o download da versão mais atual e instale-a.
O processo de instalação é extremamente simples e rápido. Além disso, você pode copiar e colar o diretório instalado na sua máquina em um pen drive, por exemplo. Os programas funcionarão perfeitamente (o que alias é uma vantagem muito grande, afinal, instalar Visual Studio em uma máquina de produção seria um pouco complicado certo :)).

Este pacote de ferramentas apresenta uma série de programas tão interessantes quando o WinDBG, e serão apresentados no decorrer desta série de posts. Mas no momento, nosso foco é na apresentação deste incrível programinha que irá mudar nossas vidas.

WinDBG, quem é você?

WinDBG é um user mode e kernel mode debugger com uma interface gráfica. Entendeu?
Tá bom, vamos voltar um pouco...

O que é User Mode e Kernel Mode?

Pense em seu sistema operacional com um divisão ao meio, criando duas partes. Uma parte é básica para o sistema e garante seu funcionamento, gerenciamento de memória e comunicação com hardware. A outra parte mantém os programas em execução, proprietários, maléficos, benéficos, aqueles e-mails estranhos, etc. Agora imagine se os programas tivessem acesso à parte do sistema operacional? Seria muito simples danificar o funcionamento da máquina, alterando comunicação com hardware, deletando arquivos básicos, influenciando na troca de context das threads, etc.

Por esse motivo, o Windows utiliza dois modos de acesso ao processador: user mode e kernel mode. As aplicações de usuário rodam em user mode, e o sistema operacional em kernel mode.

Kernel mode garante ao sistema operacional acesso a todos as memórias e instruções de CPU. User mode garante que as aplicações de usuário não irão afetar (pelo menos não de forma tão fácil) as instruções contidas em kernel mode, garantindo assim maior estabilidade e segurança para o sistema operacional.

Voltando ao assunto...

Explicações dadas, WinDBG é um user mode e kernel mode debugger. Nosso foco aqui é apenas User Mode, que é exatamente onde se concentram as aplicações desenvolvidas que iremos testar, assim como as threads que iremos criar, os objetos que iremos armazenar, etc. Além disso, para discutir kernel mode teríamos que ter muiitttooo mais espaço no servidor :).

Símbolos

Quando construímos aplicativos ou bibliotecas, os arquivos principais são gerados com o que chamados de arquivos de símbolos (symbol files). Estes arquivos são básicos para debugar qualquer tipo de processo. Nestes posts, todos os símbolos estão em arquivos com extensão PDB (Program DataBase), contendo normalmente informações como o nome e endereço de variáveis globais, nome de métodos e suas assinaturas, nome e locais de variáveis locais, tipos, números das linhas do códigos, entre outras.

Por conter estas informações importantes para analisar qualquer tipo de exceção, o debugger precisa encontrar estes arquivos de símbolos para dar a informação mais precisa e adequada. Por isso, é extremamente importante que a configuração de símbolos esteja correta. No momento certo vou informar como fazer isso e como gerar arquivos PDB dos programas criados da melhor maneira possível.

Processos e threads

Outra definição muito importante que precisamos ter antes de partir para a mão na massa é sobre Processos.
Um processo nada mais é do que um ambiente (armazenando recursos) para a execução de um programa.

Mais especificamente, um processo contém as seguintes informações:

  • Um private virtual address space, que é uma série de endereços virtuais de memória que o processo pode usar;
  • Um executável, representando o código inicial mapeado para o endereço virtual do processo;
  • Uma lista de recursos do sistema, acessíveis a todas a threads do processo;
  • Um contexto de segurança chamado access token que identifica o usuário, grupos de segurança e privilégios;
  • Um identificador único chamado PID (process ID);
  • Pelo menos uma thread de execução.

Já as threads (ou segmenos, como alguns livros traduzem, argh!) são as entidades que o Windows agenda para execução. Como citado acima, os processos precisam ter pelo menos uma thread de execução. Do contrário, os processos não irão executar nada!

Uma thread inclui os componentes listados abaixo:

  • Uma série de registros representando o estado no processador;
  • Dois stacks, um para excução enquanto estiver em kernel mode e outro enquanto estiver em user mode;
  • Uma área de armazenagem local chamada de TLS (thread-local storage);
  • Um identificador único chamado Thread ID;
  • Algumas vezes contém o próprio contexto de segurança, principalmente em aplicações multithreaded.

Todas essas informações, agrupadas, formam o thread context.

Para a introdução já está de bom tamanho.
Instale o WinDBG e aguarde o próximo post da série, onde apresentarei a ferramenta e mostrarei um exemplo simples de debugging.

Espero que tenha gostado. Até lá!

-- André

No Comments