Ponto V!

Home C/C++ Visual C++ Como usar o Depurador do Visual C++
Bruno Crivelari Sanches
Como usar o Depurador do Visual C++Imprimir
Escrito por Bruno Crivelari Sanches

Este artigo visa mostrar a programadores iniciantes (e aquele não tão iniciantes assim) as técnicas básicas para se usar a ferramenta de debug do Visual C++ e como entender esta para que possa ser usada na investigação de problemas. Apesar de não existir uma interface padrão para debug de código e existirem inúmeras ferramentas com os mais variados recursos, os conceitos em geral são os mesmos, assim acredito que um desenvolvedor que utilize outra IDE possa tirar proveito de algumas técnicas mostradas aqui.

O termo “debug” surgiu nos primórdios da computação quando um dos computadores “gigantes” da época parou de funcionar devido a um inseto (segundo dizem uma mariposa) que se enfiou no meio de algumas válvulas, assim toda vez que o computador parava de funcionar alguém dizia algo como “vamos tirar os insetos”, surgindo assim o termo “debug” em inglês. Apesar da existência de discussões quanto a termo ter tido origem graças aos computadores, esta é a história mais conhecida e famosa a respeito da origem desse nome.

A operação de depuração ou debugging consiste na busca por erros em um software e sua correção, esta é uma tarefa na maioria das vezes complicadas e é bem raro (se não impossível) encontrar um desenvolvedor que nunca tenho perdido horas ou dias devido a um único bug. Para tornar esta tarefa mais simples foram sendo desenvolvidas inúmeras ferramentas para auxiliar nesta tarefa, que geralmente são apenas chamadas de ferramenta de Debug ou apenas debugger ou depurador em português.

Conhecendo o Depurador

Os depuradores em geral permitem que o desenvolvedor execute seu software linha a linha, controlando toda a execução, assim o desenvolvedor pode acompanhar o comportamento do seu software passo a passo e vendo o que ocorre com as variáveis durante a execução de cada instrução. Além disso, o depurador permite ao desenvolvedor modificar valor de variáveis, inserir pontos de paradas (breakpoints) e em alguns casos, até mesmo alterar o código sendo executado.

Vamos observar a figura abaixo e aprender um pouco mais sobre o depurador do visual:

Depurador do Visual C++

Na imagem acima temos o depurador do Visual sendo executado e alguns realces que inseri para destacar as principais funcionalidades.

Na parte superior temos os controles básicos do depurador, que permitem iniciar a execução, interrompe-la, executar uma linha, uma função, etc. Em seguida temos duas setas vermelhas, a primeira aponta para uma pequena seta amarela, esta indica o ponto de execução do programa, nesse caso o programa esta parado antes de executar a linha “b = a;”. Na próxima seta temos um ponto de parada, representado por uma “bola” vermelha, isso quer dizer que mesmo que você mande o programa continuar uma execução, ele vai parar toda vez que atingir um ponto de parada.

Na parte de baixo temos duas seções, na esquerda temos a janela “autos”, essa janela contém a lista de variáveis acessadas recentemente pelo programa, nesse caso a e b, note que seus respectivos valores são mostrados. Neste mesma região temos na parte de baixo outras janelas, sendo a “Locals” responsável por mostrar as variáveis locais do método em execução, “threads” onde podemos visualizar todas as threads do programa, “modules” são todos os módulos que o programa carregou e a janela “Watch” que nos permite inserir qualquer variável ou expressão para que possamos monitorar seus valores.

Do lado direito temos a pilha de chamadas (Call Stack), que nos mostra a função sendo executada atualmente (função inverte) e quais funções foram sendo chamadas até que se chegasse em inverte. Nesse caso podemos ver que inverte foi chamada por main, que foi chamada por __tmainCRTStartup, que foi chamada pelo kernel do Windows. A janela ainda mostra outras chamadas anteriores a esta, mas estas são de fora do nosso programa e não nos interessam nos problemas que vamos estudar.

Depurando um Programa

Para aprender as funções básicas do depurador iremos usar o programa listado abaixo:

#include <stdio.h>
#include <string.h>

