C Depurar com gdb

O programa morreu com "Falha de segmentação" e não disse onde. Ou a média deu 8,00 quando deveria dar 8,33. Você pode encher o código de printf para descobrir o que está acontecendo, ou pode usar um depurador: um programa que roda o seu programa em câmera lenta, deixa você parar em qualquer linha e olhar o valor de cada variável. No Linux, o depurador clássico é o gdb (GNU Debugger, "depurador do GNU"). Nesta aula você vai usá-lo para achar a linha exata de um crash e para caçar um erro de conta, passo a passo.

Você vai aprender
  • Como preparar o programa para depuração: a opção -g do gcc (e por que sem -O2).
  • Como rodar dentro do gdb (gdb ./programa, run) e achar a linha de um crash com backtrace.
  • Como olhar variáveis com print, trocar de função com frame e acompanhar valores com display.
  • Como pausar o programa numa linha (break) e andar linha a linha (next e step).
  • Como seguir em frente (continue, finish) e sair (quit).
  • O atalho gdb -batch -ex run -ex bt ./programa, que transforma "Falha de segmentação" em "linha 19".

Todas as sessões desta aula são reais, feitas com o gdb 17.1 e o gcc 15 na máquina fictícia da Ana, na pasta ~/c/gdb. O gdb não funciona no editor do guia: ele precisa controlar um programa de verdade no seu sistema. Para praticar, instale-o (no Ubuntu e no Debian: sudo apt install gdb; veja Instalar no Linux). As caixas "Experimente" desta página servem para corrigir, no editor, os erros que o gdb encontrou.


A ideia: pausar o vídeo

Ver um programa rodar normalmente é como ver o vídeo de um gol em velocidade máxima: tudo acontece em milésimos de segundo, e você só vê o placar no fim. O depurador é o controle remoto:

No controle remotoNo gdb
Dar o playrun
Marcar um ponto para pausar sozinhobreak (um ponto de parada, em inglês breakpoint)
Avançar um quadronext / step
Dar zoom num detalheprint variável
Deixar um detalhe sempre na teladisplay variável
Continuar até o próximo ponto marcadocontinue
"Como chegamos aqui?"backtrace

1. Preparar o programa: -g

O executável que o gcc gera normalmente não guarda os nomes das variáveis nem os números das linhas: o processador não precisa disso para rodar. Para o depurador poder dizer "linha 19, variável a", compile com a opção -g, que acrescenta essas informações de depuração ao executável:

$ gcc -std=c23 -Wall -Wextra -g crash.c -o crash
gcc -std=c23 -Wall -Wextra
O comando padrão do guia (veja Compilar com gcc).
-g
"Guarde as informações de depuração": nomes de variáveis, de funções e a ligação entre cada pedaço do programa e a linha do .c de onde ele veio. O programa fica maior, mas roda igual.
crash.c -o crash
O arquivo de entrada e o nome do executável, como sempre.

E deixe as otimizações desligadas, que é o padrão do gcc (chamado -O0, "nível zero de otimização"). Com -O2, o compilador reorganiza, junta e apaga linhas, e o passo a passo fica confuso (seção 6). Resumo: para depurar, -g e sem -O2.


2. Achar a linha de um crash

Este programa procura alunos numa turma. A função buscar devolve um ponteiro para o aluno encontrado, ou NULL se não achar (veja Ponteiro Nulo e Structs e Ponteiros):

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

struct aluno {
    char nome[20];
    double nota;
};

struct aluno *buscar(struct aluno turma[], int n, const char *nome) {
    for (int i = 0; i < n; i++) {
        if (strcmp(turma[i].nome, nome) == 0) {
            return &turma[i];
        }
    }
    return NULL;
}

void mostrar(const struct aluno *a) {
    printf("%s: %.1f\n", a->nome, a->nota);
}

