GNU GDB

ARCO

Revision History
Revision 1.111 / 10 / 2002Revised by: Fernando Sancho
Formateado como DocBook.
Revision 1.010 / 03 / 2000Revised by: David Villa
Inicial.

Guia básica para el uso de GDB.


Table of Contents
1. Introducción
2. Depurando
3. Ejemplo de una Sesión de Depuración
4. Depuración Postmortem
4.1. Ejemplo de depuración postmortem
4.2. Configuración de los ficheros core
5. Opciones
6. Referencias

1. Introducción

Un depurador o "debugger" permite observar cómo funciona un programa en desarrollo durante su ejecución. GDB además permite depurar programas que terminaron con un fallo (depuración postmortem) e incluso procesos que ya se encuentran en ejecución.


2. Depurando

Para sacar el máximo partido a gdb es necesario que el programa que vamos a depurar esté compilado con información simbólica, es decir, en el código ejecutable del programa deben aparecer los nombres de las variables que lo conforman, funciones, tipos, números de línea, etc.

Para compilar un programa con información simbólica es necesario pasar la opción -g al compilador. Veamos un ejemplo:

	$ gcc -g -o hola hola.c
      

Si quiere incluir información de depuración adicional, útil exclusivamente para gdb, la opción será -ggdb en lugar de -g.

Para invocar a gdb simplemente hay que escribir gdb en la línea de comandos, aunque normalmente, se indica en la llamada el nombre del programa que queremos depurar.

	$ gdb hola
      

Una vez arrancado, gdb carga el binario y lo deja dispuesto para comenzar su ejecución. En este momento aparecerá en pantalla un indicador como este:

	(gdb)
      

Eso significa que gdb está a la espera de un comando (llamados gdb-commands u órdenes-gdb). Para obtener una lista de dichas órdenes y una pequeña explicación de cada una de ellas se usa help. Veremos una lista similar a la siguiente:


	(gdb) help
	List of classes of commands:
	aliases -- Aliases of other commands
	breakpoints -- Making program stop at certain points
	data -- Examining data
	files -- Specifying and examining files
	internals -- Maintenance commands
	obscure -- Obscure features
	running -- Running the program
	stack -- Examining the stack
	status -- Status inquiries
	support -- Support facilities
	tracepoints -- Tracing of program execution without stopping the
	program
	user-defined -- User-defined commands

	Type "help" followed by a class name for a list of commands in
	that class.
	Type "help" followed by command name for full documentation.
	Command name abbreviations are allowed if unambiguous.
      

Cada categoría engloba órdenes y subcategorías. Para obtener información sobre una categoría concreta utiliza la orden help seguida del nombre de la categoría en cuestión, por ejemplo:

	(gdb) help running
      

A continuación aparecen las órdenes básicas de gdb imprescindibles para depurar un programa.

break [fichero:]funcion

Inserta un punto de ruptura al comienzo de la función indicada, también se puede utilizar un número de línea. Cuando la ejecución del programa alcanza el punto establecido, gdb lo detiene y se solicita una nueva orden.

run [arglist]

Ejecuta el programa que queremos depurar (el que anterioremente pasamos en la línea de comandos). Se puede pasar una lista de argumentos al programa como se haría si se invocara directamente.

bt

Backtrace o traceo inverso, muestra la pila de ejecución del programa.

print <expr>

Muestra el valor de una variable o expresión que pueda evaluarse en tiempo de ejecución.

continue

Continúa ejecutando el programa después de haberse producido cualquier tipo de parada.

next

Ejecuta la siguiente línea del programa. Para ello, el programa debe estar parado (stopped) pero en ejecución. Permite hacer un traceo a nivel, es decir, ante una llamada a función la ejecutará de forma atómica y después seguirá con la siguiente sentencia.

step

Ejecuta la siguiente línea del programa, pero en este caso realiza un traceo contínuo, es decir, ante una llamada a función la siguiente sentencia a ejecutar será la primera sentencia de dicha función.

list

Imprime en pantalla la siguiente línea que se va a ejecutar y su contexto, normalmente las 5 líneas anteriores y las 5 posteriores.

quit

Sale de gdb.

Cualquier orden de gdb puede ser abreviada siempre que contenga el texto suficiente como para no ser confundida con otra de nombre similar. Es decir, si se introduce n, gdb sabe que se refiere a next pues no hay ninguna otra orden que comience por n.


3. Ejemplo de una Sesión de Depuración

A continuación se muestra el listado de un programa que tiene errores de ejecución y después veremos como corregirlos con el depurador.


      #include <stdio.h>
      #include <ctype.h>

      char* Mstrupr(char* szCad); 

      int main()
      {
         char szCadena[] = "Esto es una cadena";
         
         printf("%s\n", Mstrupr(szCadena));
         printf("%s\n", Mstrupr("Esto es otra cadena"));

         return 0;
      }

      char* Mstrupr(char* szCad) 
      {
         int i;
      
         for (i=0; szCad[i]; i++) 
         szCad[i] = toupper(szCad[i]);

         return szCad;
      }
    

Guardamos el listado anterior en un fichero llamado sample.c y lo compilamos con la orden:


      $ make sample CC=gcc CFLAGS="-ggdb -Wall -ansi -pedantic"
      gcc -ggdb -Wall -ansi -pedantic sample.c -o sample 
      $
    

Según el compilador no hay error alguno en el código, pero al ejecutar, esto es lo que sucede:


      $ ./sample
      ESTO ES UNA CADENA
      Segmentation Fault (core dumped)
      $
    

