Guia basico GDB

De MediaWiki do Campus São José
Ir para navegação Ir para pesquisar

Traduzido de Tutorial of gcc and gdb.

Introdução

gdb é um depurador (debugger) feito pelo projeto GNU. gdb pode executar passo a passo o seu programa linha por linha e mesmo instrução por instrução. Com ele se consegue também observar o valor de qualquer variável em tempo de execução. Por fim, ele ajuda a identificar o lugar e o motivo que causou um erro de execução do programa.

Uso básico

Todo programa a ser depurado com gdb precisa ser compilado pelo gcc com a opção -g. Isso é demonstrado abaixo com um pequeno programa de exemplo.

  1. Copie este programa para o arquivo exemplo.c:
    #include <stdio.h>
    #include <string.h>
    
    int main() {
      int n;
      char nome[32];
    
      printf("Digite seu nome: ");
      fgets(nome, 32, stdin);
      nome[strlen(nome)] = 0;
      for (n=strlen(nome); n >= 0; n--) {
        printf("%c", nome[n]);
      }
      return 0;
    }
    
  2. Compile-o da seguinte forma:
    gcc -g -o exemplo exemplo.c
    
  3. Use o gdb para depurá-lo:
    gdb ./exemplo
    
    • ... o que vai mostrar uma apresentação do gdb na sua tela:
      GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
      Copyright (C) 2012 Free Software Foundation, Inc.
      License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
      This is free software: you are free to change and redistribute it.
      There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
      and "show warranty" for details.
      This GDB was configured as "x86_64-linux-gnu".
      For bug reporting instructions, please see:
      <http://bugs.launchpad.net/gdb-linaro/>...
      Lendo símbolos de /home/aluno/exemplo...concluído.
      (gdb)
      
  4. Antes de iniciar a execução do programa, defina ao menos um ponto de parada (breakpoint). O gdb para a execução nesse ponto, a partir do qual se pode executar passo-a-passo. Um ponto de parada pode ser uma função ou uma linha do programa. No exemplo, o ponto de parada foi definido como sendo a função main:
    (gdb) break main
    Ponto de parada 1 at 0x40063c: file exemplo.c, line 4.
    
  5. Execute o programa, notando que a execução para no ponto de parada que foi definido:
    (gdb) run
    Starting program: /home/aluno/exemplo 
    
    Breakpoint 1, main () at exemplo.c:4
    4	int main() {
    
  6. Para avançar uma linha do programa, use o comando next. Após esse comando é mostrada a próxima linha do programa a ser executada:
    (gdb) next
    8	  printf("Digite seu nome: ");
    
  7. Para terminar a depuração, use o comando quit:
    (gdb) quit
    A debugging session is active.
    
    	Inferior 1 [process 15861] will be killed.
    
    Quit anyway? (y or n) 
    Y
    

Execução de um programa que pede argumentos de linha de comando

Devem-se informar os argumentos do programa em seguida ao comando run. Ex: se o programa exemplo pedisse um argumento:

(gdb) run arg1

... e se pedisse dois argumentos:

(gdb) run arg1 arg2

Execução para descobrir em que linha está um erro

Quando um programa termina com um erro fatal, como Falha de segmentação, torna-se necessário saber exatamente o que o programa tentou fazer e desencadeou o erro. O gdb possibilita descobrir isso facilmente, bastando executar o programa dentro do depurador. Por exemplo, seja o programa erro.c:

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

int main() {
  char * nome;
 
  printf("Digite seu nome: ");
  fgets(nome, 32, stdin);
  nome[strlen(nome)-1] = 0;
  printf("seu nome é: %s\n", nome);
  return 0;
}

Ao compilá-lo e executá-lo, obtém-se um erro fatal:

$ gcc -g -o erro erro.c
$ ./erro
Digite seu nome: maneca
Falha de segmentação (imagem do núcleo gravada)

Executando-se esse programa com gdb descobre-se onde ocorreu o erro. Após o erro, usa-se o comando backtrace para mostrar em que linha do programa ele aconteceu:

$ gdb erro
(gdb) run
Starting program: /home/aluno/erro 
Digite seu nome: maneca

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7a8a270 in _IO_getline_info () from /lib/x86_64-linux-gnu/libc.so.6
(gdb) backtrace
#0  0x00007ffff7a8a270 in _IO_getline_info ()
   from /lib/x86_64-linux-gnu/libc.so.6
#1  0x00007ffff7a8913b in fgets () from /lib/x86_64-linux-gnu/libc.so.6
#2  0x00000000004005b9 in main () at erro.c:8
(gdb)


Nesse exemplo, o erro se deu na linha 8 de erro.c:

fgets(nome, 32, stdin);

... e o motivo foi ter tentado escrever dados na variável nome, porém essa variável não aponta uma área de memória previamente alocada.

Passo a passo

A execução passo-a-passo pode ser feita com os comandos next ou step. Com next executa-se uma linha de código, sem entrar em funções que porventura sejam chamadas. Com step executa-se uma linha, porém entrando em funções que forem chamadas.

Passo-a-passo com next

Por exemplo, seja o programa exemplo1.c abaixo:

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

void mostraInvertido(char * texto) {
  int n;

  for (n=strlen(texto); n > 0; n--) {
    printf("%c", texto[n-1]);
  }
  puts("");
}
 
int main() {
  char nome[32];
 
  printf("Digite seu nome: ");
  fgets(nome, 32, stdin);
  nome[strlen(nome)-1] = 0;
  mostraInvertido(nome);
  return 0;
}

Se a depuração passo-a-passo for realizada com next:

(gdb) break main
Ponto de parada 1 at 0x4006f4: file exemplo1.c, line 13.
(gdb) run
Starting program: /home/aluno/exemplo1 

Breakpoint 1, main () at ex1.c:13
13	int main() {
(gdb) next
16	  printf("Digite seu nome: ");
(gdb) next
17	  fgets(nome, 32, stdin);
(gdb) next
Digite seu nome: maneca
18	  nome[strlen(nome)-1] = 0;
(gdb) next
19	  mostraInvertido(nome);
(gdb) next
acenam
20	  return 0;
(gdb) next
21	}
(gdb) next
0x00007ffff7a3b78d in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6
(gdb)

Passo-a-passo com step

Note o que acontece se for usado step ao executar a linha 19 (mostraInvertido(nome)). O depurador começa a executar dentro da funjção mostraInvertido:

(gdb) run
Starting program: /home/msobral/tmp/ex1 

Breakpoint 1, main () at ex1.c:13
13	int main() {
(gdb) next
16	  printf("Digite seu nome: ");
(gdb) next
17	  fgets(nome, 32, stdin);
(gdb) next
Digite seu nome: maneca
18	  nome[strlen(nome)-1] = 0;
(gdb) next
19	  mostraInvertido(nome);
(gdb) step
mostraInvertido (texto=0x7fffffffe030 "maneca") at ex1.c:7
7	  for (n=strlen(texto); n > 0; n--) {
(gdb) next
8	    printf("%c", texto[n-1]);
(gdb) next
7	  for (n=strlen(texto); n > 0; n--) {
(gdb) next
8	    printf("%c", texto[n-1]);
(gdb) next
7	  for (n=strlen(texto); n > 0; n--) {
(gdb) next
8	    printf("%c", texto[n-1]);
(gdb) next
7	  for (n=strlen(texto); n > 0; n--) {
(gdb) next
8	    printf("%c", texto[n-1]);
(gdb) next
7	  for (n=strlen(texto); n > 0; n--) {
(gdb) next
8	    printf("%c", texto[n-1]);
(gdb) next
7	  for (n=strlen(texto); n > 0; n--) {
(gdb) next
8	    printf("%c", texto[n-1]);
(gdb) next
7	  for (n=strlen(texto); n > 0; n--) {
(gdb) next
10	  puts("");
(gdb) next
acenam
11	}
(gdb) next
main () at exemplo1.c:20
20	  return 0;
(gdb) next
21	}
(gdb) next
0x00007ffff7a3b78d in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6
(gdb)

Executando até o o final de uma função

Estando no passo-a-passo, pode-se executar até o final da função corrente com o comando finish. Assim o programa executa direto até chegar o final da função, e logo após retornar da função volta-se ao passo-a-passo:

(gdb) run
Starting program: /home/aluno/exemplo1 

Breakpoint 1, main () at exemplo1.c:13
13	int main() {
(gdb) next
16	  printf("Digite seu nome: ");
(gdb) next
17	  fgets(nome, 32, stdin);
(gdb) next
Digite seu nome: maneca
18	  nome[strlen(nome)-1] = 0;
(gdb) next
19	  mostraInvertido(nome);
(gdb) step
mostraInvertido (texto=0x7fffffffe030 "maneca") at ex1.c:7
7	  for (n=strlen(texto); n > 0; n--) {
(gdb) next
8	    printf("%c", texto[n-1]);
(gdb) next
7	  for (n=strlen(texto); n > 0; n--) {
(gdb) next
8	    printf("%c", texto[n-1]);
(gdb) finish
Run till exit from #0  mostraInvertido (texto=0x7fffffffe030 "maneca")
    at ex1.c:8
acenam
main () at exemplo1.c:20
20	  return 0;
(gdb) next
21	}
(gdb) next
0x00007ffff7a3b78d in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6
(gdb)

Saindo do passo-a-passo

Pode-se sair do passo-a-passo com o comando continue. Com ele o programa volta a executar continuamente, até chegar a um ponto de parada ou terminar.

(gdb) run
Starting program: /home/aluno/exemplo1

Breakpoint 1, main () at exemplo1.c:13
13	int main() {
(gdb) next
16	  printf("Digite seu nome: ");
(gdb) next
17	  fgets(nome, 32, stdin);
(gdb) continue
Continuando.
Digite seu nome: maneca
acenam
[Inferior 1 (process 17002) exited normally]
(gdb)

Executando até um ponto específico

Uma vez no passo-a-passo, pode-se usar o comando advance para avançar até uma certa linha do código, ou até uma determinada função. Quer dizer, a execução é feita continuamente (sem passo-a-passo) até chegar ao ponto do programa desejado.

(gdb) run
Starting program: /home/aluno/exemplo1

Breakpoint 1, main () at exemplo1.c:13
13	int main() {
(gdb) advance 17
main () at exemplo1.c:17
17	  fgets(nome, 32, stdin);
(gdb) next
Digite seu nome: maneca
18	  nome[strlen(nome)-1] = 0;
(gdb) continue
Continuando.
acenam
[Inferior 1 (process 17077) exited normally]
(gdb)

Avançando até uma certa linha do programa


(gdb) run
Starting program: /home/aluno/exemplo1

Breakpoint 1, main () at exemplo1.c:13
13	int main() {
(gdb) advance mostraInvertido
Digite seu nome: maneca
mostraInvertido (texto=0x7fffffffe030 "maneca") at exemplo1.c:7
7	  for (n=strlen(texto); n > 0; n--) {
(gdb) next
8	    printf("%c", texto[n-1]);
(gdb) continue
Continuando.
acenam
[Inferior 1 (process 17095) exited normally]
(gdb)

Avançando até uma certa função

Mostrando o valor de variáveis

Variáveis podem ter seus valores mostrados com o comando display. Para mostrar o conteúdo de variáveis, basta usar display nome_da_variavel:

(gdb) run
Starting program: /home/aluno/exemplo1

Breakpoint 1, main () at exemplo1.c:13
13	int main() {
(gdb) next
16	  printf("Digite seu nome: ");
(gdb) next
17	  fgets(nome, 32, stdin);
(gdb) next
Digite seu nome: maneca
18	  nome[strlen(nome)-1] = 0;
(gdb) display nome
1: nome = "maneca\n\000\220\a@", '\000' <repete 13 vezes>"\240, \005@\000\000\000\000"
(gdb) next
19	  mostraInvertido(nome);
1: nome = "maneca\000\000\220\a@", '\000' <repete 13 vezes>"\240, \005@\000\000\000\000"
(gdb)

Observe que a variável continuará a ser apresentada a cada linha executada, a menos que se use o comando undisplay numero_da_variavel para suprimi-la. Nesse caso, o numero_da_variavel está informado à frente de seu nome - no exemplo acima, o número é 1.

Mostrando o conteúdo apontado por ponteiros

Quando uma variável é um ponteiro, pode-se mostrar o conteúdo de memória por ela apontado ao se usar display *nome_da_variavel.

Programa de teste
#include <stdio.h>
#include <stdlib.h>

struct Pessoa {
  char nome[32];
  char cpf[16];
};

void mostra_pessoa(struct Pessoa * p) {
  printf("Nome: %s, CPF=%s\n", p->nome, p->cpf);
}

int main() {
  struct Pessoa * alguem;
  int n;

  alguem = (struct Pessoa*) malloc(sizeof(struct Pessoa));
  printf("Nome: ");
  scanf("%32[^\n]", alguem->nome);
  printf("CPF: ");
  scanf("%s", alguem->cpf);

  mostra_pessoa(alguem);

  free(alguem);

  return 0;
}
(gdb) break main
Ponto de parada 1 at 0x40062b: file ex2.c, line 17.
(gdb) run
Starting program: /home/msobral/tmp/ex2 

Breakpoint 1, main () at ex2.c:17
17	  alguem = (struct Pessoa*) malloc(sizeof(struct Pessoa));
(gdb) next
18	  printf("Nome: ");
(gdb) next
19	  scanf("%32[^\n]", alguem->nome);
(gdb) next
Nome: Maneca
20	  printf("CPF: ");
(gdb) next
21	  scanf("%s", alguem->cpf);
(gdb) next
CPF: 888.777.666-55
23	  mostra_pessoa(alguem);
(gdb) display *alguem
1: *alguem = {nome = "Maneca", '\000' <repete 25 vezes>, cpf = "888.777.666-55\000"}
(gdb) step
mostra_pessoa (p=0x602010) at ex2.c:10
10	  printf("Nome: %s, CPF=%s\n", p->nome, p->cpf);
(gdb) display *p
2: *p = {nome = "Maneca", '\000' <repete 25 vezes>, cpf = "888.777.666-55\000"}
(gdb) next
Nome: Maneca, CPF=888.777.666-55
11	}
2: *p = {nome = "Maneca", '\000' <repete 25 vezes>, cpf = "888.777.666-55\000"}
(gdb)

Monitorando o valor de variáveis

O depurador possibilita monitorar variáveis por meio do comando watch. Quando uma variável monitorada tem seu valor alterado, a execução do programa é interrompida e mostra-se o valor da variável.

Programa de teste
#include <stdio.h>

int main() {
  int v1[10];
  int * v2;
  int i;

  for (i=0; i < 10; i++) v1[i] = i*i;

  v2 = v1;
  printf("v1=%p, v2=%p\n", v1, v2);

  for (i=0; i < 10; i++) {
    printf("v2[%d]=%d\n", i, v2[i]);
  }
  for (i=0; i < 10; i++) {
    printf("v2[%d]=%d\n", i, *(v2+i));
  }

  v2[0] = 99999;
  printf("v2[0]=%d, v1[0]=%d\n", v2[0], v1[0]);
}
(gdb) break main
Ponto de parada 1 at 0x4004fc: file vet.c, line 8.
(gdb) run
Starting program: /home/msobral/tmp/vet 

Breakpoint 1, main () at vet.c:8
8	  for (i=0; i < 10; i++) v1[i] = i*i;
(gdb) watch v2
Hardware watchpoint 2: v2
(gdb) continue
Continuando.
Hardware watchpoint 2: v2

Old value = (int *) 0x7fffffffe0e0
New value = (int *) 0x7fffffffdfc0
main () at vet.c:16
16	  printf("v1=%p, v2=%p\n", v1, v2);
(gdb)