Publicidade

gdb na Prática em C

Percorra uma sessão completa de depuração em um programa com bug.

Por que gdb

Depuração com printf funciona, mas o gdb é mais rápido quando você precisa:

Configuração (flag -g)

Sempre compile com informações de depuração habilitadas primeiro.
$ gcc -g -O0 main.c -o app # -g embute números de linha & nomes de variáveis $ gdb ./app (gdb)
Por que -O0? Com otimizações ligadas, variáveis podem desaparecer em registradores e linhas do código-fonte podem sumir, então step e print não se comportam como o esperado. Use sempre -O0 durante a depuração.

Programa de exemplo com bug

// sum.c: deveria calcular 1+2+...+N (tem um bug)
#include <stdio.h>

int sum_to(int n) {
    int s = 0;
    for (int i = 1; i < n; i++) {   // BUG: deveria ser i <= n
        s += i;
    }
    return s;
}

int main(void) {
    int r = sum_to(10);
    printf("sum = %d\n", r);   // esperado 55, obtido 45
    return 0;
}

Sessão básica: break / run / step / print

$ gcc -g sum.c -o sum $ gdb ./sum (gdb) break sum_to # breakpoint no início da função Breakpoint 1 at 0x1149: file sum.c, line 5. (gdb) run # inicia o programa Breakpoint 1, sum_to (n=10) at sum.c:5 5 int s = 0; (gdb) next # avança uma linha (sem entrar em chamadas) 6 for (int i = 1; i < n; i++) { (gdb) print n $1 = 10 (gdb) print i No symbol "i" in current context. # ainda não foi declarada (gdb) next 7 s += i; (gdb) print i $2 = 1 # agora i=1 (gdb) continue sum = 45 # errado! esperado 55

Breakpoints condicionais

(gdb) break sum.c:7 if i == 5 (gdb) run Breakpoint 2, sum_to at sum.c:7 (gdb) print s $3 = 10 # 0+1+2+3+4 = 10 ✓
step vs. next: step (s) entra nas funções chamadas; next (n) trata uma chamada como uma única linha.

Impressão automática com display

(gdb) display i # imprime a cada parada (gdb) display s (gdb) next 1: i = 3 2: s = 3

Stack trace (bt / frame)

Quando um bug acontece bem fundo em uma cadeia de chamadas, você vai querer saber quem chamou a função atual.
(gdb) backtrace # ou 'bt' #0 sum_to (n=10) at sum.c:7 #1 main () at sum.c:13 (gdb) frame 1 # pula para o frame do main #1 main () at sum.c:13 13 int r = sum_to(10); (gdb) print r $4 = 0 # ainda 0, a chamada não terminou (gdb) frame 0 # volta para o frame interno
Depurando recursão: bt mostra todos os níveis; pule entre eles com frame N e inspecione cada conjunto de argumentos.

Watchpoints (para quando uma variável muda)

Perfeito para bugs do tipo "de algum jeito esta variável ficou com o valor errado".
(gdb) break main (gdb) run (gdb) watch r # observa escritas em r Hardware watchpoint 2: r (gdb) continue Hardware watchpoint 2: r Old value = 0 New value = 45 main () at sum.c:13
Escopo: Um watchpoint em uma variável local é liberado quando sua função retorna. Para globais ou memória no heap, use watch *ptr ou watch -location.

Depurando um segfault

Quando executado sob o gdb, um crash para exatamente na instrução problemática.
// crash.c
#include <stdio.h>
#include <string.h>

int main(void) {
    char *p = NULL;
    strcpy(p, "hello");    // escrita através de NULL → crash
    return 0;
}
$ gcc -g crash.c -o crash $ gdb ./crash (gdb) run Program received signal SIGSEGV, Segmentation fault. __strcpy_avx2 () at .. # dentro da libc (gdb) bt #0 __strcpy_avx2 () from /lib/libc.so.6 #1 0x... in main () at crash.c:6 # ← nosso código (gdb) frame 1 #1 main () at crash.c:6 6 strcpy(p, "hello"); (gdb) print p $1 = 0x0 # NULL, confirmado
Post-mortem com core dumps: Habilite com ulimit -c unlimited; após o crash, rode gdb ./app core.

Resumo rápido

ComandoAbrev.Significado
break fn / file:linebDefine um breakpoint
info breakpointsi bLista todos
delete NdRemove o N-ésimo
run [args]rInicia o programa
continuecContinua até o próximo break
nextnPróxima linha (sem entrar em chamadas)
stepsPróxima linha (entra em chamadas)
finishfinExecuta até a função atual retornar
print exprpMostra um valor (p *p, p a[5], p arr@10)
display exprdispImprime automaticamente a cada parada
backtracebtMostra o stack trace
frame NfMuda para o frame N
watch expr-Para quando expr muda
listlMostra 10 linhas do código
set variable x=5set varModifica uma variável em execução
quitqSai do gdb

Modo TUI

$ gdb -tui ./app # painel de código em cima, prompt embaixo # alterne com Ctrl+X depois A
IDEs: VS Code e CLion dirigem o gdb por baixo dos panos. Depois que você ficar confortável no terminal, as GUIs deles aceleram ainda mais.

Desafios

Desafio 1: Corrigir sum_to
Use gdb para descobrir por que sum_to(10) retorna 45 em vez de 55, e corrija.
Desafio 2: Prática de watchpoint
Escreva um programa em que uma global int g = 0; é modificada por várias funções. Use watch g para observar cada mudança em ordem.
Desafio 3: Inspecione a recursão
Calcule fact(5) recursivamente, pare quando n==2 e percorra cada frame com frame N e print n para ver a pilha.
Desafio 4: Localize um segfault
Escreva um pequeno acesso a array fora dos limites, rode sob gdb e use bt e print i para encontrar o índice problemático.

Teste de Revisão

Confira seu entendimento desta aula.

Q1. Qual comando inicia a execução do programa no gdb?

run
go
exec

run (ou r) inicia o programa e o executa até o próximo breakpoint. Você pode passar argumentos como run arg1 arg2.

Q2. Qual comando define um breakpoint no início de main?

break main
stop main
watch main

break (ou b) para em uma localização específica, enquanto watch para quando o valor de uma variável muda.

Q3. Qual comando mostra o valor atual da variável x?

print x ou p x
show x
get x

print avalia uma expressão e exibe o resultado. Arrays e structs são expandidos para você inspecionar o conteúdo.