struct Dados
{
    char nome[6];
    char apelido[6];
};

void preenche(Dados &data)
{    
    strcpy(data.apelido, "Pinda");
    strcpy(data.nome, "Pindamonhangaba");
}

int main(int, char **)
{
    Dados data;

    preenche(data);

    return 0;
}

Programa parado na primeira linha de códigoPara isso crie um novo projeto “console” e entre com o código acima. Após fazer isso pode-se ligar o depurador pressionando a tecla F5 (ou acessando o menu: Debug –> Start Debugging). Após pressionar o F5 a principio quase nada ocorre, o programa vai ser compilador e executado, depois o visual volta ao estado inicial. Isso ocorre porque o programa simplesmente terminou a execução e o que precisamos fazer é dizer ao depurador que pare a execução para que possamos analisar o programa. Para fazer isso, posicione o cursor do mouse na primeira linha da função main, que deve conter a declaração: “Dados data;” e em seguida pressione a tecla F9 (ou acesse o menu “Debug –> Toggle Breakpoint”), assim deve surgir um circulo vermelho a esquerda da linha, isso indica, como já vimos, um ponto de parada. Pressionando F5 novamente o programa irá iniciar a execução, mas será imediatamente interrompido, note que as telas do visual se modificaram e temos a seta amarela posicionada a esquerda da linha onde inserimos o ponto de parada.

Pode ocorrer que o ponto de parada se desloque algumas linhas acima ou abaixo da linha onde ele foi colocado, isso ocorre quando na linha onde ele é inserido não existe código a ser executado, mas não é nada que afete o processo.

Mensagem de aviso sobre dados corrompidosAgora vamos executar uma função do programa, para isso pressione F10 (ou acesse no menu “Debug –> Step Out”), assim que a ação for feita, a setinha amarela deve saltar para a próxima linha, isso indica que a instrução foi executada, continue pressionando F10 até o final do programa, caso no final surja uma mensagem sobre memória corrompida, simplesmente clique em “Continue”, isso ocorre devido aos bugs que foram inseridos no programa.

Corrigindo Bugs

Vamos usar o depurador para corrigir o erro de corrupção de memória, para isso, pressione novamente a tecla F5 para iniciar o depurador (caso o programa ainda esteja rodando, pode-se pressionar CTRL + F5 para encerrar sua execução). O programa deve estar novamente parado antes da função preenche, então agora pressione F11 ao invés de F10 (ou então acesse “Debug –> Step Into”), este comando é similar ao anterior, mas ao invés de “saltar” as funções, este entra dentro delas (dai o nome “into”), neste momento a seta amarela deve saltar para a inicio da função preenche e pressionando F10 novamente ela deve parar exatamente no primeiro strcpy.

Agora observe na parte de baixo da tela a janela “Autos”, deve ser possível visualizar a variável local “data”, note que existe um sinal de “+” ao lado do nome dela, isso ocorre pois estamos lidando com uma struct, clicando neste sinal, podemos visualizar os membros da struct, como cada membro é um array, o depurador permite que cada membro seja expandindo, tornando-se possível visualizar cada elemento do array e também é possível modificar os valores destes.

Depurando a função preenche

Pressionando-se F10 novamente a função strcpy vai ser executada, observe como o valor de apelido foi atualizado na janela auto, além de que o depurador mudou a cor desta variável para vermelho de forma a indicar ao programador que seu valor foi alterado na ultima linha de código que foi executada. Agora se observarmos já podemos começar a entender o problema: ambas variáveis possuem apenas 6 caracteres de tamanho, observe na janela auto como data.nome possui alguns caracteres de lixo (pois não foi inicializada) e logo em seguida temos o valor de data.apelido, pois o depurador exibe uma string como qualquer outra rotina em C iria exibir: imprimindo caracteres até encontrar o valor zero.valores de data

Se pressionarmos F10 novamente vamos ver que nome é atualizado e apelido acaba sendo afetado, pois a string copiada é grande demais e acaba invadindo o espaço da próxima e para piorar a situação ela acaba ocupando todo o espaço de data.apelido e mais alguns caracteres, isso acaba por afetar a pilha do programa e temos a explicação da mensagem de erro que vimos anteriormente:

valores invalidos de data

Este é um exemplo simples de buffer overflow e podemos visualizar a sua ocorrência de maneira bem simples com o uso do depurador, para corrigi-lo devemos expandir o tamanho dos arrays ou então usar strings de tamanho apropriado (o ideal mesmo seria não utilizar as funções de strings do C e partir para algo mais seguro, como a classe std::string).

Pontos de Parada com Condições

É comum em certos programas um bug ser causado apenas com determinado valor e depois de o programa já ter executado um determinado trecho de código varias vezes. Nesse caso, se colocarmos um ponto de parada no código, o programa vai ser interrompido a todo momento e o desenvolvedor vai ter que ficar re-iniciando sua execução até que o programa chegue num valor desejado. Um caso comum seria um loop de jogo onde apenas quando a energia de um inimigo atinge certo valor temos um bug.

Para simplificar o trabalho temos os pontos de paradas por condições, nesse tipo de ponto de parada podemos definir uma condição para o ponto seja considerado, enquanto a condição não ocorrer o programa continua a execução. Vamos então depurar o exemplo abaixo:

#include <stdio.h>

int main(int, char **)
{
    int vec[5];

    for(int i = 0;i < 1024 * 1024;++i)
    {        
        vec[i] = 0;

        printf("%d = %d\n", i, vec[i]);
    }

    return 0;
}

O problema no código é bem visível, mas para não complicar o exemplo optei por usar um código simples para focarmos apenas na operação de depuração. Pressionando F5 o programa vai executar e deve ser interrompido devido a um acesso ilegal de memória, pois esta sendo feito um acesso absurdo no array. Nos meus testes o erro ocorre no índice 158(mas isso pode variar), agora imagine que queiramos investigar o porque deste valor estar sendo usado, uma forma de fazer isso é ir pressionando F10 centenas de vezes até o loop atingir o valor 158, ou podemos colocar um ponto de parada com condição, para isso clique com o mouse na linha que contém “vec[i] = 0”, pressione F9 para que um ponto de parada seja criado.

Agora clique com o botão direito do mouse na linha do ponto de parada e selecione: “Breakpoint –> Condition”, na janela que se abrir digite: “i == 158” (use aqui o valor que o programa apresentou o erro no seu ambiente). Clique em Ok e pressione F5 para que o programa inicie a execução, se tudo estiver correto o programa vai ser interrompido e o valor de i será exatamente 158, isso permite ao desenvolvedor economizar um tempo precioso na investigação de problemas.

configurando a condição de um ponto de parada

Perceba que a condição pode ser qualquer tipo de expressão que seja valida na linha do código, assim pode-se usar para o teste o valor de qualquer variável acessível a partir desta linha e ainda combinar os operadores lógicos do C/C++.

Pontos de Parada em Dados

O ponto de parada em dados é bem útil para quando temos um programa grande e por um motivo qualquer precisamos descobrir o que alterou o valor de uma determinada variável, mais precisamente: um endereço de memória. Isso pode ocorrer em um jogo onde por algum motivo obscuro a energia do jogador sempre é modificada para um determinado valor, apesar do problema parecer ser fácil de resolver com uma busca no código por pontos onde a energia é alterada, isso nem sempre é viável em uma grande base de código, ainda mais quando os nomes envolvidos são nomes comuns no código.

Uma forma de se detectar e se resolver esse problema de uma maneira simples é inserindo um ponto de parada que interrompa o programa sempre que a variável for afetada, assim ao invés de especificarmos uma linha de código, vamos especificar um endereço de memória, vamos usar como exemplo o programa a seguir:

#include <stdio.h>
#include <stdlib.h>

void atualiza(int *vec)
{
    int index = rand() % 128;

    vec[index] = 0;
}

int main(int, char **)
{
    int vec[128];

    for(int i = 0; i < 128; ++i)
        vec[i] = 1;

    int count = 0;
    while(vec[64])
    {
        ++count;
        atualiza(vec);
    }

    return 0;
}

