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.
- Como preparar o programa para depuração: a opção
-gdo gcc (e por que sem-O2). - Como rodar dentro do gdb (
gdb ./programa,run) e achar a linha de um crash combacktrace. - Como olhar variáveis com
print, trocar de função comframee acompanhar valores comdisplay. - Como pausar o programa numa linha (
break) e andar linha a linha (nextestep). - 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 remoto | No gdb |
|---|---|
| Dar o play | run |
| Marcar um ponto para pausar sozinho | break (um ponto de parada, em inglês breakpoint) |
| Avançar um quadro | next / step |
| Dar zoom num detalhe | print variável |
| Deixar um detalhe sempre na tela | display variável |
| Continuar até o próximo ponto marcado | continue |
| "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
.cde 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
-gguardou. - run
- Começa a rodar o programa, dentro do gdb. As duas linhas sobre
libthread_dbsã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âmetroavale0x0(zero, o ponteiro nulo), no arquivocrash.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: omain, na linha 25, chamou…#0 mostrar (a=0x0) at crash.c:19: …a funçãomostrar, 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 parastruct alunoque vale zero, oNULL. - 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
turmaneste contexto": cada quadro só enxerga as variáveis da sua função (veja Escopo). Aturmamora nomain, 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, aturmaexiste, e o gdb mostra o array inteiro, com as structs dentro. O'\000' <repete 16 vezes>são os'\0'que sobram nochar nome[20]depois do"Ana"(veja Strings). - print turma[1].nome / print turma[0].nota * 2
- O
printaceita 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
strlenque 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
letraspassoupalavra=0x0(nulo) para ostrlen. - #2 main () at conta.c:11
- E quem chamou
letrasfoi omain, 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, ostepentrou emmedia, e o gdb mostrou os parâmetros recebidos:notas(um endereço, porque o array chega como ponteiro; veja Ponteiros e Arrays) en=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
displayganha 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 testai < n. Asomavai 0, 7, 15: certo até aqui. - undisplay 2
- Para de mostrar o display número 2 (o
i).undisplaysem 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
| Forma | Bom para | Limite |
|---|---|---|
printf espalhados | Uma 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 interativo | Entender 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 Valgrind | Erros 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
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.
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.
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.
<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).
#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.
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
| Comando | Atalho | O que faz |
|---|---|---|
gcc … -g arquivo.c -o prog | Compila com informações de depuração (e sem -O2). | |
gdb -q ./prog | Abre o programa no gdb. | |
gdb -batch -ex run -ex bt ./prog | Roda e, se quebrar, mostra onde. | |
run | r | Começa a rodar. |
break main / break 8 / break arq.c:8 | b | Marca um ponto de parada. |
info breakpoints | i b | Lista os pontos de parada. |
delete N | d | Apaga o ponto de parada N. |
next | n | Próxima linha, sem entrar em funções. |
step | s | Próxima linha, entrando nas funções. |
continue | c | Segue até o próximo ponto de parada ou o fim. |
finish | Termina a função atual e mostra o valor devolvido. | |
print expr | p | Mostra uma variável ou expressão. |
display expr / undisplay N | Mostra a expressão a cada parada / para de mostrar. | |
backtrace | bt | Mostra a pilha de chamadas. |
frame N | f | Vai para o quadro N da pilha. |
list | l | Mostra o código em volta da linha atual. |
info locals | Mostra todas as variáveis locais da função atual. | |
quit | q | Sai 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:
2. Onde quebrou?
O programa parou com SIGSEGV dentro do gdb. Qual comando mostra a pilha de chamadas?
3. Parar numa linha
Complete para marcar um ponto de parada na linha 15 do arquivo loja.c:
4. Sempre na tela
Complete para o gdb mostrar o valor de total sozinho, a cada parada:
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
-ge sem-O2para depurar. gdb -q ./progabre;runroda; num crash, o gdb congela o programa no ponto do erro e mostra arquivo, linha e parâmetros.backtracemostra quem chamou quem; a primeira linha com um arquivo seu é a pista.frame Nmuda de quadro;printmostra variáveis e expressões;displaymostra a cada passo.breakpausa numa função ou linha;nextanda sem entrar em funções;stepentra;continuesegue;finishsai da função atual;quitsai.- 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.