next up previous contents index
Next: Utiliser gdb sous Emacs Up: Outils pour la programmation Previous: Outils pour la programmation

     
Déboguage avec gdb

Le programme gdb permet d'aider le programmeur à trouver les bogues dans un programme C.

Voici un exemple d'utilisation de gdb : un programme nommé bogue qui plante lorsqu'il lit une entrée au clavier. Voici le code source du programme bogue :

#include <stdio.h>

static char buf[256];
void lit_entree (char *s);

main ()
{
  char *entree = NULL;

  lit_entree (entree);
  printf ("Vous avez tape : %s\n", entree);
}

void 
lit_entree (char *s)
{
  printf ("Commande : ");
  gets (s);
}

Quand on lance ce programme, il plante si on entre une chaîne au clavier. Par exemple si on tape ``test'' :

Commande : test
Segmentation fault

Le débogueur gdb va nous aider. Il faut d'abord compiler le programme avec l'option -g pour le déboguage, et sans optimisations (utiliser éventuellement -O0, pour supprimer toute optimisations) :

gcc -g -o bogue bogue.c

Maintenant, on peut lancer gdb :

gdb bogue

On peut lancer le programme par la commandes run :

Commande : test

Program received signal SIGSEGV, Segmentation fault.
0x4002fbe8 in _IO_gets (buf=0x0)
(gdb)

La commande where permet de déterminer où le programme s'est arrêté :

#0  0x4002fbe8 in _IO_gets (buf=0x0)
#1  0x10e1 in lit_entree (s=0x0) at bogue.c:17
#2  0x10a3 in main () at bogue.c:11
(gdb)

La commande list permet d'inspecter les lignes de code suspectes. Par exemple, pour inspecter autour de la ligne 17 :

list bogue.c:17

12      }
13
14      void lit_entree(char *s)
15      {
16      printf("Commande : ");
17      gets(s);
18      }

Il semble que l'appel de lit_entree pose problème. Pour s'en assurer, on peut regarder où l'appel a été émis (ligne 11) :

list bogue.c:11

6
7       main ()
8       {
9         char *entree = NULL;
10
11        lit_entree (entree);
12        printf ("Vous avez tape : %s\n", entree);
13      }
14
15      void 
(gdb)

C'est clair, le problème vient de cette fichue ligne 11, puisque le pointeur entree n'est pas initialisé. On peut corriger le programme en affectant le pointeur entree à buf qui lui est initialisé. Pour cela, taper :

file bogue

Il apparaît alors :

A program is being debugged already.  Kill it? (y or n) y

Load new symbol table from "bogue"? (y or n) y
Reading symbols from bogue...done.
(gdb)

On peut regarder les premières lignes du programme, là où sont déclarées les variables, par la commande list :

