5
Introdução
Ao escrever um programa que esteja um pouco além do tradicional “Hello World!” o desenvolvedor precisa saber utilizar as ferramentas de depuração disponíveis no sistema. Sem elas, depurar um aplicativo que não seja básico é algo trabalhoso. Se programas básicos são trabalhosos de depurar, programas que utilizam apontadores podem tornar-se um verdadeiro suplício.
No FreeBSD estão disponíveis várias ferramentas de depuração, das quais, algumas serão abordadas. São elas DDD, Valgrind e o recurso de cobertura de código do GCC.
Depurando programas com o DDD
O Data Display Debugger, ou DDD, é uma interface para o gdb, tornando o uso deste último bem mais amigável para o usuário. Para usar o DDD obviamente é necessário que o mesmo esteja presente no sistema. Para instalá-lo o usuário pode utilizar o ports do FreeBSD. Assim, pode-se executar os seguintes comandos para realizar a instalação:
# (cd /usr/ports/devel/ddd && make install clean)
Escrevendo o programa exemplo
Depois de instalar o DDD escreva o seguinte programa exemplo em C que implementa uma pilha, para em seguida depurá-lo.
#include <stdio.h>
#include <stdlib.h>
typedef struct no_s{
int payload;
struct no_s *next;
}no_t;
void push(no_t **pilha, int elemento){
no_t *aux;
aux = malloc(sizeof(no_t));
aux->payload = elemento;
aux->next = *pilha;
*pilha = aux;
}
int pop(no_t **pilha){
no_t *aux;
aux = *pilha;
*pilha = (*pilha)->next;
return aux->payload;
}
int main(int argc, char **argv){
no_t *pilha;
int elemento;
printf(“Informe um elemento maior que 0 para empilhar e menor que 0 para desempilhar todos\n”);
do{
scanf(“%u”, &elemento);
if(elemento >= 0){
push(&pilha, elemento);
}
}while(elemento >=0);
printf(“============================\n”);
while(pilha != NULL){
printf(“%u\n”, pop(&pilha));
}
return 0;
}
Salve este programa como pilha.c e compile o mesmo com o comando:
$ gcc -Wall -o pilha pilha.c
Atenção! Ao copiar o texto do browser, as aspas que o browser exibe não são as reconhecidas pelo compilador C, então você deve substitui-las manualmente.
O programa deve compilar sem nenhum erro.
Execute o programa e forneça as entradas 1, 2, 3, -1. O programa imprime as saídas 3, 2 e 1 e depois apresenta um erro.
$ ./pilha
Informe um elemento maior que 0 para empilhar e menor que 0 para desempilhar todos
1
2
3
-1
============================
3
2
1
Segmentation fault: 11 (imagem do núcleo gravada)
Para encontrar a causa da falha de segmento compile novamente o programa adicionando o parâmetro -g para que o GCC adicione os símbolos de depuração no executável.
$ gcc -Wall -g -o pilha pilha.c
Execute novamente o programa com os mesmos parâmetros de entrada: 1, 2, 3 e -1.
O programa termina novamente com um erro, sendo que dessa vez uma imagem de memória com os símbolos necessários para depuração foi criada. O arquivo com a imagem é o pilha.core.
Depurando o arquivo pilha.core
Execute o ddd informando o programa que será depurado.
$ ddd pilha
Em seguida abra o arquivo pilha.core utilizando as opções de menu “File->Open Core Dump…”
O DDD mostra exatamente a linha que causou o problema adicionando uma seta vermelha à mesma. Uma outra informação bastante útil é a pilha de chamadas de rotinas. Esta mostra o caminho que o programa seguiu até o momento em que executou a operação ilegal. Para visualizar a pilha de chamadas usa-se a opção de menu “Status->Backtrace…”.
A janela exibe a sequência de chamadas de todas as rotinas até o ponto em que o problema ocorreu, inclusive com a linha da rotina .
Adicionando um ponto de parada
Para adicionar o ponto de parada procure a linha com a seta vermelha do lado dela. Clique com o botão direito e selecione “Set Breakpoint”. Execute novamente o programa teclando em F3, sendo que o cursor deve estar na janela de mensagens do DDD.
Dica: Eu gosto de executar o programa com a seguinte opção marcada: “Program->Run in Execution Window”. Dessa forma o ddd sempre executa o programa em um terminal próprio impedindo a poluição que normalmente ocorre quando a entrada e saída de dados é feita diretamente na janela.
Durante a execução, o DDD vai pará-lo exatamente no ponto onde está configurado o breakpoint. Neste, pode-se iniciar a execução passo-a-passo. Para executar o programa linha por linha sem entrar nas chamadas de sub-rotina usa-se a tecla F5, para “entrar” nas chamadas de sub-rotina a tecla a ser usada é a F6. Para continuar a execução normalmente usa-se a tecla F9.
Visualizando o conteúdo de variáveis
Executar o programa passo a passo observando os valores das variáveis pode ajudar muito a encontrar o erro. Para isto, neste programa, adiciona-se um ponto de parada na linha correspondente ao segundo while (linha 40). Para isso clique com o botão direito e selecione “Set Breakpoint” na inha 40 e remova o da linha 22 clicando com o botão direito na mesma e escolhendo a opção de menu “Disable Breakpoint” para desabilitar ou “Delete Breakpoint” para remover. Execute novamente programa pressionando F3 (com o cursor na janela de mensagens do DDD) e entre com os valores 1, 2, 3, -1. O programa deve parar no local da placa de “STOP”.
Muito bem, para examinar o estado da lista encadeada utilizada na implementação da pilha, clique com o botão direito sobre a variável “pilha” e selecione “Display pilha”. A variável deve ser exibida na área destinada a exibição de variáveis. Nesta região, clique novamente com o botão direito sobre o quadro onde é exibida a variável “pilha” e selecione “Display *()”. Clique agora sobre o campo “next” do quadro apontado por “pilha” e selecione “Display *()”. Repita a operação sobre os quadros que vão surgindo inclusive para o quadro com o payload = 1. Sua tela deve estar como na imagem abaixo:
Observe que o último campo “next” aponta para uma posição inválida diferente de NULL (0x0). Este é o erro então. O laço iterage até que o valor NULL seja encontrado, mas este nunca o é porque a variável não foi inicializada como tal. Para consertar o bug procure a linha 31 onde a variável “*pilha” é declara e substitua a linha por:
no_t *pilha=NULL;
Compile o programa. Abra-o novamente no DDD usando a opção “File->Open Program…” e execute-o com F3, informando os mesmos valores de entrada. Quando o programa parar na placa “STOP” aperte F9 para continuar a execução. O erro não deverá ocorrer mais.
Conclusões
Neste post foi tratado o básico da depuração de um programa C utilizando o software de interface gráfica para o gdb, o DDD. Com este, foi possível encontrar o erro que fazia com o que o programa pilha.c abortasse. Além disso, visualizou-se a lista encadeada utilizada na representação da pilha permitindo a fácil compreensão de como os dados estavam organizados na memória. Isto é particularmente útil quando se está trabalhando com estruturas de dados que utilizam listas encadeadas.