75.40 Algoritmos y Programación I
Cátedra Ing. Pablo Guarna
Debugging
Ezequiel González Busquin
Universidad de Buenos Aires 75.40 Algoritmos y Programación I
Facultad de Ingeniería Cátedra Ing. Pablo Guarna
Técnicas de Debugging Página 2
Contenidos Contenidos.................................................................................................................... 2
Versiones...................................................................................................................... 2
Etimología .................................................................................................................... 3
Bugs más comunes........................................................................................................ 5
División por cero ...................................................................................................... 5
Uso de variables sin inicializar .................................................................................. 5
Desbordamiento (overflow) de variables numéricas .................................................. 5
Pérdida de precisión en la conversión de tipos de datos numéricos ............................ 6
Ciclos infinitos.......................................................................................................... 6
Iteraciones fuera de rango ......................................................................................... 6
Instrucciones fuera del bloque................................................................................... 6
Variables pasadas por valor en lugar de pasadas por referencia ................................. 7
Desbordamiento de pila (Stack overflow).................................................................. 7
Asignación en lugar de preguntar por igual ............................................................... 7
Otros ......................................................................................................................... 7
Debugging .................................................................................................................... 8
Debugging en Free Pascal ......................................................................................... 8
Menú Run ............................................................................................................. 9
Menú Debug ......................................................................................................... 9
Conclusiones............................................................................................................... 12
Referencias ................................................................................................................. 14
Versiones
Versión Fecha Autor(es) Descripción
1.0 Marzo de 2008 Ezequiel González Busquin
egbusquin(arroba)gmail.com
Versión inicial
Universidad de Buenos Aires 75.40 Algoritmos y Programación I
Facultad de Ingeniería Cátedra Ing. Pablo Guarna
Técnicas de Debugging Página 3
“Si una secuencia de pasos puede fallar,
lo hará en la peor secuencia posible”
Ley de Murphy
Etimología
Si alguien busca la definición de bug en un diccionario de inglés descubrirá que
significa insecto o bicho. E inmediatamente se preguntará qué tienen que ver los
insectos con la programación.
Si bien el término ya se utilizaba en otras áreas de la ingeniería como ser la aeronáutica,
para la informática un bug es cualquier error, mal funcionamiento o falla que produce
que el software no funcione como se esperaba. La mayoría de los bugs provienen de los
errores cometidos al programar, aunque algunos otros pueden provenir de fallas en el
diseño y los menos de la conversión que los compiladores hacen del código fuente a
código de máquina.
Cuando se construyeron las primeras computadoras, que ocupaban cuartos enteros –
como ser la ENIAC (Fig. 1), durante los años 40 en Estados Unidos– la circuitería
consistía miles de elementos tales como relés, resistencias, capacitares y tubos de vacío.
Fig. 1: Computadora ENIAC
Los relés (Fig. 2) son llaves que abren y cierran circuitos eléctricos –tales como las
llaves de luz que utilizamos hoy en día—cuya activación se hace por medio de una
Universidad de Buenos Aires 75.40 Algoritmos y Programación I
Facultad de Ingeniería Cátedra Ing. Pablo Guarna
Técnicas de Debugging Página 4
corriente. Cuando esta corriente pasa a través de un electroimán se mueve una armadura
que abre o cierra el circuito.
Fig. 2: Relé abierto y cerrado
Las resistencias y tubos de vacío producían calor, lo cual atrae a los insectos. Si los
insectos morían entre los dos contactos de un relé, el circuito podía cerrarse (o no llegar
a hacerlo) lo cual producía resultados inesperados e incorrectos.
Fig. 3: Esta polilla es posiblemente el primer bug detectado en una computadora, encontrada por Grace
Hopper en la computadora Mark II de la Universidad de Harvard, el 9 de Septiembre de 1947.
Fig. 2: Relé abierto y cerrado
Universidad de Buenos Aires 75.40 Algoritmos y Programación I
Facultad de Ingeniería Cátedra Ing. Pablo Guarna
Técnicas de Debugging Página 5
Bugs más comunes
Entre los bugs más comunes que cometemos podemos encontrar
División por cero
La división por cero no está definida como operación matemática. Es por esto que si
intentamos dividir una variable por otra cuyo valor es cero, el programa se cuelga. La
ejecución es finalizada automáticamente.
Var a, b: integer;
Writeln('Ingrese los valores de A y B');
Readln (a, b);
Writeln (a, ' dividido ', b, ' resulta ser ', a / b );
{¡si b=0 se cuelga!}
Uso de variables sin inicializar
No siempre las variables toman un valor inicial, esto depende del lenguaje, del
compilador e incluso de opciones del compilador que son modificables por el usuario.
Una variable sin inicializar contiene “basura”, es decir, su valor es cualquier valor, dado
por la interpretación de los bytes en memoria que esa variable ocupa.
Var cantidad, acumulador: integer;
Repeat
Writeln (‘Ingrese cantidades, 0 para terminar’);
Readln (cantidad);
acumulador := acumulador + cantidad;
{¿inicialmente cuánto vale acumulador?}
Until cantidad = 0;
Desbordamiento (overflow) de variables numéricas
Dependiendo de la plataforma donde se esté trabajando y del tipo de variable, las
variables numéricas tienen un rango posible de valores. Por ejemplo un byte utiliza
siempre 8 bits. Con esta cantidad de bits se pueden representar hasta 28 = 256 cifras
distintas. Si se intenta almacenar un valor mayor en un byte se desborda su capacidad y
el valor resultante no es el esperado.
Existen opciones en el compilador que permiten que si se detecta un desbordamiento de
un entero se aborte la ejecución del programa.
Universidad de Buenos Aires 75.40 Algoritmos y Programación I
Facultad de Ingeniería Cátedra Ing. Pablo Guarna
Técnicas de Debugging Página 6
Var a, b, c: byte; {byte: números enteros de 0 a 255}
a := 250;
b := 10;
c := a + b;
writeln (a, ‘+’, b, ‘=’, c);
Pérdida de precisión en la conversión de tipos de datos numéricos
Este error sucede generalmente al asignar un valor con parte decimal a una variable
entera. El compilador automáticamente efectúa un redondeo (acercar al entero más
próximo) ó un truncamiento (uso sólo de la parte entera).
Número original Redondeado Truncado
5,24 5 5
2,99 3 2
4,5 5 4
7 7 7
Ciclos infinitos
Condiciones de corte de un ciclo iterativo que jamás se cumplen hacen que el ciclo
nunca termine y el programa se cuelgue. Si bien el programa sigue ejecutándose, jamás
terminará ni saldrá del ciclo, con lo cual para el usuario el programa ha dejado de
funcionar.
a := 1;
while (a = 1) do
begin
… {no se modifica el valor de a en el ciclo}
end;
Iteraciones fuera de rango
Al recorrer todos los elementos de un arreglo, lo más normal es utilizar un ciclo y una
variable de control que indica qué elemento del arreglo es el actual. Si la variable de
control supera el límite superior del arreglo se intenta acceder a una posición inexistente
del mismo. Este error es muchas veces detectado y el programa se interrumpe, pero
puede que esto no suceda y que se acceda a posiciones “basura” del arreglo.
Instrucciones fuera del bloque
Por errores de sangría o indentación (castellanización del término inglés indentation) en
el código puede confundirse qué sentencias están dentro o fuera de un bloque.
Universidad de Buenos Aires 75.40 Algoritmos y Programación I
Facultad de Ingeniería Cátedra Ing. Pablo Guarna
Técnicas de Debugging Página 7
Código original Código correcto var a, b;
Readln(a, b);
while (a < b) do
a := a + 1;
b := b – 1;
var a, b;
Readln(a, b);
while (a < b) do
begin
a := a + 1;
b := b – 1;
end;
Variables pasadas por valor en lugar de pasadas por referencia
Las variables que se pasan como parámetro a procedimientos y funciones por valor no
son modificadas dentro del procedimiento o función invocados, sino que se utiliza una
copia. Si se deseaba hacer modificaciones de las variables dentro del procedimiento
debe indicarse que su pasaje se realiza por referencia.
Desbordamiento de pila (Stack overflow)
El uso de procedimientos y funciones no es gratuito a nivel uso de memoria: Cada
invocación hace que el programa almacene desde dónde se invoca a la subrutina. Luego
se reserva la memoria necesaria para el uso de las variables locales. Al salir de la
subrutina la memoria de variables locales se libera y se retorna al lugar desde donde se
invocó.
Por consiguiente, el uso de una gran cantidad de llamadas a subrutina y de variables
locales puede llegar a saturar el tamaño de la memoria donde se almacena esa
información, denominada “pila”o “stack” de llamadas, provocando un desbordamiento
y un posterior cuelgue.
Asignación en lugar de preguntar por igual
Es un problema típico en C en y los lenguajes que derivan su sintaxis de ellos. Dado que
en C el operador de asignación es = y el de comparación por igual es == es común la
confusión y el incorrecto funcionamiento del programa.
Otros
Existen otros problemas muy comunes que son los principales bugs de la programación,
pero que dejamos fuera de análisis. Sin ser abarcativos y sólo para mencionar algunos,
diremos que están aquellos relacionados al uso de memoria dinámica:
• Referenciar un puntero nulo
• Acceso a memoria no solicitada: violación de acceso
• Desbordamiento de búfers (Buffer overflow)
• Pérdida de memoria dinámica (Memory leaks)
Universidad de Buenos Aires 75.40 Algoritmos y Programación I
Facultad de Ingeniería Cátedra Ing. Pablo Guarna
Técnicas de Debugging Página 8
Y aquellos que suceden cuando hay concurrencia:
• Abrazos mortales (Deadlocks)
• Condiciones de carrera (Race conditions)
Debugging
El debugging o depuración en informática se refiere a las acciones llevadas a cabo para
eliminar bugs del software.
Existen diversas técnicas de debugging, así como también herramientas específicas de
software denominados debuggers. Los debuggers están disponibles para casi todos los
lenguajes de programación y plataformas de desarrollo. Muchos de ellos se encuentran
integrados junto al compilador y un editor de texto en lo que se denomina IDE
(Integrated Development Enviroment, Entorno Integrado de Desarrollo) con lo cual
desde una misma aplicación o ventana se puede codificar un programa, compilar y
depurar.
El debugging del software es fundamental. Es necesario para entender cómo funciona
un programa y un arma poderosísima para la detección de problemas. Las técnicas van
desde el uso de banderas (por ejemplo, escribir por pantalla “Pasé por acá” para ver si
el flujo de un programa alcanza una determinada línea, “La variable X vale 3” para
visualizar el valor de una variable en un momento dado, etc) hasta el uso de breakpoints
y watches.
Debugging en Free Pascal
Una vez que tenemos el programa codificado, guardamos este código fuente del
programa como un archivo con extensión “.pas”. Luego, En Compile > Compile
invocamos al compilador de nuestro código fuente. El compilador se encargará de
transformar nuestro código fuente en código máquina o código ejecutable, que es el
código que entiende la computadora. Si tenemos errores, se desplegará una ventana
donde se nos indica cuál es, en qué línea del archivo fuente ocurre y una breve
descripción del mismo. El compilador puede de esta misma forma reportar advertencias
(warnings) sobre posibles errores cometidos. Un compilador por lo general toma una
decisión ante una advertencia que puede no resultar en lo que el programador quería, y
es por eso que la advertencia se genera. Haciendo ajustes en el código pueden
eliminarse (y es altamente recomendable) todas las advertencias de un programa.
En Free Pascal tenemos dos menúes que son de especial interés a la hora de hacer
debugging de un programa. Ellos son Debug y Run.
Universidad de Buenos Aires 75.40 Algoritmos y Programación I
Facultad de Ingeniería Cátedra Ing. Pablo Guarna
Técnicas de Debugging Página 9
Menú Run
Fig. 4: El menu Run de Free Pascal
Run (correr): Lanza a correr, ejecuta el programa. El mismo corre desde un principio
ejecutando cada sentencia del mismo una tras otra.
Step over (Pasar por encima): Ejecuta el programa desde la primera sentencia o desde
la sentencia actual, de a una sentencia a la vez. Si se encuentra una invocación a
subrutirna, no se entra en la misma, se la ejecuta como si fuera una sentencia común.
Trace into (Seguir adentro): Es igual que la opción anterior, pero ante una subrutina se
salta a su primera sentencia.
Goto cursor (Ir hasta el cursor): Ejecuta el programa hasta llegar a la línea sobre la cual
está posicionado el cursor actualmente.
Until return (Hasta un return): Ejecuta la subrutina hasta alcanzar la sentencia return o,
como caso particular, al programa principal hasta su finalización.
Program reset (Reinicio del programa): Inicia la ejecución del programa nuevamente
desde el principio.
Menú Debug
Output (Salida): Muestra la salida por pantalla del programa. Es lo que se vería si el
programa no se estuviera ejecutando desde el IDE.
User screen (Pantalla del usuario): Muestra la salida del programa en otra ventana del
IDE.
Universidad de Buenos Aires 75.40 Algoritmos y Programación I
Facultad de Ingeniería Cátedra Ing. Pablo Guarna
Técnicas de Debugging Página 10
Fig. 5: El menú Debug de Free Pascal
Add Watch (Agregar Visor): Agrega una variable para ser visto su valor en cada
instante.
Watches (Visores): Despliega la lista de variables bajo inspección.
Fig. 6: Un programa en Free Pascal con la ventana de watches. Se puede visualizar el valor de las
variables al haber llegado la ejecución a la línea actual. En rojo, el valor de la variable que se modificó en
la sentencia inmediata anterior.
Breakpoint (Punto de detención): Agrega a la sentencia bajo el cursor como punto de
detención en la ejecución del programa.
Universidad de Buenos Aires 75.40 Algoritmos y Programación I
Facultad de Ingeniería Cátedra Ing. Pablo Guarna
Técnicas de Debugging Página 11
Breakpoint List (Lista de Puntos de detención): Despliega una ventana con el listado
de breakpoints.
Fig. 7: Ventana con la lista de breakpoints del programa de la Fig. 6. El breakpoint de la línea 11 tiene la
condición de que el valor de la variable c sea menor que el de la a.
• Type (tipo): Existen diversos tipos de breakpoints, el más común es el file-line
que detiene la ejecución al llegar ésta a una sentencia determinada.
• Status (estado): Cada breakpoint puede activarse (enabled) o desactivarse
(disabled). Los breakpoints desactivados son ignorados, como si no existieran.
• Position (posición): Indica la línea y el archivo de código fuente donde está el
breakpoint.
• Conditions (condiciones): Pueden agregarse condiciones al breakpoint. Al
alcanzarse la sentencia donde está el breakpoint se evalúan las condiciones. Si
las mismas se cumplen, se detiene la ejecución del programa, en caso contrario
la ejecución continúa como si el breakpoint no existiese.
Evaluate (Evaluar): Al estar el programa en ejecución, permite evaluar expresiones
haciendo uso de las variables con visibilidad en el ámbito actual (como ser, variables
globales y las variables locales de la subrutina actual).
Call Stack (Pila de llamadas): Muestra el apilamiento de las invocaciones a subrutinas,
indicando desde qué línea se hizo la invocación, el nombre de la subrutina invocada y
en caso de existir, los parámetros actuales de invocación.
Universidad de Buenos Aires 75.40 Algoritmos y Programación I
Facultad de Ingeniería Cátedra Ing. Pablo Guarna
Técnicas de Debugging Página 12
Fig. 8: Pila de llamadas y ventana de watches en un pequeño programa
Existen otros comandos más avanzados y de uso no tan frecuente, los cuales son:
Disassemble (Desensamblar): Muestra el código assembler generado por el compilador
a partir del código fuente y permite su depuración, ejecución instrucción a instrucción,
etc.
Registers (Registros): Permite visualizar el estado de los registros de la CPU (Central
Processing Unit, Unidad Central de Procesamiento).
Floating point unit (Unidad de Punto Flotante): Permite visualizar el estado de la
unidad de punto flotante, que es un hardware especialmente diseñado para realizar
operaciones de punto flotante.
Vector unit (Unidad vectorial): Permite visualizar el estado de la unidad de cálculos
vectoriales, hardware específico para el manejo de operaciones vectoriales, de especial
interés en aplicaciones con gráficos 3D.
GDB window (Ventana GDB): GDB es el “GNU Debugger”. Es un debugger que
funciona en diversas plataformas y con distintos lenguajes de programación. GDB no
tiene una interfaz, sino que recibe los comandos en este caso desde la interfaz de Free
Pascal. Con esta ventana pueden darse a GDB instrucciones directas, en caso de ser
necesario.
Conclusiones
Los bugs del software son caros: su detección es complicada, insume horas de trabajo.
Su corrección puede también ser compleja, porque un pequeño cambio en un programa
puede tener consecuencias terribles alejadas en el tiempo y en el código. Desde hacer
Universidad de Buenos Aires 75.40 Algoritmos y Programación I
Facultad de Ingeniería Cátedra Ing. Pablo Guarna
Técnicas de Debugging Página 13
fallar una misión de la NASA hasta hacer que un cliente pierda sus datos y deje de
comprar nuestros productos, los bugs afectan económicamente a los proyectos y pueden
hacerlos fracasar. Un minucioso control, buenas prácticas de programación y un testing
eficiente del software hacen disminuir el riesgo.
Los bugs del software pueden ser mortales: han ocurrido casos en donde errores en la
programación de aparatos de medicina han provocado la muerte a pacientes.
Los bugs del software siempre están: no existe la posibilidad de armar un sistema
informático sin bugs: al crecer el sistema aumenta la complejidad y pueden darse
condiciones que no estuvieron contempladas desde un comienzo. Los bugs se producen
generalmente por errores humanos, nadie es perfecto y todos cometemos errores.
Es importante cada vez que se utiliza un nuevo entorno, un nuevo lenguaje de
programación, buscar, aprender y utilizar las herramientas para debug disponibles. Cada
vez hay más y mejores debuggers (¡incluso hay algunos que permiten hacer un debug
hacia atrás en el tiempo de ejecución!) y seguramente en el futuro más y mejores
aplicaciones harán cada vez más eficiente la erradicación de bugs.
Universidad de Buenos Aires 75.40 Algoritmos y Programación I
Facultad de Ingeniería Cátedra Ing. Pablo Guarna
Técnicas de Debugging Página 14
Referencias
Imagen de la portada.
http://www.stevenbrown.ca/blog/archives/category/projects
Software bug.
http://en.wikipedia.org/wiki/Software_bug
GDB (GNU Debugger).
http://es.wikipedia.org/wiki/GNU_Debugger
Técnicas de Depuración (en Turbo Pascal 7.0). Augusto Vega.
http://www.geocities.com/algyprog_lopez/Depuracion.zip
Los peores bugs de la historia.
http://axxon.com.ar/not/156/c-1560164.htm
Debugging hacia atrás en el tiempo. Bil Lewis, Google TechTalks 2006.
http://video.google.com/videoplay?docid=3897010229726822034