Comando gdb no Linux (debug de programas) [Guia Básico]

O Comando gdb no Linux é uma ferramenta muito útil para se depurar softwares compilados com o GCC.

Utilizando o depurador gdb pela primeira vez

Para que o depurador possa funcionar, é necessário que o software seja compilado com a opção “-g” do gcc, de forma que o compilador irá providenciar informações de depuração no código objeto.

Exemplo:

$ gcc –wall –g principal.c rede.c interface.c –o pingnovo

A ideia principal para depurar um programa é inserir um breakpoint no ponto onde você deseja iniciar a depuração em uma determinada linha N do código.

O breakpoint faz uma parada na execução do programa em algum determinado ponto que se deseje examinar com mais cuidado.

Depois de inserir o breakpoint, deve-se executar o programa dentro do gdb e avançar na execução, conferindo os parâmetros passados às funções, o tipo e conteúdo das variáveis, até que o erro seja encontrado. Então, corrige-se o erro no código-fonte e compila-se novamente o programa.

Para utilizar o gdb é simples: 

# gdb programa-executável
GNU gdb 5.3.92
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i586-suse-linux"...
(gdb)

Sua interface é baseada em comandos com sintaxes relativamente simples.

Os comandos podem ser escritos por extenso ou de forma simplificada, onde geralmente utilizam-se somente suas primeiras letras. Os parâmetros como números de linha e nomes de variáveis são passados imediatamente após o nome do comando. 

Vejamos os comandos mais importantes: 

Para inserir um breakpoint em uma determinada linha do código: 

break N 
ou 
b N

Onde N é a linha de código que se deseja parar a execução do programa. Para isto, pode ser necessário visualizar o código-fonte do programa.

Para remover todos os breakpoints: 

delete
ou 
d

Para rodar o programa até encontrar o primeiro breakpoint: 

run
ou 

Para listar o código fonte do programa: 

list
ou 
l

Para executar as próximas instruções do programa. Uma instrução pode ser entendida como uma função: 

next N 
ou 
n N

Onde N é o número de instruções.

Para executar as próximas N linhas depois do breakpoint: 

step N 
ou 
s N

Para encerrar o programa: 

kill
ou 
k

Para exibir informações sobre o tipo de uma variável: 

what variável 
ou 
w variável 

Para exibir o conteúdo de uma variável: 

print variável 
ou 
p variável

Para visualizar o estado de uma variável a cada passo da execução: 

display variáve

Para visualizar o conteúdo de todas as variáveis instanciadas:

info locals

Para ver os registradores do programa: 

info registers

Para visualizar o código assembly do programa: 

disassemble

Para visualizar pilha de recursão: 

backtrace
ou 
bt

Para obter ajuda em relação a algum comando: 

help comando 
ou 
h comando

Para sair do gdb: 

quit
ou 
q

Exemplo de depuração de um programa

Depurar programas sem utilizar ferramentas apropriadas pode ser uma tarefa exaustiva e muitas vezes impossível. Para auxiliar esta tarefa, segue um exemplo do procedimento para fazer descobertas dos problemas utilizando o GDB.

O seguinte programa exemplo foi feito para provocar um erro de cálculo de ponto flutuante:

#include <stdio.h>
float calculo(int numero1, int numero2)
{
  int diferenca;
  float resultado;
  diferenca = numero1 - numero2;
  resultado = numero1 / diferenca;
  return resultado;
}
int main(int argc, char *argv[])
{
  int valor, divisor, i;
  float resultado, total;
  valor = 10.1;
  divisor = 6;
  total = 0;
  for(i = 0; i < 10; i++)
  {
    resultado = calculo(valor, divisor);
        total += resultado;
        printf("%d  dividido por %d é %f. A soma dos resultados é %f.\n", valor, valor-divisor, resultado,total);
    divisor++;
    valor--;
  }
return 0;
}

Para compilar este programa salvo como “erro.c”:

$ gcc –g erro.c –o erro

A opção “-g” compila o programa com as informações necessárias para depuração.

Este programa deveria executar um loop dez vezes e calcular o valor acumulado de sucessivas divisões. No entanto ao executá-lo, um erro será abortará sua execução:

$ ./erro
10  dividido por 4 é 2.000000. A soma dos resultados é 2.000000.
9  dividido por 2 é 4.000000. A soma dos resultados é 6.000000.
Floating point exception

Para fazer a depuração é necessário executar o programa através do GDB:

$ gdb erro
GNU gdb 5.3.92
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i586-suse-linux"...
(gdb)

O programa poderá ser executado dentro do GDB com o comando “run”:

(gdb) run
Starting program: /root/sistemas/erro
10  dividido por 4 é 2.000000. A soma dos resultados é 2.000000.
9  dividido por 2 é 4.000000. A soma dos resultados é 6.000000.
Program received signal SIGFPE, Arithmetic exception.
0x0804835e in calculo (numero1=8, numero2=8) at gdb.c:7
7         resultado = numero1 / diferenca;

O GDB indicou que o programa fez uma operação matemática ilegal na linha 7 precisamente na função calculo:

Os valores das variáveis numero1 e numero2 são 8. Podemos listar o código fonte do programa para ver o que ele está fazendo com o comando “list”:

(gdb) list
2       float calculo(int numero1, int numero2)
3       {
4         int diferenca;
5         float resultado;
6         diferenca = numero1 - numero2;
7         resultado = numero1 / diferenca;
8         return resultado;
9       }
10      int main(int argc, char *argv[])
11      {

O comando list irá listar o código de 10 em 10 linhas. Ele poderá ser repetido até que todo o código seja exibido.

Como primeiro passo é interessante examinar o conteúdo das variáveis com o comando “print”:

(gdb) print numero1
$1 = 8
(gdb) print numero2
$2 = 8
(gdb) print diferenca
$3 = 0

Na linha 7 do programa onde ocorreu o erro, pode-se verificar que a operação “resultado = numero1 / diferença”. Analisando o valor das variáveis e a instrução pode-se deduzir que o programa está tentando fazer uma divisão por zero.

Podemos continuar o processamento do programa com o comando “continue”:

(gdb) continue
Continuing.
Program terminated with signal SIGFPE, Arithmetic exception.
The program no longer exists.

Como o erro divisão por zero aborta a execução, o programa não continuará. Será necessário usar os breakpoints e executar o programa novamente. É interessante instalar o ponto de parada na função main antes da chamada da função calculo.

(gdb) list
10      int main(int argc, char *argv[])
11      {
12        int valor, divisor, i;
13        float resultado, total;
14        valor = 10.1;
15        divisor = 6;
16        total = 0;
17        for(i = 0; i < 10; i++)
18        {
19          resultado = calculo(valor, divisor);
20              total += resultado;
21              printf("%d  dividido por %d é %f. A soma dos resultados é %f.\n", = valor, valor-divisor, resultado,total);
22          divisor++;
23          valor--;
24        }
25      return 0;

Podemos instalar o ponto de parada na linha 12 no início da função main e executar novamente o programa:

(gdb) break 12
Breakpoint 1 at 0x8048384: file gdb.c, line 12.
(gdb) run
Starting program: /root/sistemas/erro
Breakpoint 1, main (argc=1, argv=0xbfffd874) at gdb.c:14
14        valor = 10.1;

O processamento irá parar na linha 14 que é contém instrução significativa depois da linha 12. Podemos avançar o processamento linha a linha com o comando “next”:

(gdb) next
15        divisor = 6;
(gdb) next
16        total = 0;
(gdb) next
17        for(i = 0; i < 10; i++)

Os conteúdos das variáveis poderão ser exibidos com o comando “print” seguido do nome da variável:

(gdb) print valor
$5 = 10
(gdb) print divisor
$6 = 6

O valor de todas as variáveis instanciadas poderá ser visto com o comando “info locals”:

(gdb) info locals
valor = 10
divisor = 6
i = 0
resultado = -1.99878407
total = 0

O próximo passo é executar o programa linha a linha até que o conteúdo das variáveis valor e divisor seja 8. O comando “display” permite que o conteúdo de uma variável seja exibido a cada instrução processada:

(gdb) display valor
1: valor = 10
(gdb) display divisor
2: divisor = 6

O avanço das instruções poderá ser feito com o comando “next”:

(gdb) next
20              total += resultado;
2: divisor = 6
1: valor = 10
(gdb) next
21              printf("%d  dividido por %d é %f. A soma dos resultados é %f.\n", valor, valor-divisor, resultado,total);
2: divisor = 6
1: valor = 10
(gdb) next
10  dividido por 4 é 2.000000. A soma dos resultados é 2.000000.
22          divisor++;
2: divisor = 6
1: valor = 10
(gdb) next
23          valor--;
2: divisor = 7
1: valor = 10
(gdb) next
17        for(i = 0; i < 10; i++)
2: divisor = 7
1: valor = 9
(gdb) next
19          resultado = calculo(valor, divisor);
2: divisor = 7
1: valor = 9
(gdb) next
20              total += resultado;
2: divisor = 7
1: valor = 9
(gdb) next
21              printf("%d  dividido por %d é %f. A soma dos resultados é %f.\n", valor, valor-divisor, resultado,total);
2: divisor = 7
1: valor = 9
(gdb) next
9  dividido por 2 é 4.000000. A soma dos resultados é 6.000000.
22          divisor++;
2: divisor = 7
1: valor = 9
(gdb) next
23          valor--;
2: divisor = 8
1: valor = 9
(gdb) next
17        for(i = 0; i < 10; i++)
2: divisor = 8
1: valor = 8

Neste ponto, a função calculo irá retornar a divisão por zero. O valor da variável poderá ser trocado em execução para driblar o erro com o comando “set”:

(gdb) info locals
valor = 8
divisor = 8
i = 1
resultado = 4
total = 6
(gdb) set valor = 10
(gdb) info locals
valor = 10
divisor = 8
i = 1
resultado = 4
total = 6

O programa irá continuar o processamento até encontrar outra divisão por zero.

(gdb) next
19          resultado = calculo(valor, divisor);
2: divisor = 8
1: valor = 10
(gdb) next
20              total += resultado;
2: divisor = 8
1: valor = 10
(gdb) next
21              printf("%d  dividido por %d é %f. A soma dos resultados é %f.\n", valor, valor-divisor, resultado,total);
2: divisor = 8
1: valor = 10
(gdb) next
10  dividido por 2 é 5.000000. A soma dos resultados é 11.000000.
22          divisor++;
2: divisor = 8
1: valor = 10
(gdb) next
23          valor--;
2: divisor = 9
1: valor = 10
(gdb) next
17        for(i = 0; i < 10; i++)
2: divisor = 9
1: valor = 9
(gdb) next
19          resultado = calculo(valor, divisor);
2: divisor = 9
1: valor = 9
(gdb) next
Program received signal SIGFPE, Arithmetic exception.
0x0804835e in calculo (numero1=9, numero2=9) at gdb.c:7
7         resultado = numero1 / diferenca;

No caso deste exemplo, o programa está na declaração das variáveis como inteiros não permitindo o resto de uma divisão.

O GDB é um poderoso depurador de programas. Encorajamos você a instalar uma interface gráfica para ele para facilitar o seu uso. Há outros depuradores também disponíveis.

Aprenda muito mais sobre Linux em nosso curso online. Você pode efetuar a matrícula aqui. Se você já tem uma conta, ou quer criar uma, basta entrar ou criar seu usuário aqui.

Gostou? Compartilhe