Não vem ao caso discutir a utilidade de um programa estupendo como esse, mas nosso objetivo aqui é descobrir quando a função atualiza modifica o valor de vec[64]. Para tal, coloque um ponto de parada na primeira linha da função main (para os esquecidos: posicione o cursor na linha e pressione F9). Aperte F5 para iniciar a execução, assim que o programa for interrompido, selecione: “Debug –> New Breakpoint –> New Data Breakpoint”, na janela que se abrir, digite: &vec[64] ou seja, o endereço da posição 64 de vec. Clique em Ok e pressione F5.

Criando um ponto de parada de dados

Pressionado F5 o programa vai continuar a execução e deve parar no primeiro loop e exibir uma caixa de diálogo avisando que um valor foi alterado naquele endereço especificado, pressionando F5 novamente o programa será interrompido dentro da função atualiza, no exato momento em que o valor de vec[64] foi alterado, assim o desenvolvedor pode rapidamente identificar alterações em determinadas variáveis.

Dicas do Depurador e Ponteiros

O depurador do Visual também utiliza alguns valores especiais em variáveis para tentar auxiliar o desenvolvedor, para explorarmos estes valores vamos criar mais um magnifico programa de 1001 utilidades:

#include <stdio.h>

struct Node
{
    int dado;

    Node *pProx;
};

int main(int, char **)
{
    Node *p = new Node;

    p->dado = 1;
    p->pProx = new Node;
    p->pProx->dado = 2;
    p->pProx->pProx = NULL;

    delete p->pProx;
    delete p;

    return 0;
}

Variável não inicializadaInsira então um ponto de parada na primeira linha do programa (onde é feito o primeiro “new node”) e pressione F5, quando a execução for interrompida, posicione o cursor do mouse sobre a variável *p, deve surgir um pequeno popup próximo ao cursor, essa janela nos mostra o valor atual de p ( o mesmo valor pode ser visto na janela auto, na parte de baixo da tela). Perceba que o valor em hexadecimal 0xCCCCCCCC não esta ai por acaso, o depurador e o compilador do visual que cuidam de inserir esse valor em variáveis não inicializadas, dessa forma se você perceber uma variável causando problemas e seu valor for 0xCCCCCCCC é bem provável que esta variável nunca tenha sido inicializada.

É importante frisar que utilizei o termo “provável”, pois nada impede que alguma variável do seu programa venha possuir este valor, as chances são pequenas, mas isso pode ocorrer.

Agora pressione F10 para que o “new” seja executado e posicione novamente o mouse sobre p, perceba que o valor de p pode ser expandido clicando-se no botão “+”, vemos que o valor de pProx e de dado é de 0xCDCDCDCD, este é um valor usado em memória alocada, isso indica ao desenvolvedor que ele pode estar lidando com um bloco de memória que foi alocado, mas nunca foi inicializado. Aqui novamente temos a possibilidade deste valor ser algo valido para o programa em questão, mas essa informação pode ser muito útil para se investigar um problema.Memória alocada não inicializada

Por fim, posicione o cursor de texto sobre o “return” contido na ultima linha do programa e pressione CTRL + F10, isso fará com que o programa continue a execução até que esta chegue onde se encontra o cursor (como se fosse criado um ponto de parada temporário), vamos então novamente inspecionar o valor de p posicionando o mouse sobre ele. Expandindo o valor de p podemos ver que os valores de dado e de pProx são de 0xFEEEFEEE, este valor é usado para indicar que p aponta para uma região de memória que foi alocada dinamicamente, mas já foi liberada (com uso de free ou delete), assim fica simples encontrar certos tipos de dangling pointers.

Dangling pointer

Estes valores especiais são gerados em tempo de execução por código inserido pelo compilador e pelas versões debug da biblioteca padrão C/C++ que são incluídos com o Visual, dessa forma, apenas um programa compilado em modo debug vai exibir tais valores, pois em modo release esses valores não são gerados e como consequência variáveis e memória não inicializadas vão passar a obter valores totalmente aleatórios, como é o esperado de um programa C/C++.

Conclusão

Neste artigo foram mostrados os principais comandos a se usar na depuração de programas C/C++ com o Visual C++, existem mais comandos na ferramenta além de inúmeras técnicas que foram sendo criadas e descobertas, por isso fica a sugestão ao leitor de também consultar a documentação da ferramenta em busca de novos comandos além é claro de explorar a ferramenta.

