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.