UC3M

Grado en Ing. Telemática/Sist. Audiovisuales/Sist. de Comunicaciones

Arquitectura de Sistemas

Septiembre 2017 - Enero 2018

Capítulo 16. El programa depurador o debugger

Uno de los principales problemas al desarrollar aplicaciones son los errores de ejecución. Compilar un programa no es garantía suficiente de que funciona de la manera prevista. Es más, el ciclo de desarrollo de un programa está ocupado, en su mayoría por las tareas de diagnosticar y corregir los errores de ejecución. A los errores de ejecución en programas se les suele denominar en inglés bugs (bichos).

El origen de la utilización del término bug para describir los errores en un programa es un poco confuso, pero hay dos referencias documentadas a las cuales se les suele atribuir este mérito.

La primera de ellas se puede encontrar en una carta que Edison escribió en 1878 en la que se puede leer:

It has been just so in all of my inventions. The first step is an intuition, and comes with a burst, then difficulties arise -- this thing gives out and [it is] then that Bugs -- as such little faults and difficulties are called -- show themselves and months of intense watching, study and labor are requisite before commercial success or failure is certainly reached.

Fuente: Edison to Puskas, 13 de noviembre de 1878, Edison papers, Edison National Laboratory, U.S. National Park Service, West Orange, N.J., citado en Thomas P. Hughes, American Genesis: A History of the American Genius for Invention, Penguin Books, 1989, página 75.

La invención del término se atribuye generalmente a la ingeniera Grace Hopper que en 1946 estaba en el laboratorio de computación de la universidad de Harvard trabajando en los ordenadores con nombre Mark II y Mark III. Los operadores descubrieron que la causa de un error detectado en el Mark II era una polilla que se había quedado atrapada entre los contactos de un relé (por aquel entonces el elemento básico de un ordenador) que a su vez era parte de la lógica interna del ordenador. Estos operadores estaban familiarizados con el término bug e incluso pegaron el insecto en su libro de notas con la anotación First actual case of bug being found (primer caso en el que realmente se encuentra un bug) tal y como ilustra la siguiente figura (Fuente: U.S. Naval Historical Center Photograph)

Hoy en día, los métodos que se utilizan para depurar los errores de un programa son múltiples y con diferentes niveles de eficacia. El método consistente en insertar llamadas a printf que escriben en pantalla mensajes es quizás el más ineficiente de todos ellos. En realidad lo que se precisa es una herramienta que permita ejecutar de forma controlada un programa, que permita suspender la ejecución en cualquier punto para poder realizar comprobaciones, ver el contenido de las variables, etc.

Esta herramienta se conoce con el nombre de depurador o, su término inglés, debugger. El depurador es un ejecutable cuya misión es permitir la ejecución controlada de un segundo ejecutable. Se comporta como un envoltorio dentro del cual se desarrolla una ejecución normal de un programa, pero a la vez permite realizar una serie de operaciones específicas para visualizar el entorno de ejecución en cualquier instante.

Más concretamente, el depurador permite:

  • ejecutar un programa línea a línea

  • detener la ejecución temporalmente en una línea de código concreta

  • detener temporalmente la ejecución bajo determinadas condiciones

  • visualizar el contenido de las variables en un determinado momento de la ejecución

  • cambiar el valor del entorno de ejecución para poder ver el efecto de una corrección en el programa

Uno de los depuradores más utilizados en entornos Linux es gdb (Debugger de GNU). En este documento se describen los comandos más relevantes de este depurador para ser utilizados con un programa escrito en C. Todos los ejemplos utilizados en el resto del documento se basan en el programa cuyo código fuente se muestra a continuación (y que está en el fichero gdb_use.c).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <stdio.h>
#include <stdlib.h>

#define SIZE 1000

struct data_unit 
{
  int data;
  struct data_unit *next;
};
typedef struct data_unit unit, *unit_ptr;

void function(unit_ptr table) 
{
  int y;
  for (y = 0; y < SIZE - 1; y++) 
  {
    table[y].data = y;
    table[y].next = &table[y + 1];
  }
  table[SIZE - 1].data = SIZE - 1;
}

int check(unit_ptr table) 
{
  int y;
  for (y = 0; y < SIZE; y++) 
  {
    if ((table[y].data + 1) != table[y].next->data) 
    {
      return 1;
    }
  }
  return 0;
}

int main(int argc, char **argv) 
{
  unit_ptr buf;

  buf = (unit_ptr)calloc(SIZE, sizeof(unit_ptr));
  function(buf);
  if (check(buf)) 
  {
    printf("Error detectado en tabla\n");
  }
  free(buf);

  return 0;
}

16.1. Arranque y parada del depurador

Para que un programa escrito en C pueda ser manipulado por gdb es preciso realizar una compilación que incluya como parte del ejecutable, un conjunto de datos adicionales. Esto se consigue incluyendo la opción -g al invocar el compilador:

$ gcc -Wall -g -o gdb_use gdb_use.c

Una vez creado el fichero ejecutable se invoca el depurador con el comando:

$ gdb gdb_use

Tras arrancar el depurador se muestra por pantalla un mensaje seguido del prompt (gdb):

$ gdb gdb_use
GNU gdb (GDB) 7.0-ubuntu
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
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 "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/test/gdb_use...done.
(gdb) 

En este instante, el programa depurador ha arrancado, pero la ejecución del programa gdb_use (que se ha pasado como primer argumento) todavía no. La interacción con el depurador se realiza a través de comandos introducidos a continuación del prompt, de forma similar a como se proporcionan comandos a un shell o intérprete de comandos en Linux.

Para arrancar la ejecución del programa se utiliza el comando run (o su abreviatura r). Tras introducir este comando, el programa se ejecuta de forma normal. Si se produce un error o una interrupción en la ejecución, el programa se detiene y el control vuelve al depurador, por lo que se muestra por pantalla de nuevo el prompt (gdb). Por ejemplo:

(gdb) r
Starting program: /home/test/gdb_use 

Program received signal SIGSEGV, Segmentation fault.
0x08048441 in check (table=0x804a008) at gdb_use.c:29
29          if ((table[y].data + 1) != table[y].next->data) 
(gdb) 

Cuando se produce un error, el depurador muestra el lugar en el código responsable de ese error. Es posible que el error se produzca en una rutina interna del sistema. En tal caso, no quiere decir que el error esté en tal librería, sino que se ha manifestado en la librería por causas debidas al código del programa.

Si se desea detener un programa mientras se está ejecutando se debe pulsar Crtl-C (la tecla control, y mientras se mantiene pulsada, se pulsa C). La interrupción del programa es capturada por el depurador, y el control lo retoma su intérprete de comandos. En este instante, la ejecución del programa ha sido detenida pero no terminada. Prueba de ello, es que la ejecución puede continuarse mediante el comando continue (que se puede abreviar simplemente con la letra c).

Para salir del depurador se utiliza el comando quit (abreviado por la letra q). Si se pretende terminar la sesión del depurador mientras el programa está en ejecución se pide confirmación para terminar dicha ejecución.

(gdb) q
The program is running.  Exit anyway? (y or n) y
$

El comando help muestra la información referente a todos los comandos y sus opciones. Si se invoca sin parámetros, se muestran las categorías en las que se clasifican los comandos. Si el comando va seguido del nombre de una categoría, proporciona información detallada sobre los comandos que contiene. Si se invoca seguido de un comando, describe su utilización.