int main(void) {
    struct aluno turma[] = {{"Ana", 9.5}, {"Bruno", 7.0}};
    mostrar(buscar(turma, 2, "Ana"));
    mostrar(buscar(turma, 2, "Zeca"));
    printf("Fim.\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra -g crash.c -o crash
$ ./crash
Ana: 9.5
Falha de segmentação     (imagem do núcleo gravada) ./crash
$ echo $?
139

O programa mostrou a Ana e morreu, sem dizer onde (sobre a falha de segmentação e o código 139, veja Comportamento Indefinido). O Fim. nunca apareceu. Vamos perguntar ao gdb.

Rodar dentro do gdb: run

$ gdb -q ./crash
Reading symbols from ./crash...
(gdb) run
Starting program: /home/ana/c/gdb/crash
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/x86_64-linux-gnu/libthread_db.so.1".
Ana: 9.5

Program received signal SIGSEGV, Segmentation fault.
0x000055555555522e in mostrar (a=0x0) at crash.c:19
19	    printf("%s: %.1f\n", a->nome, a->nota);
gdb -q ./crash
Abre o gdb com o programa. O -q (quiet, "quieto") esconde a mensagem de boas-vindas com a licença. O gdb mostra o seu prompt, (gdb), e espera comandos. O programa ainda não está rodando.
Reading symbols from ./crash...
"Lendo os símbolos": o gdb leu as informações que o -g guardou.
run
Começa a rodar o programa, dentro do gdb. As duas linhas sobre libthread_db são técnicas e aparecem sempre; pode ignorar.
Ana: 9.5
A saída normal do programa, que continua aparecendo no mesmo terminal.
Program received signal SIGSEGV, Segmentation fault.
"O programa recebeu o sinal SIGSEGV, falha de segmentação". Em vez de deixar o programa morrer, o gdb o congelou no instante exato do erro.
in mostrar (a=0x0) at crash.c:19
Onde ele congelou: na função mostrar, cujo parâmetro a vale 0x0 (zero, o ponteiro nulo), no arquivo crash.c, linha 19. O número comprido do começo (0x0000…522e) é o endereço da instrução na memória; não precisa dele.
19 printf(…)
O texto da linha 19, para você não precisar abrir o arquivo.

Em uma tela, o gdb já respondeu à pergunta: o crash foi na linha 19, e o ponteiro a é nulo. O programa continua congelado; dá para investigar mais.

"Como chegamos aqui?": backtrace

(gdb) backtrace
#0  0x000055555555522e in mostrar (a=0x0) at crash.c:19
#1  0x0000555555555313 in main () at crash.c:25

O backtrace ("rastro para trás", atalho bt) mostra a pilha de chamadas: quem chamou quem até chegar ao erro (veja Pilha e Heap). Leia de baixo para cima:

  • #1 main () at crash.c:25: o main, na linha 25, chamou…
  • #0 mostrar (a=0x0) at crash.c:19: …a função mostrar, que quebrou na linha 19.

Cada linha da pilha se chama quadro (em inglês, frame), numerado a partir de 0. O #0 é sempre onde o programa está agora.

Olhar as variáveis: print e frame

(gdb) print a
$1 = (const struct aluno *) 0x0
(gdb) print *a
❌️ Não é possível acessar a memória no endereço 0x0
(gdb) print turma
❌️ No symbol "turma" in current context.
(gdb) frame 1
#1  0x0000555555555313 in main () at crash.c:25
25	    mostrar(buscar(turma, 2, "Zeca"));
(gdb) print turma
$2 = {{nome = "Ana", '\000' <repete 16 vezes>, nota = 9.5}, {
    nome = "Bruno", '\000' <repete 14 vezes>, nota = 7}}
(gdb) print turma[1].nome
$3 = "Bruno", '\000' <repete 14 vezes>
(gdb) print turma[0].nota * 2
$4 = 19
print a
Mostra o valor de uma variável (atalho: p a). Cada resultado ganha um número, $1, $2…, que você pode reusar depois (print $1). (const struct aluno *) 0x0: um ponteiro para struct aluno que vale zero, o NULL.
print *a
Tenta ver o aluno apontado. O gdb responde (em português, porque o sistema da Ana está em português) que não é possível acessar a memória no endereço 0. Foi exatamente isso que o programa tentou fazer. O ❌️ é o jeito do gdb 17 marcar erros no terminal.
print turma → No symbol "turma" in current context.
"Não existe o símbolo turma neste contexto": cada quadro só enxerga as variáveis da sua função (veja Escopo). A turma mora no main, que é o quadro 1.
frame 1
"Vá para o quadro 1": o main. O gdb mostra a linha onde ele estava, a 25, que procura o "Zeca".
print turma
No main, a turma existe, e o gdb mostra o array inteiro, com as structs dentro. O '\000' <repete 16 vezes> são os '\0' que sobram no char nome[20] depois do "Ana" (veja Strings).
print turma[1].nome / print turma[0].nota * 2
O print aceita expressões do C: índices, campos, contas.

Para ver o código em volta da linha atual, use list (atalho l):

(gdb) list
20	}
21
22	int main(void) {
23	    struct aluno turma[] = {{"Ana", 9.5}, {"Bruno", 7.0}};
24	    mostrar(buscar(turma, 2, "Ana"));
25	    mostrar(buscar(turma, 2, "Zeca"));
26	    printf("Fim.\n");
27	    return 0;
28	}
(gdb) quit
A debugging session is active.

	Inferior 1 [process 26922] will be killed.

Quit anyway? (y or n) y

O quit (atalho q) sai do gdb. Como o programa ainda estava congelado, o gdb avisa que vai encerrá-lo ("Inferior 1" é como o gdb chama o programa que ele controla) e pergunta se pode: y (yes, sim).

Diagnóstico: na linha 25, o main buscou "Zeca", que não está na turma; buscar devolveu NULL; mostrar usou o ponteiro nulo sem conferir. Achamos o erro sem acrescentar um único printf ao código.

Missão: rode primeiro para ver a falha de segmentação (o editor também a mostra, com o código 139). Depois corrija o erro que o gdb encontrou: no começo de mostrar, se a for NULL, mostre Aluno não encontrado e saia da função com return;. O programa deve chegar ao Fim. e terminar com código 0.

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

struct aluno {
    char nome[20];
    double nota;
};

struct aluno *buscar(struct aluno turma[], int n, const char *nome) {
    for (int i = 0; i < n; i++) {
        if (strcmp(turma[i].nome, nome) == 0) {
            return &turma[i];
        }
    }
    return NULL;
}

void mostrar(const struct aluno *a) {
    printf("%s: %.1f\n", a->nome, a->nota);
}

int main(void) {
    struct aluno turma[] = {{"Ana", 9.5}, {"Bruno", 7.0}};
    mostrar(buscar(turma, 2, "Ana"));
    mostrar(buscar(turma, 2, "Zeca"));
    printf("Fim.\n");
    return 0;
}
#include <stdio.h>
#include <string.h>

struct aluno {
    char nome[20];
    double nota;
};

struct aluno *buscar(struct aluno turma[], int n, const char *nome) {
    for (int i = 0; i < n; i++) {
        if (strcmp(turma[i].nome, nome) == 0) {
            return &turma[i];
        }
    }
    return NULL;
}

void mostrar(const struct aluno *a) {
    if (a == NULL) {
        printf("Aluno não encontrado\n");
        return;
    }
    printf("%s: %.1f\n", a->nome, a->nota);
}

int main(void) {
    struct aluno turma[] = {{"Ana", 9.5}, {"Bruno", 7.0}};
    mostrar(buscar(turma, 2, "Ana"));
    mostrar(buscar(turma, 2, "Zeca"));
    printf("Fim.\n");
    return 0;
}

3. Atalho: tudo numa linha com -batch

Quando você só quer saber onde o programa quebrou, dá para passar os comandos direto na linha de comando. -batch ("em lote") roda os comandos e sai sozinho; cada -ex (execute) é um comando:

$ gdb -batch -ex run -ex bt ./crash
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/x86_64-linux-gnu/libthread_db.so.1".
Ana: 9.5

Program received signal SIGSEGV, Segmentation fault.
0x000055555555522e in mostrar (a=0x0) at crash.c:19
19	    printf("%s: %.1f\n", a->nome, a->nota);
#0  0x000055555555522e in mostrar (a=0x0) at crash.c:19
#1  0x0000555555555313 in main () at crash.c:25

Guarde este comando: gdb -batch -ex run -ex bt ./programa é a forma mais rápida de transformar "Falha de segmentação" em "linha 19".

Sem -g, sem linhas

O mesmo programa compilado sem -g:

$ gcc -std=c23 -Wall -Wextra crash.c -o crash-semg
$ gdb -batch -ex run -ex bt ./crash-semg
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/x86_64-linux-gnu/libthread_db.so.1".
Ana: 9.5

Program received signal SIGSEGV, Segmentation fault.
0x000055555555522e in mostrar ()
#0  0x000055555555522e in mostrar ()
#1  0x0000555555555313 in main ()

O gdb ainda sabe os nomes das funções, mas não as linhas, nem os valores dos parâmetros (sumiu o a=0x0). Por isso o -g.

Quando o quadro #0 é da biblioteca

Muitas vezes o programa quebra dentro de uma função da biblioteca do C, como strlen, strcpy ou printf. Este programa conta as letras de cada palavra de uma lista, mas o último item é NULL:

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

size_t letras(const char *palavra) {
    return strlen(palavra);
}

int main(void) {
    const char *lista[] = {"arroz", "feijão", NULL};
    for (int i = 0; i < 3; i++) {
        printf("%zu\n", letras(lista[i]));
    }
    return 0;
}
$ gcc -std=c23 -Wall -Wextra -g conta.c -o conta
$ ./conta
5
7
Falha de segmentação     (imagem do núcleo gravada) ./conta
$ gdb -batch -ex run -ex bt ./conta
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/x86_64-linux-gnu/libthread_db.so.1".
5
7

Program received signal SIGSEGV, Segmentation fault.
__strlen_avx2_rtm () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:76
aviso: 76	../sysdeps/x86_64/multiarch/strlen-avx2.S: Arquivo ou diretório inexistente
#0  __strlen_avx2_rtm () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:76
#1  0x00005555555551a5 in letras (palavra=0x0) at conta.c:5
#2  0x00005555555551fb in main () at conta.c:11
__strlen_avx2_rtm () at ../sysdeps/…/strlen-avx2.S:76
O programa congelou dentro da versão do strlen que a biblioteca do C (a glibc) escolheu para o processador da Ana. No seu computador, o nome pode ser outro (__strlen_evex, __strlen_sse2…).
aviso: … Arquivo ou diretório inexistente
O gdb queria mostrar o código-fonte do strlen, mas ele não está instalado. Normal; pode ignorar.
#1 letras (palavra=0x0) at conta.c:5
A primeira linha que é de um arquivo seu: aqui está a pista. A função letras passou palavra=0x0 (nulo) para o strlen.
#2 main () at conta.c:11
E quem chamou letras foi o main, na linha 11, dentro do laço.

Regra prática: o erro quase nunca é da biblioteca. Ela só recebeu algo estragado do seu código. Desça o backtrace até a primeira linha com um arquivo seu. (Repare também no 7: "feijão" tem 6 letras, mas 7 bytes, porque o ã ocupa 2 bytes em UTF-8. Veja Acentos e UTF-8.)


4. Andar linha a linha: break, next, step

Nem todo erro derruba o programa. Este calcula a média de 7, 8 e 10, que é 8,33…, mas mostra 8,00:

#include <stdio.h>

double media(const int notas[], int n) {
    int soma = 0;
    for (int i = 0; i < n; i++) {
        soma += notas[i];
    }
    return soma / n;
}

int main(void) {
    int notas[] = {7, 8, 10};
    double m = media(notas, 3);
    printf("Média: %.2f\n", m);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra -g media.c -o media
$ ./media
Média: 8.00

Nenhum aviso, nenhum crash, só um resultado errado. Vamos assistir em câmera lenta. Antes, um erro comum: mandar andar uma linha sem o programa estar rodando.

$ gdb -q ./media
Reading symbols from ./media...
(gdb) next
❌️ The program is not being run.

"O programa não está rodando." Primeiro é preciso marcar um ponto de parada e dar run:

(gdb) break main
Ponto de parada 1 at 0x11ca: file media.c, line 11.
(gdb) run
Starting program: /home/ana/c/gdb/media
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main () at media.c:11
11	int main(void) {
break main
Marca um ponto de parada na função main (atalho: b main). Também dá para marcar por linha (break 8) ou por arquivo e linha (break media.c:8). O gdb confirma meio em português, meio em inglês: "Ponto de parada 1… arquivo media.c, linha 11".
run
Roda até o primeiro ponto de parada e pausa.
Breakpoint 1, main () at media.c:11
Parou no ponto 1. A linha mostrada (11 int main(void) {) é a próxima a ser executada: ela ainda não rodou.
(gdb) next
12	    int notas[] = {7, 8, 10};
(gdb) next
13	    double m = media(notas, 3);
(gdb) step
media (notas=0x7fffffffeacc, n=3) at media.c:4
4	    int soma = 0;
(gdb) next
5	    for (int i = 0; i < n; i++) {
(gdb) next
6	        soma += notas[i];
next
Executa a linha atual inteira e para na seguinte (atalho: n). Se a linha chama uma função, a função roda toda, sem você entrar nela.
step
Como o next, mas entra na função chamada (atalho: s). Na linha 13, o step entrou em media, e o gdb mostrou os parâmetros recebidos: notas (um endereço, porque o array chega como ponteiro; veja Ponteiros e Arrays) e n=3.

Acompanhar variáveis: display

Dar print soma depois de cada passo cansa. O display pede ao gdb para mostrar a variável sozinho, toda vez que o programa parar:

(gdb) display soma
1: soma = 0
(gdb) display i
2: i = 0
(gdb) next
5	    for (int i = 0; i < n; i++) {
1: soma = 7
2: i = 0
(gdb) next
6	        soma += notas[i];
1: soma = 7
2: i = 1
(gdb) next
5	    for (int i = 0; i < n; i++) {
1: soma = 15
2: i = 1
(gdb) next
6	        soma += notas[i];
1: soma = 15
2: i = 2
(gdb) undisplay 2
display soma / display i
Cada display ganha um número (1:, 2:) e aparece depois de cada passo.
6 → 5 → 6…
O laço: a linha 6 soma, a linha 5 faz o i++ e testa i < n. A soma vai 0, 7, 15: certo até aqui.
undisplay 2
Para de mostrar o display número 2 (o i). undisplay sem número apaga todos (e pergunta antes).

Pular para a frente: break numa linha e continue

Em vez de apertar next várias vezes por volta, marque um ponto de parada na linha do return e deixe o laço terminar sozinho:

(gdb) break 8
Ponto de parada 2 at 0x5555555551ad: file media.c, line 8.
(gdb) continue
Continuing.

Breakpoint 2, media (notas=0x7fffffffeacc, n=3) at media.c:8
8	    return soma / n;
1: soma = 25
(gdb) print soma
$1 = 25
(gdb) print n
$2 = 3
(gdb) print soma / n
$3 = 8
(gdb) print (double) soma / n
$4 = 8.3333333333333339

Achamos! A soma está certa (25) e o n também (3), mas soma / n dá 8: os dois lados são int, então a divisão é inteira e joga fora o ",33" (veja Operadores Aritméticos). Só depois o 8 vira double (8.0), tarde demais. Com a soma convertida para double antes da divisão ((double) soma, veja Conversão de Tipos), o resultado sai certo. O print deixou testar a correção antes de mexer no código.

(gdb) finish
Run till exit from #0  media (notas=0x7fffffffeacc, n=3) at media.c:8
0x00005555555551ff in main () at media.c:13
13	    double m = media(notas, 3);
Value returned is $5 = 8
(gdb) next
14	    printf("Média: %.2f\n", m);
(gdb) print m
$6 = 8
(gdb) continue
Continuing.
Média: 8.00
[Inferior 1 (process 28304) exited normally]
(gdb) quit
finish
Roda até a função atual terminar, volta para quem chamou e mostra o valor devolvido: Value returned is $5 = 8 ("valor devolvido").
continue
Segue rodando até o próximo ponto de parada ou até o fim (atalho: c). exited normally = "terminou normalmente", com código 0.
quit
Agora o programa já terminou, então o gdb sai sem perguntar nada.

Missão: corrija a divisão inteira que o gdb revelou, para a saída ser Média: 8.33. Dica: converta a soma para double antes de dividir, como no print (double) soma / n da sessão.

#include <stdio.h>

double media(const int notas[], int n) {
    int soma = 0;
    for (int i = 0; i < n; i++) {
        soma += notas[i];
    }
    return soma / n;
}

int main(void) {
    int notas[] = {7, 8, 10};
    double m = media(notas, 3);
    printf("Média: %.2f\n", m);
    return 0;
}
#include <stdio.h>

double media(const int notas[], int n) {
    int soma = 0;
    for (int i = 0; i < n; i++) {
        soma += notas[i];
    }
    return (double) soma / n;
}

int main(void) {
    int notas[] = {7, 8, 10};
    double m = media(notas, 3);
    printf("Média: %.2f\n", m);
    return 0;
}

5. next ou step?

O step entra em qualquer função que tenha informações de depuração. Nas funções da biblioteca do C (printf, strlen…), normalmente ele passa direto, porque o Ubuntu não instala o código delas. Mas se você der step ou next na última linha do main, cai no código que chamou o main:

(gdb) next
16	}
(gdb) next
__libc_start_call_main (main=main@entry=0x5555555551be <main>,
    argc=argc@entry=1, argv=argv@entry=0x7fffffffec18)
    at ../sysdeps/nptl/libc_start_call_main.h:83
⚠️ aviso: 83	../sysdeps/nptl/libc_start_call_main.h: Arquivo ou diretório inexistente
(gdb) next
[Inferior 1 (process 28051) exited normally]

__libc_start_call_main é a função da glibc que chama o seu main e, quando ele volta, encerra o programa com o código devolvido (veja Encerrar o Programa). Não há nada para ver ali: dê continue. Regra prática: next por padrão; step só nas linhas que chamam funções suas; e, se entrou onde não queria, finish volta para quem chamou.


6. Por que depurar sem -O2

Com otimização, o compilador reescreve o programa à vontade. O mesmo media.c, compilado com -O2 -g:

$ gcc -std=c23 -Wall -Wextra -O2 -g media.c -o media-O2
$ gdb -q ./media-O2
Reading symbols from ./media-O2...
(gdb) break media
Ponto de parada 1 at 0x1180: file media.c, line 3.
(gdb) run
Starting program: /home/ana/c/gdb/media-O2
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/x86_64-linux-gnu/libthread_db.so.1".
Média: 8.00
[Inferior 1 (process 28628) exited normally]
(gdb) info breakpoints
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000555555555180 in media at media.c:3
(gdb) quit

O ponto de parada em media existe (o info breakpoints, "informações dos pontos de parada", lista ele), mas o programa rodou até o fim sem parar. Com -O2, o gcc copiou o código de media para dentro do main (isso se chama inlining; veja Funções inline), e a função original nunca é chamada. Com otimização também é comum o gdb mostrar <optimized out> ("removida pela otimização") no lugar de uma variável, e o next pular de um lado para o outro. Para depurar, compile com -g e sem -O2 (ou com -Og, uma otimização leve feita para não atrapalhar a depuração).


Formas diferentes de depurar

FormaBom paraLimite
printf espalhadosUma dúvida rápida, em qualquer lugar (até no editor do guia).Precisa recompilar a cada pergunta; num crash, o texto pode nem aparecer (fica no buffer, veja stdin, stdout e stderr).
gdb -batch -ex run -ex bt"Onde quebrou?" em 1 segundo.Só diz onde parou, não por quê.
gdb interativoEntender um erro de lógica, passo a passo, olhando variáveis.Exige aprender os comandos.
Depurador com botões (IDE)O mesmo que o gdb interativo, com o mouse.Depende da IDE.
Sanitizers e ValgrindErros de memória que não derrubam o programa (estouro de array, memória usada depois do free).Encontram o erro, mas não deixam andar linha a linha.

As IDEs têm depuradores visuais (veja Editores e IDEs): você clica na margem para marcar um ponto de parada, usa botões para Step Over (o next), Step Into (o step), Step Out (o finish) e Continue, e vê as variáveis numa janela, sem digitar print. No Linux, a maioria delas (VS Code com a extensão C/C++, CLion, Code::Blocks) usa o próprio gdb por baixo. No Windows, o Visual Studio tem um depurador próprio; no macOS, o Xcode usa o LLDB, o depurador do projeto LLVM (o mesmo do Clang), com comandos parecidos (breakpoint set, next, step, bt). Qual usar: para "onde quebrou?", o atalho -batch; para "por que o resultado está errado?", o gdb interativo ou o da sua IDE.


Quando dá errado

"No symbol "x" in current context."

O que aconteceu: a variável não existe no quadro atual: ela é de outra função, ou de um bloco que já terminou, ou o programa nem está rodando.

Como resolver: veja os quadros com backtrace e mude com frame N. Se o programa não estiver rodando, marque um break e dê run.

"The program is not being run."

O que aconteceu: você deu next, step ou continue antes do run, ou depois de o programa terminar (exited normally).

Como resolver: marque um ponto de parada (break main) e dê run.

O backtrace mostra só mostrar (), sem arquivo nem linha

O que aconteceu: o programa foi compilado sem -g.

Como resolver: compile de novo com -g e repita.

O programa passou pelo ponto de parada sem parar; <optimized out>; o next pula linhas

O que aconteceu: o programa foi compilado com otimização (-O2, -O3).

Como resolver: compile com -g e sem -O2 (ou com -Og).

O quadro #0 é um nome estranho, como __strlen_avx2_rtm

O que aconteceu: o programa quebrou dentro da biblioteca do C, que recebeu um argumento ruim (um ponteiro nulo, uma string sem '\0').

Como resolver: desça o backtrace até o primeiro quadro com at seu-arquivo.c:N, vá até ele com frame N e olhe os argumentos com print.

"gdb: comando não encontrado"

O que aconteceu: o gdb não está instalado.

Como resolver: no Ubuntu e no Debian, sudo apt install gdb. No macOS, use o lldb, que vem com as ferramentas do Xcode.


Referência rápida

ComandoAtalhoO que faz
gcc … -g arquivo.c -o progCompila com informações de depuração (e sem -O2).
gdb -q ./progAbre o programa no gdb.
gdb -batch -ex run -ex bt ./progRoda e, se quebrar, mostra onde.
runrComeça a rodar.
break main / break 8 / break arq.c:8bMarca um ponto de parada.
info breakpointsi bLista os pontos de parada.
delete NdApaga o ponto de parada N.
nextnPróxima linha, sem entrar em funções.
stepsPróxima linha, entrando nas funções.
continuecSegue até o próximo ponto de parada ou o fim.
finishTermina a função atual e mostra o valor devolvido.
print exprpMostra uma variável ou expressão.
display expr / undisplay NMostra a expressão a cada parada / para de mostrar.
backtracebtMostra a pilha de chamadas.
frame NfVai para o quadro N da pilha.
listlMostra o código em volta da linha atual.
info localsMostra todas as variáveis locais da função atual.
quitqSai do gdb.

Apertar Enter sem digitar nada repete o último comando: ótimo para uma sequência de next.


Exercícios

1. Compilar para depurar

Complete a opção que acrescenta as informações de depuração:

gcc -std=c23 -Wall -Wextra prog.c -o prog

2. Onde quebrou?

O programa parou com SIGSEGV dentro do gdb. Qual comando mostra a pilha de chamadas?

(gdb)

3. Parar numa linha

Complete para marcar um ponto de parada na linha 15 do arquivo loja.c:

(gdb)

4. Sempre na tela

Complete para o gdb mostrar o valor de total sozinho, a cada parada:

(gdb)

5. A linha atual é double m = media(notas, 3); e você quer ver o que acontece dentro de media. Qual comando?

6. No backtrace, o quadro #0 é __strlen_avx2_rtm () e o #1 é letras (palavra=0x0) at conta.c:5. Onde provavelmente está o erro?

7. Você marcou break media, deu run e o programa foi até o fim sem parar. Qual a causa mais provável?


Resumo

  • Compile com -g e sem -O2 para depurar.
  • gdb -q ./prog abre; run roda; num crash, o gdb congela o programa no ponto do erro e mostra arquivo, linha e parâmetros.
  • backtrace mostra quem chamou quem; a primeira linha com um arquivo seu é a pista. frame N muda de quadro; print mostra variáveis e expressões; display mostra a cada passo.
  • break pausa numa função ou linha; next anda sem entrar em funções; step entra; continue segue; finish sai da função atual; quit sai.
  • Atalho para crashes: gdb -batch -ex run -ex bt ./prog.
  • O gdb não roda no editor do guia; os depuradores das IDEs fazem o mesmo, com botões.