Depurar un programa (C, C++, ...)

Ante las preguntas realizadas por Oscar sobre el uso de GDB y problemas con el famoso "segmentation fault", me he decidido a hacer un pequeño manual de GDB y otras herramientas útiles para el depurado. Seguramente se me queden muchas cosas en el tintero, pero todos sabemos que las recetas no están cerradas cuando se publican. Se agradecerán todos los comentarios.

gdb

GDB o el GNU DeBugger, es el programa usado para depurar los programas en GNU/Linux. No es el único, pero sí uno de los más potentes, sobre todo para programas escritos en C y C++.

Para poder utilizar todas sus características, será necesario que, durante la compilación, dejemos información de depurado en el ejecutable. Si estamos utilizando gcc o cc, esto se hace mediante la opción -ggdb.

¡Ya tenemos nuestro fichero preparado para ser depurado! Ahora basta utilizar la orden:

$ gdb ejecutable

y se nos abrirá la línea de órdenes de gdb (todo en modo texto, claro).

Hay que tener en cuenta una cosa: cuando un programa falla, puede deberse a un error o bien a los efectos colaterales de un error. Me explico con un ejemplo: si tenemos un puntero no inicializado y le asignamos un valor, puede que dé error o puede que no (depende de dónde esté apuntando); por ello puede dar el problema más adelante (esto se conoce como el "Síndrome del puntero loco" entre los coleguitas :-D).

Por ello, es interesante no pensar que gdb te dice "aquí está el problema", sino "el problema está por aquí", que es muy diferente.

Uso de GDB

gdb es bastante complejo, y sólo detallaré aquí los pasos iniciales para debugar el típico "hola mundo".

Primero, cargaremos el ejecutable compilado con las opciones de debug "-ggdb":

gdb ejecutable

Se nos inicia la línea de órdenes gdb. Lo primero, será poner un punto de ruptura ("Breakpoint"). Se puede poner en una línea determinada o bien en una función. Dado que nuestro programa "hola mundo" es pequeñín y no sabemos dónde pusimos el main, plantamos la ruptura en el propio main:

gdb> br main

Y lo siguiente es ejecutar el programa:

gdb> r

Así que se parará en la primera instrucción del main. Ahora podemos utilizar una serie de argumentos que explicaré brevemente:

  • p "print" imprime el valor de una variable.
  • d "display" igual que print, pero se muestran cada vez que pulsemos "s" o "n".
  • n "next" Continúa con la siguiente instrucción de la función actual
  • s "step" Continúa con la siguiente instrucción de la que se tiene información de depurado (si hay una llamada a función, entrará dentro).
  • c "continue" continúa la ejecución del programa.
  • h "help" muestra la ayuda

GNU/Emacs

GDB sólo es muy poco amigable, así que yo prefiero utilizarlo junto con GNU/Emacs. Gano dos cosas, principalmente: ver el código y coloreado del mismo.

Para ello, basta con pulsa ESC+x (o M+x) y escribir la orden "gdb". Emacs nos pedirá el resto de la línea de órdenes, donde pondremos el nombre del ejecutable y del core (si es que existe). Cuando lo tengamos, nos aparecerá una pantalla tipo terminal igual que si hubiéramos iniciado gdb en una consola, pero cuando comencemos a trazar el código se nos dividirá la pantalla en dos, mostrando en el otro lado el código (con una flechita muy molona que nos cuenta por dónde vamos).

ddd

Aunque muchas veces uso Emacs con gdb, debo admitir que mi favorito es ddd. Su instalación es realmente compleja:

# apt-get install ddd

Seguro que los de Windows lo tienen más fácil. Bien. El caso es que la pantalla se divide en tres partes (si no es así mira las opciones del menú "View") en las que tenemos, de arriba abajo: datos, código y terminal gdb. Precioso. Si cargamos un programa con opciones de debug ("-ggdb") podremos ver cómo se mueven los punteritos, cómo cambian las direcciones a las que apuntan, cómo se construyen las estructuras, ... Vamos, muchas cosas que nos cuentan pero no se ven ;)

Y, a parte de los menús TK pseudo-molones, siempre tenemos la parte de abajo con un gdm normal.

NOTA He observado un comportamiento extraño al pulsar con el ratón en la zona de datos o en la de GDB, ya que toma las teclas que pulses como entradas de la pantalla GDB, pero no funcionará el intro. Sin embargo, si pulsas directamente sobre la ventana de código, te funciona todo igual y el intro sí va. No me preguntéis porqué es así, pero ocurre. Seguramente esto sea así porque lo tenéis configurado de esta manera. Para solucionarlo, tenéis que ir a "Edit"->"Preferences", y en la pestaña "Startup" veréis que pone "Keyboard focus a)Point to Type b) Click to Type". Os podéis imaginar: seleccionáis si queréis que el foco se obtenga cuando el puntero del ratón está justo encima (bastante molesto) o os hace falta hacer click para dar el foco (molesto, pero no tanto).

COREs

Una gran ayuda frente a los "segmentation faults" son los archivos COREs. Te preguntarás dónde andan, claro :-D pues no andan por ningún sitio porque los tienes desactivados. Para activarlos, sólo tienes que hacer:

$ ulimit -c unlimited

Cuando vuelvas a ejecutar el programa te pondrá "segmentation fault (core dumped)". Genial. Ahora ejecuta gdb de la siguiente manera:

$ gdb ejecutable core

y puedes ver dónde cascó exactamente con la orden de gdb "whereis", que te mostrará la pila. Acuérdate de volver a quitar el límite de los cores o te ocuparán espacio en el disco duro (y no poco):

$ ulimit -c 0

Más información

  • Páginas "man" de gdb, ddd, ...

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de david.villa

Manuales

En la sección de manuales hay una pequeña receta sobre GDB con un ejemplo de código, que también puede servir de ayuda.

No soy portavoz de ningún colectivo, grupo o facción. Mi opinión es personal e intransferible.