Para localizar el problema utilizamos gdb con el programa del siguiente modo:


      $ gdb sample
      .
      .
      (gdb) break main
      Breakpoint 1 at 0x8048348: file sample.c, line 8
      (gdb) run
      Breakpoint 1, main() at sample.c:8
      8     char szCadena[] = "Esto es una cadena";
      (gdb) next
      10    printf("%s\n", Mstrupr(szCadena));
      (gdb) next
      ESTO ES UNA CADENA
      12    printf("%s\n", Mstrupr("Esto es otra cadena"));
      (gdb) step
      Mstrupr(szCad=0x804856f "Esto es otra cadena") at sample.c:17
      21    for (i=0; szCad[i]; i++)
      (gdb) next
      22       szCad[i] = toupper(szCad[i]);
      (gdb) next
      Program received signal SIGSEGV, Segmentation fault
      0x80484e1 in Mstrupr (szCad=0x08048e1 in Mstrupr (szCad=0x0804856f
      "Esto es otra cadena") at sample.c:19
      22       szCad[i] = toupper(szCad[i]);
      (quit) next
      Program terminated with signal SIGSEGV, Segmentation fault
      The program no longer exists 
      (gdb) quit
      $
      

Como vemos, el programa recibe la señal SIGSEGV cuyo significado es "fallo de segmentación" o "violación de segmento", eso quiere decir que nuestro programa ha intentado acceder a memoria que NO le ha sido asignada por el sistema operativo.

El punto en el que el programa es abortado corresponde con la asignación:


      szCad[i] = toupper(szCad[i]);	
    

que corresponde a la función Mstrupr( ), es decir, el valor a la izquierda de la asignación es una localización de memoria que no podemos modificar. Dicha posición de memoria es parte de la cadena "Esto es otra cadena", concretamente el primer carácter.

Pero... ¿por qué no falló la función la primera vez que se llamó y sí la segunda? La respuesta es evidente a la vista del código: En la primera llamada, el argumento es una cadena estática, mientras que en la segunda llamada el argumento es un literal, y como es lógico un literal no puede modificarse porque no es una variable.

De modo que, para solucionar el problema debemos almacenar el literal en otra cadena igual que se hace con la primera. El código modificado quedaría como sigue (la función Mstrupr( ) no sufre ninguna modificación).


      int main()
      {
         char szCadena[] = "Esto es una cadena";
         char szOtraCadena[] = "Esto es otra cadena";
      
         printf("%s\n", Mstrupr(szCadena));
         printf("%s\n", Mstrupr(szOtraCadena));

         return 0;
      }
    

Al compilar y ejecutar el nuevo código obtenemos:


	$ ./sample
	ESTO ES UNA CADENA
	ESTO ES OTRA CADENA
	$
      


4. Depuración Postmortem

Cuando un programa comete una operación no permitida: división por cero, acceso a un fichero inexistente, acceso a memoria que no le pertenece, utilización de un puntero nulo, etc. el proceso que instancia el programa muere, mejor dicho, el sistema operativo lo mata; y en ese momento el sistema vuelca el contenido de la memoria que ocupaba el programa a un fichero al que le da por nombre core. Por eso, cuando un programa falla, el sistema muestra el motivo del fallo e indica que se creó el core. Por ejemplo:

      Segmentation fault (core dumped)
    

Un archivo core es de gran ayuda ya que permite reproducir y eliminar errores esporádicos, es decir, que ocurren en circunstancias muy específicas y difíciles de reproducir, como ocurre por ejemplo en programas de comunicaciones.

Con gdb es posible analizar un core; para ello se le invoca de la siguiente forma:

      $ gdb <fichero> core
    

siendo <fichero> el fichero ejecutable que generó el core, es importante que sea la misma versión del programa y no otra. De este modo gdb es capaz de hacer una correspondencia entre el código objeto del programa y la imagen de memoria almacenada en el core y determinar exactamente en que punto falló el programa y porqué.


4.1. Ejemplo de depuración postmortem

En este apartado depuraremos el código original del ejemplo anterior, pero esta vez haremos uso del fichero core que el sistema ha generado. De esta forma gdb nos informará del lugar exacto donde se produjo el fallo sin tener que tracear el programa.


	$ gdb sample core
	.
	.
	Core was generated by `/home/user/sample'.
	Program terminated with signal 11, Segmentation Fault.
	Reading symbols from /lib/libc.so.6...done.
	Loaded symbols for /lib/libc.so.6...done.
	Reading symbols from /lib/ld-linux.so.2...done.
	Loaded symbols for /lib/ld-linux.so.2...done.
	#0    0x80484e1 in Mstrupr(szCad=0x804856f "Esto es otra cadena"
	at sample.c:24
	22         szCad[i]=toupper(szCad[i]);
	(gdb) quit
	$
      


4.2. Configuración de los ficheros core

La creación de los ficheros core normalmente corre a cargo de la shell. En la shell bash, el tamaño límite del fichero core se fija por medio de la directiva ulimit con la opción -c, ya que ulimit sirve para definir muchas otras cosas. Así pues, si no se desea limitar el tamaño de los core debe ejecutar:

	$ ulimit -c unlimited		 
      


5. Opciones

GDB acepta varios modificadores por línea de comando relativos a su comportamiento y al paso de información y archivos. Estos son algunos de ellos:

-help

Lista todas las opciones con pequeñas explicaciones.

-c <fichero>

Usa <fichero> como si fuera un core.

-x <fichero>

Ejecuta las órdenes-gdb contenidas en el fichero <fichero>.

-q

Modo "quiet". No imprime los mensajes introducidos ni de copyright.


6. Referencias