Por fim a depuração de código depende muito da experiência do desenvolvedor, tanto como programador assim como seu conhecimento a respeito do código que esta sendo depurado, sendo que nos problemas mais complexos a depuração passa a depender muito da capacidade do programador de tentar entender o problema e conseguir isolar as possíveis causas e ao meu ver um bom programador além de saber como escrever um bom código também deve dominar a arte da depuração.


Comentários (26)
  • Douglas Púppio
    avatar

    Boa Bruno!! Bom artigo!

    Estou com o visual studio express 2010...fiz um jogo usando o Code::Blocks e achei este muito interessante e sufiiente para o que precisava. Mas quero usar o visual no proximo game (Ogre3D/C++) e este artigo foi bem interessante.

    vlw!!

    abraços

    >D

  • Bruno Crivelari Sanches
    avatar

    Obrigado Douglas!

    Bom, se a questão for apenas o depurador, o Code::Blocks também possui e talvez seja o suficiente para as suas necessidades.

  • jeferson
    avatar

    otimo post. a proposito como assim pindamonhangaba, você é daqui tambem?

  • Bruno Crivelari Sanches
    avatar

    Obrigado Jeferson.

    Não sou de Pinda, só precisava de um nome grande e foi o primeiro que me veio na cabeça :cheer:

    Mas não estou longe de Pinda, moro em Cruzeiro.

  • jeferson
    avatar

    ah claro, eu sabia q eu seria o unico a me interessar por isso nesse fim de mundo, mas cruzeiro e pinda nao são tao longe assim.. e esse tutorial foi muito bom, principalmente pra mim que to começando no c++

  • Bruno Crivelari Sanches
    avatar

    Que bom que o tutorial foi proveitoso! :)

  • Bruno Romano
    avatar

    Opa nao li todo o tutorial, mas estão de parabens com o trabalho de vocês, e não sei se você chegou a comentar, pois nao li todo o artigo, mas o visual studio tem uma feature que eu acho simplismente fantastica, que vc pode arrastar e soltar a "flechinha amarela", ajudando e MUITO na hora de debugar, aqueles famosos erros que vc precisa recompilar e reexecutar o código para entrar no if, ou voltar a ter uma situação X no for, ele resolve simplismente retirando o ponto de execução de um lugar e colocando em outro, para mim é o motivo de nao querer usar nenhuma outra IDE alem desta. =)

  • Bruno Crivelari Sanches
    avatar

    Essa de arrastar a setinha eu não conhecia, muito bom! Eu costumava fazer isso usando uma janela de watch e alterando o valor das variáveis envolvidas na condição!

    No caso de recompilar, você pode alterar o código com o debug rodando que ele re-compila na hora e continua a execução sem problemas.

    Valeu Bruno

  • Bruno
    avatar

    Mas no caso quando vc alterava as váriaveis vc não podia voltar atras de um IF que ja foi neh ? Ou vc alterava registrador ? (não sei se é possivel isso)

    que pelo watch vc pode vizualizar registrador e até altera-lo, mas ai é bom ir com calma, só mexi no eax até hoje, hehe.

    Se for listar os beneficios que o visual studio tem da alguns posts, o VC é muito completo e cheio de engenhocas, como esse da flechinha amarela, visualizar memória, o watch, clicar nas threads e conseguir depurar outra thread, carregar os dump's de uma aplicação que deu crash, error lookup que vc pode pegar um código de erro do sistema e ver o que ele significa, o Remote Debug =O outra ferramenta de tirar o chapéu.... enfim essas são algumas das que eu usava e achava ótimas.

    Vc poderia listar tudo que sabe sobre essas features do VC e o pessoal que sabe vai complementando vai que descobrimos outras coisas muito legais e que ajudam muito ?

    Se você fizer mais sobre isso eu crio alguns sobre como utilizar o Eclipse junto com o CDT com exemplos e utilitários que já usei, e mando para vc postar ai junto se você quiser.

  • Bruno Crivelari Sanches
    avatar

    Olá Bruno,

    eu aos poucos vou adicionando artigos sobre o Visual, geralmente sobre as ferramentas mais usadas, ele é grande e complexo demais para eu documentar tudo aqui e duvido que eu conheça tanta coisa assim :).

    sobre Eclipse e CDT, se quiser escrever algo, mande um email (ali na pagina de contatos) e podemos discutir o assunto.

    Obrigado

  • Ralph
    avatar

    devido a forma descritiva, e ilustrada, como V.S.a elaborou este trabalho vai uma nota 9. Salvei o seu trabalho para novas e apre-
    ciadas consultas. Se não for por direito, com certeza de fato V.s.a
    e um exelente professor. Parabens.

  • Bruno Crivelari Sanches
    avatar

    Muito obrigado Ralph!

  • Renata
    avatar

    Olá!

    Por favor veja se pode me ajuadar. A janela Autos que exibe o valor das variáveis durante um debugg passo-a-passo (com F11) não aparece!

    Já procurei essa janela em todos os menus e não achei.

    Você poderia me dizer como eu faço com que ela reaparessa???

  • Bruno Crivelari Sanches
    avatar

    Com o debug rodando (ou melhor, parado num breakpoint), selecione no menu: "Debug -> Windows -> Auto" ou pressione CTRL + ALT + V e depois A

    T+

  • Renata
    avatar

    ooops, desculpa: é reapareça!!!

  • nico  - debug
    avatar

    muito bom! obrigado! continue simples indo direto ao ponto com exemplos praticos.

  • Bruno Crivelari Sanches
    avatar

    Valeu Nico!

  • Renata  - Obrigada!
    avatar

    Valeu!!! Muito Obrigada!!!!!!!

  • Bruno Crivelari Sanches
    avatar

    Obrigado Renata!

  • André Constantino
    avatar

    Mto bom Bruno...otima iniciativa
    me ajudou e me ajudara mto...

    vlw

  • Bruno Crivelari Sanches
    avatar

    Que bom André! Obrigado pelos elogios!

  • Anônimo
    avatar

    ola, fiz esse codigo , ele esta correto pois exita a MSG certinho ,mas tambem exibe um caracteres estranhos.
    eu uso o visual c++ 2010, testei no g++ e no devc++ e tudo correu normal , so no visual que sai esquisito. compila ele e ve o que esta errado .obrigado.


    #include
    #define space 32
    using namespace std;
    int main ()
    {
    char msg[9],aux;
    int i;
    msg[0] = space ;
    msg[1] = 'u';
    msg[2] = 'o';
    msg[3] = 't';
    msg[4] = 'r';
    msg[5] = 'e';
    msg[6] = 'c';
    msg[7] = 'a';
    msg[8] = '!';
    for(i = 2; i

  • Vinícius Godoy de Mendonça
    avatar

    Poste o código no pastebin.

  • daniel  - help
    avatar

    ola codigo postado.

    http://pastebin.com/e9wugqkY

  • ViniGodoy  - Problema
    avatar

    Seu problema é que as Strings em C devem ser terminadas com \0. No seu caso, você gerou um array com uma string sem essa terminação. Então, o compilador não sabe onde parar de exibir a String.

    No caso do DevBlocks (que você nem deveria usar) e do G++, o que pode ter acontecido é de você ter dado sorte e estar numa área de memória zerada. Porém, esse tipo de problema se manifestaria no futuro, e poderia até acabar com sua aplicação "executando uma operação ilegal e sendo fechada".

    Para corrigir é simples. Declare o array com 10 posições:
    char msg[10]

    E atribua a posição 9 o \0:
    msg[9] = '\0';

    Como seu código mexe nessa posição, o código já vai funcionar.

Escrever um comentário
Your Contact Details:
Gravatar enabled
Comentário:
[b] [i] [u] [url] [quote] [code] [img]   
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch::(:shock:
:X:side::):P:unsure::woohoo::huh::whistle:;):S:!::?::idea::arrow:
Security
Por favor coloque o código anti-spam que você lê na imagem.
LAST_UPDATED2  

Busca

Linguagens

Twitter