Source file is more recent than executable.
1       #include <stdio.h>
2
3       static char buf[256];
4       void lit_entree (char *s);
5
6       main ()
7       {
8         char *entree = NULL;
9
10        lit_entree (entree);
(gdb)

On met un point d'arrêt à la ligne 10, juste avant l'affectation douteuse. La programme s'arrêtera à ce stade pour toutes les opérations qui suivront :

break 10

On voit alors s'afficher sur l'écran :

Breakpoint 1 at 0x80484bd: file bogue.c, line 10.
(gdb)

On peut donc lancer le programme par la commande run, qui s'exécute jusqu'à la ligne 10 :

Starting program: /home/mathieu/prog/C/gdb/bogue 

Breakpoint 1, main () at bogue.c:10
10        lit_entree (entree);

Et on corrige par la commande set :

set var entree=buf

La commande cont permet de continuer l'exécution du programme, puisque maintenant le pointeur entree est initialisé :

Continuing.
Commande : test
Vous avez tape : test

Program exited with code 026.
(gdb)

Le programme s'est terminé normalement. Il ne reste plus qu'à apporter les modifications nécessaires au fichier source, et à recompiler (sans l'option -g, puisque tout marche dorénavant).

On peut par ailleurs utiliser bien d'autre commandes sous gdb, comme next pour exécuter la ligne de code suivante (après un point d'arrêt, par exemple) dans la même fonction (il faut bien-sûr que le programme tourne, c'est-à-dire que run ait été lancé), ou step pour exécuter la ligne de code suivante et éventuellement les appels de fonctions rencontrés. On peut aussi exécuter le programme jusqu'à une ligne donnée, par exemple until 24 exécute le programme jusqu'à la ligne 24. La commande print permet de connaître la valeur d'une variable : print a donne :

$1 = 8

et print b donne :

$2 = 5

dans le petit programme salou évoqué précédemment.

Pour sortir d'une fonction et retourner dans la fonction courante, utiliser la commande finish. La commande quit permet de quitter gdb.

Pour déboguer un programme en cours de fonctionnement, il faut lancer gdb puis le lier au processus actif, par la commande attach suivie du numéro de processus (PID, voir section 1.9 page [*]). Par exemple :

(gdb) attach 254

attache le programme de PID 254 (il faut avoir chargé le fichier source correspondant en appelant gdb). On peut aussi lier le programme lorsqu'on appelle gdb :

gdb salou 254

Si on apporte des modifications au fichier source et qu'on le recompile, il faut avant détacher le processus avec la commande detach, effectuer les modifications, recompiler, et employer la commande file pour recharger le nouvel exécutable dans le débogueur. Il ne reste plus qu'à attacher la nouvelle version du programme par la commande attach.

Pour examiner les valeurs des variables du programme, la commande print est la plus souvent utilisée. On peut savoir la valeur d'une variable, d'une fonction, d'un élément de tableau...On peut aussi se contenter de fournir le numéro interne à gdb, lors d'un précédent print (comme $2 pour appeler b, dans l'exemple précédent). On peut même affecter une valeur :

(gdb) print b=7
$3 = 7
(gdb)

Pour avoir une définition plus complète d'une variable, utiliser la commande ptype suivie de l'adresse ou de la variable :

(gdb) ptype b
type = int
(gdb) ptype min
type = int (int, int)
(gdb)

La commande x permet d'examiner la mémoire à un niveau plus bas (dans ce cas, c'est à l'adresse de la variable que x ira) :

(gdb) x argv[0]
0xbffff4f1 <__ypbindlist+2146674173>:   0x6d6f682f
(gdb)

On peut spécifier le type d'affichage :

(gdb) x/5x argv[0]
0xbffff4f1 <__ypbindlist+2146674173>: 0x6d6f682f 0x616d2f65 0x65696874
0x72702f75
0xbffff501 <__ypbindlist+2146674189>: 0x432f676f
(gdb)

affiche 100 octets de données. On peut spécifier d'autre formats d'affichage (taper help x pour plus d'informations) :

(gdb) x/6c argv[0]
0xbffff4f1 <__ypbindlist+2146674173>: 47 '/' 104 'h' 111 'o' 109 'm' 101
'e'47 '/'
(gdb) x/s argv[0]
0xbffff4f1 <__ypbindlist+2146674173>: "/home/mathieu/prog/C/salou_gdb"
(gdb)

La commande info permet d'avoir des informations sur le programme en cours. Par exemple, info program affiche le statut d'exécution :

(gdb) info program
        Using the running image of child process 242.
Program stopped at 0x8048679.
It stopped at a breakpoint that has since been deleted.
(gdb)

La commande info locals donne le nom et les valeurs de toutes les variables locales de la fonction courante :

(gdb) info locals
i = 1
(gdb)

De même, info variables affiche la liste de toutes les variables connues dans le programme, y compris les variables manipulées dans les bibliothèques système (seules les valeurs des variables locales et globales sont accessibles).

Pour savoir où la variables est stockée, taper info address :

(gdb) info address i
Symbol "i" is a local variable at frame offset -4.
(gdb)

Ici i est stockée à 4 octets du haut de la structure de pile courante (``frame offset -4'').

Pour obtenir des informations sur la structure de pile courante, taper info frame :

(gdb) info frame
Stack level 0, frame at 0xbffff388:
 eip = 0x8048679 in main (salou.c:58); saved eip 0x804845e
 called by frame at 0xbffff39c
 source language c.
 Arglist at 0xbffff388, args: argc=1, argv=0xbffff3a8
 Locals at 0xbffff388, Previous frame's sp is 0x0
 Saved registers:
  ebp at 0xbffff388, eip at 0xbffff38c
(gdb)

A propos de break et watch :

La commande break permet d'arrêter le programme à un endroit particulier, comme une ligne (break 20), une ligne dans un autre fichier (break cercle.c:8), une fonction (break aire_du_cercle). Les point d'arrêt peuvent être conditionnels :

break aire_du_cercle if (r == 0)

La commande condition permet de modifier la condition d'arrêt. La commande info break permet d'obtenir des informations sur tous les points d'arrêt :

(gdb) info break
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x08048654 in main at salou.c:56
        breakpoint already hit 1 time
(gdb)

Les commandes disable et enable permettent de désactiver et d'activer un point d'arrêt. Les commandes clear et delete permettent de supprimer un point d'arrêt.

La commande watch permet de placer des points d'observation. La différence avec break est qu'ils peuvent être déclenchés lorsqu'une expression est vrai quelque soit l'endroit du programme :

watch (i < 2 && argv[i] == ``-h'')

L'expression obéit aux mêmes règles que celles des points d'arrêt conditionnels.


next up previous contents index
Next: Utiliser gdb sous Emacs Up: Outils pour la programmation Previous: Outils pour la programmation
MATHIEU DECORE
1999-11-03
Merci de me dire ce que pensez de ce document.