Comando gdb en Linux: Depurar e Inspeccionar Programas Compilados con GCC
El Comando gdb en Linux es una herramienta muy útil para depurar software compilado con el GCC.
Utilizando el depurador gdb por primera vez
Para que el depurador pueda funcionar, es necesario que el software sea compilado con la opción "-g" de gcc, de manera que el compilador proveerá información de depuración en el código objeto.
Ejemplo:
$ gcc –wall –g principal.c rede.c interface.c –o pingnovo
La idea principal para depurar un programa es insertar un breakpoint en el punto donde deseas iniciar la depuración en una determinada línea N del código.
El breakpoint hace una pausa en la ejecución del programa en algún punto determinado que se desee examinar con más cuidado.
Después de insertar el breakpoint, se debe ejecutar el programa dentro del gdb y avanzar en la ejecución, verificando los parámetros pasados a las funciones, el tipo y contenido de las variables, hasta que se encuentre el error. Entonces, se corrige el error en el código fuente y se compila nuevamente el programa.
Para utilizar el gdb es simple:
# gdb programa-executávelGNU gdb 5.3.92Copyright 2003 Free Software Foundation, Inc.GDB is free software, covered by the GNU General Public License, and you arewelcome 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)
Su interfaz es basada en comandos de Linux con sintaxis relativamente simples.
Los comandos pueden ser escritos de forma completa o simplificada, generalmente usando solo sus primeras letras. Los parámetros como números de línea y nombres de variables se pasan inmediatamente después del nombre del comando.
Veamos los comandos más importantes:
Para insertar un breakpoint en una determinada línea del código:
break N
o
b N
Donde N es la línea de código donde se desea detener la ejecución del programa. Para esto, puede ser necesario visualizar el código fuente del programa.
Para eliminar todos los breakpoints:
delete
o
d
Para ejecutar el programa hasta encontrar el primer breakpoint:
run
o
r
Para listar el código fuente del programa:
list
o
l
Para ejecutar las siguientes instrucciones del programa. Una instrucción puede entenderse como una función:
next N
o
n N
Donde N es el número de instrucciones.
Para ejecutar las siguientes N líneas después del breakpoint:
step N
o
s N
Para finalizar el programa:
kill
o
k
Para mostrar información sobre el tipo de una variable:
what variable
o
w variable
Para mostrar el contenido de una variable:
print variable
o
p variable
Para ver el estado de una variable en cada paso de la ejecución:
display variablel
Para ver el contenido de todas las variables instanciadas:
info locals
Para ver los registros del programa:
info registers
Para ver el código assembly del programa:
disassemble
Para ver la pila de recursión:
backtrace
o
bt
Para obtener ayuda sobre algún comando:
help comando
o
h comando
Para salir del gdb:
quit
o
q
Ejemplo de depuración de un programa
Depurar programas sin usar herramientas apropiadas puede ser una tarea agotadora y a menudo imposible. Para ayudar en esta tarea, se presenta un ejemplo del procedimiento para descubrir los problemas usando el GDB.
El siguiente programa de ejemplo fue creado para provocar un error de cálculo de punto flotante:
#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 guardado como "erro.c":
$ gcc –g erro.c –o erro
La opción "-g" compila el programa con la información necesaria para la depuración.
Este programa debería ejecutar un bucle diez veces y calcular el valor acumulado de divisiones sucesivas. Sin embargo, al ejecutarlo, un error abortará su ejecución:
$ ./erro10 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 hacer la depuración es necesario ejecutar el programa a través del GDB:
$ gdb erroGNU gdb 5.3.92Copyright 2003 Free Software Foundation, Inc.GDB is free software, covered by the GNU General Public License, and you arewelcome 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)
El programa podrá ejecutarse dentro del GDB con el comando "run":
(gdb) runStarting program: /root/sistemas/erro10 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:77 resultado = numero1 / diferenca;
El GDB indicó que el programa realizó una operación matemática ilegal en la línea 7 precisamente en la función calculo:
Los valores de las variables numero1 y numero2 son 8. Podemos listar el código fuente del programa para ver lo que está haciendo con el comando "list":
(gdb) list2 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 {
El comando list listará el código de 10 en 10 líneas. Podrá repetirse hasta que se muestre todo el código.
Como primer paso es interesante examinar el contenido de las variables con el comando "print":
(gdb) print numero1$1 = 8(gdb) print numero2$2 = 8(gdb) print diferenca$3 = 0
En la línea 7 del programa donde ocurrió el error, se puede verificar que la operación "resultado = numero1 / diferencia". Analizando el valor de las variables y la instrucción se puede deducir que el programa está intentando hacer una división por cero.
Podemos continuar el procesamiento del programa con el comando "continue":
(gdb) continueContinuing.Program terminated with signal SIGFPE, Arithmetic exception.The program no longer exists.
Como el error división por cero aborta la ejecución, el programa no continuará. Será necesario usar los breakpoints y ejecutar el programa nuevamente. Es interesante instalar el punto de parada en la función main antes de la llamada de la función calculo.
(gdb) list10 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 el punto de parada en la línea 12 al inicio de la función main y ejecutar nuevamente el programa:
(gdb) break 12Breakpoint 1 at 0x8048384: file gdb.c, line 12.(gdb) runStarting program: /root/sistemas/erroBreakpoint 1, main (argc=1, argv=0xbfffd874) at gdb.c:1414 valor = 10.1;
El procesamiento se detendrá en la línea 14 que es la que contiene la instrucción significativa después de la línea 12. Podemos avanzar el procesamiento línea a línea con el comando "next":
(gdb) next15 divisor = 6;(gdb) next16 total = 0;(gdb) next17 for(i = 0; i < 10; i++)
Los contenidos de las variables podrán ser mostrados con el comando "print" seguido del nombre de la variable:
(gdb) print valor$5 = 10(gdb) print divisor$6 = 6
El valor de todas las variables instanciadas podrá ser visto con el comando "info locals":
(gdb) info localsvalor = 10divisor = 6i = 0resultado = -1.99878407total = 0
El siguiente paso es ejecutar el programa línea a línea hasta que el contenido de las variables valor y divisor sea 8. El comando "display" permite que el contenido de una variable sea mostrado en cada instrucción procesada:
(gdb) display valor1: valor = 10(gdb) display divisor2: divisor = 6
El avance de las instrucciones podrá ser hecho con el comando "next":
(gdb) next20 total += resultado;2: divisor = 61: valor = 10(gdb) next21 printf("%d dividido por %d é %f. A soma dos resultados é %f.\n", valor, valor-divisor, resultado,total);2: divisor = 61: valor = 10(gdb) next10 dividido por 4 é 2.000000. A soma dos resultados é 2.000000.22 divisor++;2: divisor = 61: valor = 10(gdb) next23 valor--;2: divisor = 71: valor = 10(gdb) next17 for(i = 0; i < 10; i++)2: divisor = 71: valor = 9(gdb) next19 resultado = calculo(valor, divisor);2: divisor = 71: valor = 9(gdb) next20 total += resultado;2: divisor = 71: valor = 9(gdb) next21 printf("%d dividido por %d é %f. A soma dos resultados é %f.\n", valor, valor-divisor, resultado,total);2: divisor = 71: valor = 9(gdb) next9 dividido por 2 é 4.000000. A soma dos resultados é 6.000000.22 divisor++;2: divisor = 71: valor = 9(gdb) next23 valor--;2: divisor = 81: valor = 9(gdb) next17 for(i = 0; i < 10; i++)2: divisor = 81: valor = 8
En este punto, la función calculo va a retornar la división por cero. El valor de la variable podrá ser cambiado en ejecución para evadir el error con el comando "set":
(gdb) info localsvalor = 8divisor = 8i = 1resultado = 4total = 6(gdb) set valor = 10(gdb) info localsvalor = 10divisor = 8i = 1resultado = 4total = 6
El programa continuará el procesamiento hasta encontrar otra división por cero.
(gdb) next19 resultado = calculo(valor, divisor);2: divisor = 81: valor = 10(gdb) next20 total += resultado;2: divisor = 81: valor = 10(gdb) next21 printf("%d dividido por %d é %f. A soma dos resultados é %f.\n", valor, valor-divisor, resultado,total);2: divisor = 81: valor = 10(gdb) next10 dividido por 2 é 5.000000. A soma dos resultados é 11.000000.22 divisor++;2: divisor = 81: valor = 10(gdb) next23 valor--;2: divisor = 91: valor = 10(gdb) next17 for(i = 0; i < 10; i++)2: divisor = 91: valor = 9(gdb) next19 resultado = calculo(valor, divisor);2: divisor = 91: valor = 9(gdb) nextProgram received signal SIGFPE, Arithmetic exception.0x0804835e in calculo (numero1=9, numero2=9) at gdb.c:77 resultado = numero1 / diferenca;
En el caso de este ejemplo, el programa está en la declaración de las variables como enteros no permitiendo el resto de una división.
El GDB es un poderoso depurador de programas. Lo animamos a instalar una interfaz gráfica para él para facilitar su uso. Hay otros depuradores también disponibles.
Aprenda mucho más sobre Linux en nuestro curso en línea. Puede effectuer la inscripción aquí. Si ya tiene una cuenta, o quiere crear una, basta con entrar o crear su usuario aquí.
¿Te gustó? Comparte
Eso fue solo una pieza. El curso enseña el camino completo — desde tu primer comando hasta el día del examen.