Debugging com GDB

1. Introdução ao GDB

O GDB (GNU Debugger) é uma ferramenta essencial para depuração de programas escritos em Linguagem C. Ele permite que você execute o código passo a passo, inspecione variáveis, analise a pilha de chamadas e identifique a origem de erros como segmentation faults e loops infinitos.

Para instalar o GDB em sistemas Linux:

sudo apt-get install gdb    # Debian/Ubuntu
sudo yum install gdb        # CentOS/RHEL

Verifique a instalação:

gdb --version

Para que o GDB funcione corretamente, compile seu programa com a flag -g, que adiciona informações de depuração ao executável:

gcc -g -o programa programa.c

2. Comandos Essenciais do GDB

Inicie o GDB carregando seu executável:

gdb ./programa

Dentro do GDB, os comandos básicos são:

(gdb) run          # Executa o programa
(gdb) break main   # Define breakpoint na função main
(gdb) next         # Avança para próxima linha (sem entrar em funções)
(gdb) step         # Avança para próxima linha (entrando em funções)
(gdb) continue     # Continua execução até próximo breakpoint
(gdb) print var    # Exibe valor de uma variável
(gdb) display var  # Exibe valor automaticamente a cada parada
(gdb) info locals  # Mostra todas as variáveis locais

Exemplo prático:

#include <stdio.h>

int main() {
    int a = 10, b = 20;
    int soma = a + b;
    printf("Soma: %d\n", soma);
    return 0;
}

Depuração:

(gdb) break 5
(gdb) run
(gdb) print a
$1 = 10
(gdb) next
(gdb) print soma
$2 = 30

3. Trabalhando com Breakpoints e Watchpoints

Breakpoints podem ser definidos por linha, função ou arquivo:

(gdb) break 10              # Linha 10 do arquivo atual
(gdb) break main            # Função main
(gdb) break programa.c:15   # Linha 15 do arquivo programa.c
(gdb) info breakpoints      # Lista breakpoints
(gdb) delete 1              # Remove breakpoint 1
(gdb) disable 2             # Desabilita breakpoint 2

Watchpoints monitoram variáveis:

(gdb) watch variavel        # Para quando variável for modificada
(gdb) rwatch variavel       # Para quando variável for lida

Exemplo com vetor:

#include <stdio.h>

int main() {
    int vetor[5] = {0};
    for (int i = 0; i < 5; i++) {
        vetor[i] = i * 2;
    }
    return 0;
}
(gdb) break main
(gdb) run
(gdb) watch vetor[2]
(gdb) continue

4. Análise de Pilha e Frame

Quando ocorre um erro, o comando backtrace mostra a sequência de chamadas de função:

#include <stdio.h>

void funcao2() {
    int x = 10 / 0;  // Erro proposital
}

void funcao1() {
    funcao2();
}

int main() {
    funcao1();
    return 0;
}

Depuração:

(gdb) run
Program received signal SIGFPE, Arithmetic exception.
(gdb) backtrace
#0  0x... in funcao2 () at programa.c:4
#1  0x... in funcao1 () at programa.c:8
#2  0x... in main () at programa.c:12
(gdb) frame 1
(gdb) info args
(gdb) up
(gdb) down

5. Depuração de Ponteiros e Memória

Para verificar endereços de memória:

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

int main() {
    int *ptr = malloc(sizeof(int));
    *ptr = 42;
    printf("%d\n", *ptr);
    free(ptr);
    return 0;
}
(gdb) break main
(gdb) run
(gdb) print ptr
$1 = (int *) 0x5555555592a0
(gdb) print &ptr
$2 = (int **) 0x7fffffffe3e0
(gdb) x/4x ptr    # Examina 4 words de memória no endereço de ptr
0x5555555592a0:  0x0000002a  0x00000000  0x00000000  0x00000000

Para debug de segmentation fault:

#include <stdio.h>

int main() {
    int *ptr = NULL;
    *ptr = 10;  // Segmentation fault
    return 0;
}
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
(gdb) backtrace
(gdb) info registers
(gdb) print ptr
$1 = (int *) 0x0

6. Técnicas Avançadas de Depuração

Breakpoints condicionais:

(gdb) break 15 if i == 5
(gdb) break funcao if valor > 100

Comandos automáticos em breakpoints:

(gdb) break 20
(gdb) commands
> print "Breakpoint atingido"
> print x
> continue
> end

Depuração de core dumps:

ulimit -c unlimited   # Permite core dumps
./programa            # Executa até crash
gdb ./programa core   # Analisa o core dump

7. Depuração de Recursão e Estruturas de Dados

Para funções recursivas:

#include <stdio.h>

int fatorial(int n) {
    if (n <= 1) return 1;
    return n * fatorial(n - 1);
}

int main() {
    printf("%d\n", fatorial(5));
    return 0;
}
(gdb) break fatorial if n == 3
(gdb) run
(gdb) backtrace
#0  fatorial (n=3) at programa.c:4
#1  fatorial (n=4) at programa.c:5
#2  fatorial (n=5) at programa.c:5
#3  main () at programa.c:9

Para listas encadeadas:

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

struct Node {
    int valor;
    struct Node *prox;
};

int main() {
    struct Node *head = malloc(sizeof(struct Node));
    head->valor = 1;
    head->prox = malloc(sizeof(struct Node));
    head->prox->valor = 2;
    head->prox->prox = NULL;
    return 0;
}
(gdb) print head
$1 = (struct Node *) 0x5555555592a0
(gdb) print head->valor
$2 = 1
(gdb) print head->prox->valor
$3 = 2

8. Dicas Finais e Fluxo de Trabalho

Crie um arquivo .gdbinit no diretório home para personalizar o GDB:

set print pretty on
set pagination off
set confirm off
define h
    info functions
end

Combine GDB com outras ferramentas:

valgrind --leak-check=full ./programa   # Detecta vazamentos de memória
strace -o saida.txt ./programa          # Monitora chamadas de sistema

Checklist prático:

  1. Compile com -g -O0 para evitar otimizações que dificultam a depuração
  2. Use breakpoints estratégicos antes de áreas suspeitas
  3. Verifique variáveis com print após cada operação crítica
  4. Analise o backtrace imediatamente após um crash
  5. Teste com entradas mínimas para isolar o problema
  6. Documente suas sessões com logs do GDB

Referências