08 - Debugging
08 - Debugging
19 de agosto de 2019
1. Introduction
Como dijo Edsger Wybe Dijkstra: “Si la depuración es el proceso de eliminar errores, entonces la programación
debe ser el proceso de introducirlos”. El termino “BUG” es comúnmente conocido en el ámbito de la Ciencia de
la Computación ya que con éste se hace referencia a un error en un programa. Los errores encontrados dentro de
un programa se denominan de esta manera desde la vez que el primer error fue encontrado en una computadora.
Este término fue forjado por Grace Murray Hopper [2] quien en 1947 encontró un error en una computadora Mark II
producido ¡por un bicho! (bug en inglés), ver Figura 1.
Un error en el código fuente de un programa de unas 10 o 20 lı́neas no representa ningún tipo de problema en
sı́ mismo. Se corrige y se continua con la ejecución del mismo. Se está en un real problema cuando la magnitud del
código fuente comienza a escalar de unas pocas decenas de código a cientos de miles de lineas de código fuente. Allı́
encontrar y corregir un error es realmente un tema serio.
Como puede verse en la figura 2, el proceso de compilación, si se lleva a cabo manualmente, puede ser muy
complejo. Esta complejidad se basa en que el compilador debe tener toda la información necesaria para poder realizar
su trabajo. Cuanto menos información este recibe, en forma parámetros, mas acciones generales realiza. En este
apunte se asumirá que por lo menos las siguientes opciones de compilación deberán ser especificadas :
1
Dr. Mariano Méndez Dr. Mariano Méndez
Tipos de Errores Existen dos tipos básicos de errores de programación, que se identifican según en que etapa se
encuentra un programa. Estas dos etapas son la etapa de compilación (pre-procesar,compilar,ensamblar,link-editar)
ver Figura 2 y la etapa de ejecución, a partir de cuando el programa es cargado en memoria y se ejecuta la primera
instrucción del mismo.
Según un error se encuentre en una u otra etapa, se denomina error en tiempo de compilación o error en
tiempo de ejecución. Los errores en tiempo de compilación son fácilmente detectables ya que es el compilador
quien los detecta y avisa. Por ejemplo, falta cerrar una llave , falta un punto y coma, etc. Habitualmente son errores
sintácticos. Un hecho a tener en cuenta es que durante el proceso de compilación toda referencia humana dentro de
un programa es desechada en forma automática, ya que es información innecesaria para la computadora, a menos
que se le diga al compilador que no elimine dicha información.
Por el contrario un error en tiempo de ejecución, aquel que sucede mientras se esta ejecutando el programa, es
mucho mas complejo de detectar y por ende de corregir. Dentro de estos tipos de errores existen a su vez dos tipos
mas de errores: los errores de ejecución y los errores de lógica. La diferencia entre ambos tipos de errores de
ejecución es que los primeros terminan en un final abrupto del programa, por ejemplo el clásico error de ejecución es
el de la división por 0 ver Figura 4.
Mientras que los segundos, no necesariamente terminan con la ejecución abrupta pero el resultado que obtienen
es erróneo. Por ejemplo el error que puede verse en la Figura 5 que es debido a no tener en cuenta la precedencia de
los operadores matemáticos del lenguaje .
2 Debugging 2
Dr. Mariano Méndez Dr. Mariano Méndez
Arreglar un programa con errores es el proceso por el cual se confirman, una por una, que las cosas que
el programador toma como ciertas sobre el código fuente realmente lo son. Cuando se encuentra que uno
de esos supuestos no es válido, se ha encontrado una pista del lugar en el cual se encuentra un bug o
error
¿Cual es la el valor agregado que le proporciona al principio de Confirmación una herramienta de debugging?.
La forma más rudimentaria y menos aconsejable de realizar debugging de programas es mediante la adición de
instrucciones centinelas que marcan por donde va pasando el flujo de control del programa. Lamentablemente y
aunque esto parezca mentira es una de la formas utilizada por los programadores en el ámbito laboral. En el caso de
C la idea es ir poniendo printfs de forma tal de ir viendo los valores de ciertas variables o si se llego a cierto lugar del
programa.
Esta forma de realizar debug, lleva a la generación de un ciclo :
Uno de los hechos mas paradójicos es que esta comprobado que se utiliza mucho mas tiempo para hacer debug
que para programar.
1.2. Herramientas
Las herramientas creadas para la búsqueda de errores en programas se denominan “Debuggers”. En el caso de
C el más utilizado es gdb, GNU Debugger. Cabe destacar que no es el único pero si uno de los más utilizados. Este
programa no suele estar incluido en las distribuciones de linux mas comunes y debe instalárselo según corresponda a
la distribución que se está manejando. El debugger es un programa nacido en el entorno de consola y por ende no
debemos esperar que sea amigable en algún sentido. En la actualidad muchos Entornos Integrados de Desarrollo han
incorporado a gdb a su interfaz gráfica haciendo el proceso de debugging más amigable.
3 Debugging 3
Dr. Mariano Méndez Dr. Mariano Méndez
2. Gdb
En primer lugar para poder hacer debugging de un programa es necesario que el compilador sepa que debe dejar
la mayor cantidad posible de información, que le sirva a un humano para tal fin, como por ejemplo los nombres de
las variables, las lineas de código del lenguaje de alto nivel utilizado y otras varias cosas. Gdb fue creado por Richard
Stallman en el año 1986 como parte del proyecto GNU [3]. En el manual del usuario de gdb Stallman describe cuál
es el propósito del programa [4]:
El propósito de un depurador como gdb es permitirte que veas que está pasando “adentro” de otro
programa mientras éste se está ejecutando o que está haciendo otro programa en el momento que este
falló. Gdb puede hacer cuatro tipos de cosas (más otras como soporte de aquellas) para ayudarte a
encontrar bugs en el momento:
1. Iniciar tu programa, especificando cualquier cosa que sea necesaria para afectar su comportamiento.
2. Hacer que tu programa se detenga sobre ciertas condiciones especificas.
3. Examinar qué está ocurriendo, cuando el programa se detuvo.
4. Cambiar cosas en tu programa ...
Como se dijo anteriormente (1), en el proceso de compilación toda referencia a conceptos humanos son eliminados
durante el mismo, esto es nombres, comentarios, código en el lenguaje de programación, etc. solo queda el código
maquina necesario. Para poder realizar una depuracion o debug se debe especificar al compilador que no elimine
dicha información, para ello se debe utilizar una opción que el en compilador gcc es -g.
$ gcc -Wall -g ejemplo.c -o [Link]
Una vez hecho esto el compilador creará una versión ejecutable del programa que posee información para debug.
Una vez que se tiene compilado el programa sin errores de compilación y la versión ejecutable del mismo se ha
generado correctamente se proceda a iniciar el proceso de debugging.
$ gdb [Link]
Una vez que se haya ejecutado el comando se vera en la pantalla de la consola:
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <[Link]
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".
Type "show configuration" for configuration details.
Para las instrucciones de informe de errores, vea:
<[Link]
Find the GDB manual and other documentation resources online at:
<[Link]
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Leyendo sı́mbolos desde [Link]...hecho.
(gdb)
Con lo cual se sabrá que la ejecución del proceso de depuración o debugging ha comenzado.
2.1.1. help
Este comando muestra el menú de ayuda de programa. Se ejecuta (gdb) help y despliega la siguiente pantalla:
List of classes of commands:
4 Debugging 4
Dr. Mariano Méndez Dr. Mariano Méndez
Type "help" followed by a class name for a list of commands in that class.
Type "help all" for the list of all commands.
Type "help" followed by command name for full documentation.
Type "apropos word" to search for commands related to "word".
Command name abbreviations are allowed if unambiguous.
Para ejemplificar los comandos de linea de gdb se utilizara un programa C escrito en dos archivos (main.c,
swapper.c).
main.c
1 void swap ( int * a , int * b ) {
2 int c = * a ;
3 *a = *b;
4 *b = c;
5 }
6
swapper.c
Comandos de compilación:
2.1.2. list
Este comando permite listar (hasta 10 lı́neas) del código fuente del programa que está siendo depurado por gdb,
puede cambiarse con set listsize <x cantidad de lı́neas >, su ejecución se realiza como se muestra a continuación,
que muestra diez lineas de código:
(gdb) list
Para mostrar un rango de lineas se lo hace separando el rango entre comas. Este comando tiene muchas variantes,
por ejemplo:
(gdb) list nombre_funcion
Lista el código fuente de la función especificada.
5 Debugging 5
Dr. Mariano Méndez Dr. Mariano Méndez
3 *a = *b;
4 *b = c;
5 }
(gdb)
2.1.3. quit
Este comando permite la salida del ambiente de depuración y del debugger, su ejecución se realiza como se
muestra a continuación:
(gdb) quit
(gdb) run
Este comando indicará al debbuger o depurador que debe iniciar la ejecución del programa.
2.2.2. start
Este comando inicia la ejecución del programa paso a paso desde la primera linea del del mismo.
(gdb) start
2.2.3. next
Este comando permite ejecutar la siguiente linea que corresponde en la ejecución del programa. Esto puede verse
en la Figura 8.
(gdb) next
Cabe destacar que en las funciones de la biblioteca estándar de C el comando a utilizar es next, de lo contrario se
comenzará a depurar dentro de estas funciones.
2.2.4. step
Este comando permite ejecutar la próxima instrucción. Se debe tener en cuenta que si esta instrucción es una
función la ejecución sigue dentro de la misma. Esto puede verse en la Figura 7.
(gdb) step
6 Debugging 6
Dr. Mariano Méndez Dr. Mariano Méndez
Si la variable step-mode está seteada en on gdb se detendrá en cada instrucción de funciones que no tengan
información de debug, como por ejemplo printf de la libc:
Para que no suceda eso se debe deshabilitar la opción mediante la ejecución de set step-mode off
2.2.5. finish
Esta instrucción permite finalizar la ejecución de una determinada función cuando se está dentro de la misma.
(gdb) finish
7 Debugging 7
Dr. Mariano Méndez Dr. Mariano Méndez
Watchpoint: Un watchpoint es otra posibilidad para detener la ejecución de un programa pero en este caso se
realiza cuando una determinada expresión cambia [4]. Cuando se hace referencia a una expresión es considerada
una o mas variables combinadas por un operador (i.e: i++, x+y,etc)
Catchpoint:“Un catchpoint es otro tipo de breakpoint que detiene al programa cuando un cierto tipo de evento
ocurre” [4].
3.1. break
Este comando permite crear breakpoints dentro de un programa a ser debugeado. Permite agregar un breakpoint
en una linea, el nombre de una función o en una dirección de memoria donde hay una instrucción.
(gdb) break <lugar>
El <lugar> puede ser por ejemplo el nombre de una función:
(gdb) break main
Breakpoint 1 at 0x6c2: file main.c, line 4.
(gdb)
Para crear un breakpoint en una determinada linea de un determinado archivo debe especificarse de la siguiente forma
nombre archivo:linea
(gdb) break main:8
Note: breakpoint 1 also set at pc 0x6c2.
Breakpoint 2 at 0x6c2: file main.c, line 4.
(gdb)
Cuando se lo utiliza sin ninguna referencia el comando break agrega un breakpoint en la próxima instrucción a
ser ejecutada
(gdb) break
El código funciona bien pero empieza a fallar a llegar al valor 7000, lo mas común es querer para el programa cuando
la variable cont llegue a 6999:
break if (i == 6999)
Lo mas interesante es que la condición del breakpoint puede ser cualquier condición valida en C, considerando
verdadero (valor no zero) y falso (cero)
Operadores de igualdad, desigualdad o lógicos ( ¡, ¡= , == , != , ¿, ¿= , && , —— , etc.): break 180 if string==NULL &&
Operadores de corrimiento o Bitwise ( & , — , ˆ, >> , << , etc.):
break test.c:34 if (x & y) == 1
Operadores aritmeticos ( + , - , x , / , % ): break myfunc if i % (j + 3) != 0
Funciones propias, siempre y cuando estén linkeditadas en el programa: break test.c:myfunc if ! es_primo(i)
Funciones de biblioteca, siempre y cuando estén linkeditadas en el codigo fuente: break 44 if strlen(mystring) == 0
8 Debugging 8
Dr. Mariano Méndez Dr. Mariano Méndez
3.2. Watch
Permite agregar watchpoints. Recordar que un watchpoint es una variable o mas relacionadas con un operador.
El watchpoint más simple se muestra a continuación
(gdb) watch nombreDeUnaVariable
Todos los breakpoints y watchpoints pueden ser eleiminados ejecutando:
(gdb) clear
O si se requiere eliminar un determinado breackpoint se puede hacerlo ası́:
(gdb) clear funcion
Por último para consultar sobre los breakpoints se ejecuta
(gdb) info break
(gdb) info watch
Cuando se ejecuta el comando info break se listan todos los break y watchpoints seteados:
(gdb) info breakpoint
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000000006c2 in main at main.c:4
2 breakpoint keep y 0x00000000000006c2 in main at main.c:4
3 breakpoint keep y 0x000000000000074b in swap at swapper.c:2
4 breakpoint keep y 0x000000000000074b in swap at swapper.c:2
(gdb)
main.c
9 Debugging 9
Dr. Mariano Méndez Dr. Mariano Méndez
variables locales para visualizar unicamente las variables locales se ejecuta el comando info locals
funciones para visualizar las funciones de un programa se ejecuta el comando info functions
(gdb) info functions regexp
10 Debugging 10
Dr. Mariano Méndez Dr. Mariano Méndez
tipos de datos para saber el tipo de dato en el cual se definio una variable se ejecuta el comando ptype
(gdb) ptype vector
type = int [15]
tipo de una expresión el comando whatis devuelve el tipo de una expresión tras ser evaluada.
(gdb) whatis i=1
type = int
3.2.2. bt
Este comando imprime el stack de las llamadas a las funciones y muestra cual es la que se está ejecutando
actualmente.
(gdb) bt
terceraFuncion <= current location
otraFuncion
unaFuncion
main
Estos son los comandos mı́nimos que son necesario saber para poder realizar una depuración de un programa. A
continuación se realizará un ejemplo.
4. Un Ejemplo Sencillo
El ejemplo seleccionado es :
1 # include < stdio .h >
2
3 int main () {
4 int numero ;
5 int contador =1;
6 int suma ;
7
8 printf ( " Ingrese el valor de un numero entero \ n " ) ;
9 scanf ( " %d " ,& numero ) ;
10 while ( contador <= numero ) {
11 suma = suma + contador ;
12 contador ++;
13 }
14 printf ( " El valor de la suma es: %d \ n " , suma ) ;
15 return 0;
16 }
Se compila utilizando las siguientes opciones, donde —-g— indica dejar la información para la depuración y —-Wall—
pone al compilador en la opción pedante:
$ gcc -Wall -g ejemplo.c -o [Link]
Un ejercicio interesante para ver es el de hacer la prueba de compilar el mismo programa con y sin la opción de la
información de depuración:
$ gcc -Wall ejemplo.c -o ejemplo
$ gcc -Wall -g ejemplo.c -o [Link]
luego se ejecuta el comando ls con la opción -l de la siguiente forma:
$ ls -l ejemplo*
-rwxrwxr-x 1 mariano mariano 8776 sep 18 18:06 ejemplo
-rw-rw-r-- 1 mariano mariano 271 sep 16 13:11 ejemplo.c
-rwxrwxr-x 1 mariano mariano 9960 sep 18 18:07 [Link]
puede verse que elemplo ¡[Link], con lo cual se puede validar que la información de depuración ocupa espacio
en el archivo ejecutable.
Se ejecuta el programa y el resultado que arrojó la ejecución no está por lejos al valor esperado, por ende se inicia
una depuración:
$ gdb [Link]
11 Debugging 11
Dr. Mariano Méndez Dr. Mariano Méndez
(gdb) run
Starting program: /home/mariano/Dropbox/Academico/UBA/7540-Algoritmos Y Programación I 2016/teoria/12-D
Ingrese el valor de un número entero
3
El valor de la suma es:32773
[Inferior 1 (process 26928) exited normally]
El resultado obtenido de la suma de lo 3 primeros números es 32773. Este valor dista por lejos de lo que deberı́a ser,
un 6. Volvemos a hacer otra prueba, utilizando el comando —run— y obtenemos otro valor no esperado
(gdb) run
Starting program: /home/[Link]
Ingrese el valor de un número entero
7
El valor de la suma es:32795
[Inferior 1 (process 26929) exited normally]
(gdb)
Debido a que los valores no están dentro de lo esperado vamos a iniciar una depuración paso a paso, en el cual se
crea un breakpoint inicial en la función main, linea 3:
(gdb) start
Punto de interrupción temporal 1 at 0x40064e: file ejemplo.c, line 3.
Starting program: /home/[Link]
12 Debugging 12
Dr. Mariano Méndez Dr. Mariano Méndez
Old value = 1
New value = 2
main () at ejemplo.c:10
10 while(contador<=numero){
(gdb) next
11 suma=suma+contador;
(gdb) next
13 Debugging 13
Dr. Mariano Méndez Dr. Mariano Méndez
Old value = 2
New value = 3
main () at ejemplo.c:10
10 while(contador<=numero){
(gdb) next
11 suma=suma+contador;
(gdb) next
Old value = 3
New value = 4
main () at ejemplo.c:10
10 while(contador<=numero){
(gdb) next
14 printf("El valor de la suma es:%d\n",suma);
(gdb) next
El valor de la suma es:32773
15 return 0;
(gdb) next
16 }(gdb)
14 Debugging 14
Dr. Mariano Méndez Dr. Mariano Méndez
15 Debugging 15
Dr. Mariano Méndez Dr. Mariano Méndez
split: Muestra la ventana del codigo fuente, del codigo assembly y la ventana de comandos.
reg: Si se esta en el diseño de src, esta opcion muestra ademas los registros y la ventana de comando. Si se esta en
el diseno asm o split, esta opcion muestra ademas los registros y la ventana de comando.
next : Muestra el próximo diseño de pantallas.
prev: Muestra el diseño previo de pantallas.
16 Debugging 16
Dr. Mariano Méndez Dr. Mariano Méndez
17 Debugging 17
Dr. Mariano Méndez Dr. Mariano Méndez
Nota: para ampliar el tamaño de las ventanas se debe utilizar el comando winheight, adicionando el nombre de
la ventana (src, cmd,asm y regs) y un numero entero que indique el tamaño de ampliación de la ventana ; si este es
positivo crece el tamaño de la ventana y si es negativo decrece.
winheight src +10
winheight name -10
18 Debugging 18
Dr. Mariano Méndez Dr. Mariano Méndez
a esto:
Esta aplicación web es un cascaron que embellece gráficamente a gdb. Ojo! no es otro debuger sino que es una
mascara gráfica, por decirlo de algún modo para gdb. Para poder ser instalado se deben cumplir algunos requisitos:
Sistema Operativo: Ubuntu 14.04+, macOS, Windows
Se podrá disfrutar de la comodidad del entorno grafico y ademas de la consola de gdb también.
19 Debugging 19
Dr. Mariano Méndez Dr. Mariano Méndez
En esta vista de gdbgui uno de los paneles más importantes es el que muestra la Figura ?? allı́ se ven los valores
de las variables locales , los breakpoints, los watchpoints, registros, etc. Toda la información que se necesita para la
depuración está en esa ventana
En la parte inferior sigue viéndose la consola de gdb la cual puede ser manejada por los comandas habituales:
20 Debugging 20
Dr. Mariano Méndez Dr. Mariano Méndez
(gdb) py
>import sys
>print([Link])
>end
3.6.4 (default, Dec 23 2017, 19:07:07)
[GCC 7.2.1 20171128]
(gdb)
De esta forma se puede ejecutar casi cualquier cosa en python, incluso interactuando con gdb.
21 Debugging 21
Gdb Cheat Sheet
By Mariano Mendez based on a template of Michelle Cristina de Sousa Baltazar
Referencias
[1] Norman S Matloff and Peter Jay Salzman. The art of debugging with GDB, DDD, and Eclipse. No Starch Press, 2008.
[2] Jack Minker. Historical developments in computers to the 1950s, 1998.
[3] Richard Stallman et al. The gnu project, 1998.
[4] Richard Stallman, Roland Pesch, Stan Shebs, et al. Debugging with gdb. Free Software Foundation, 51:02110–1301, 2002.
23 Debugging 23