GDB est le d?bogeur GNU (Gnu DeBugger). Il permet de trouver les bogues des programmes en C/C++ ou en assembleur, et plus g?n?ralement de voir le d?roulement d’un programme quand il s’ex?cute.
GDB sait d?boguer les ex?cutables ?crits en C, C++ ou assembleur . Pour donner des informations int?ressantes, GDB utilise les symboles qu’il trouve dans les ex?cutables. Ces symboles sont g?n?r?s par le compilateur… ? condition de lui dire de le faire !
GDB est le d?bogeur GNU (Gnu DeBugger). Il permet de trouver les bogues des programmes en C/C++ ou en assembleur, et plus g?n?ralement de voir le d?roulement d’un programme quand il s’ex?cute.
Les commandes les plus utilis?es de GDB sont?:
?run
?break
?where et bt
?step et next
?display et print
?help
?kill
?quit (utile...)
et dans une moindre mesure file et core.
Qu'est-ce qu'on d?bogue??
Comme mentionn? plus haut, GDB sait d?boguer les ex?cutables ?crits en C, C++ ou assembleur (cf. cours de logiciels de base). Pour donner des informations int?ressantes, GDB utilise les symboles qu’il trouve dans les ex?cutables. Ces symboles sont g?n?r?s par le compilateur… ? condition de lui dire de le faire?!
Le compilateur est generalement utilise est gcc. L’option ? donner est -g (ou ?ventuellement -ggdb), ? la fois ? la production des fichiers objets (.o) et ? l’?dition de liens (production de l’ex?cutable proprement dit).
Les commandes
Lancer GDB
Tout d'abord, voyons comment lancer GDB. Il y a trois mani?res de le faire?:
gdb (sans argument)
gdb
gdb
la plus usuelle ?tant la deuxi?me.
Le fichier ? d?boguer est un ex?cutable?; en ce qui concerne le fichier core, nous y reviendrons plus tard. Ces commandes lancent GDB. Pour les deux premi?res versions de la ligne de commande, GDB affiche un message de bienvenue puis l’invite qui est (gdb).
Pour la suite, nous supposerons que le fichier ? d?boguer s’appelle bugger et qu’il est le r?sultat de la compilation de bugger.c.
Les commandes de gdb
La permi?re chose ? faire est de donner ? manger ? ce pauvre GDB, sans quoi il serait bien en peine de trouver des bogues. Si vous n’avez pas sp?cifi? de fichier ? d?boguer sur la ligne de commande lan?ant GDB, il est temps de taper?:
file bugger
ce qui a pour effet de charger le fichier code en m?moire. Si tout se passe bien, GDB r?pond?:
Reading symbols from bugger…done.
On va enfin pouvoir faire des choses…
Trouver o? est le bug
Quand on n’a aucune id?e de l’endroit o? est le m?chant bogue, on laisse travailler GDB. Ou plut?t, on le met au travail, parce que, c’est pas pour dire, mais il n’a pas fait grand chose pour le moment, le bougre.
On utilise la commande?:
run
Si le programme a besoin d’arguments, on les tape apr?s le run, comme si on tapait bugger ? partir du shell.
GDB lance le programme, qui s’ex?cute normalement jusqu’? ce que le bug remonte ? la surface. Si votre programme fait des lectures au clavier ou des sorties ? l’?cran, GDB les fera ?galement. Tout se passe comme en ligne de commande, jusqu’? la terminaison du programme, qui peut ?tre?:
?normale (GDB dit Program exited normally.)?;
?anormale (GDB dit Program exited abnormally., ce qui est souvent provoqu? par la commande abort du C)?;
?avec un code de retour (GDB dit Program exited with code XX., ce qui est provoqu? par la commande exit(XX)?;
?avec un signal, le plus souvent SIGSEGV (GDB dit Program received signal SIGSEGV, Segmentation fault.), suivi d’informations exploitables.
Au point o? on en est, il est temps de travailler sur un exemple concret. Prenons le programme suivant?:
#include
#include
char *lire_fichier(FILE* f){
? ? ? ? int c,i=0;
? ? ? ? char* str;
? ? ? ? while ((c=getc(f))!=EOF){
? ? ? ? ? ? ? ? str[i++]=(char)c;
? ? ? ? }
? ? ? ? str[i]=' ';
? ? ? ? return str;
}
int main(int argc, char **argv){
? ? ? ? char *chaine;
? ? ? ? FILE* file;
? ? ? ? file=fopen(argv[1],"r");
? ? ? ? chaine=lire_fichier(file);
? ? ? ? printf("Contenu du fichier:
%s
",chaine);
? ? ? ? return 0;
}
Deux mots sur le programme. Il ouvre le fichier dont le nom est pass? en argument, la fonction lire_fichier lit le contenu du fichier f et le met dans la cha?ne str.
Le bug, c’est (?videmment) qu’aucune m?moire n’est allou?e pour le pointeur str. C’est une erreur classique, et qui permet de bien voir les fonctionnalit?s de GDB, parce que, apr?s tout, on est l? pour ?a.
On reprend ? z?ro. L’invite de mon shell est $ et je commence par compiler le programme?:
$ gcc -g -o bugger bugger.c
Puis, lan?ons GDB?:
$ gdb
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. ?Type "show warranty" for details.
This GDB was configured as "sparc-sun-solaris2.8".
(gdb) file bugger
Reading symbols from bugger...done.
(gdb) run /etc/hosts
Starting program: /users3/bchastag/bugger /etc/hosts
Program received signal SIGSEGV, Segmentation fault.
0x1067c in lire_fichier (f=0xff33c304) at bugger.c:8
8 ? ? ? ? ? ? ? ? ? ? ? str[i++]=(char)c;
(gdb)
On a d?j? beaucoup d’informations. D?cortiquons la ligne qui suit Segmentation fault.
0×1067c, c’est l’adresse de l’instruction qui a tout fait planter. En g?n?ral, c’est assez illisible, alors on l’oublie.
in lire_fichier d?signe la fonction qui contient le vilain bogue.
f=0xff33c304 est la valeur du param?tre de la fonction lire_fichier. Ici, c’est un sale pointeur assez incompr?hensible, mais cela peut d?j? donner des informations. Par exemple si la valeur est 0×0, c’est un pointeur nul, et c’est louche.
at bugger.c:8 est le nom du fichier source qui contient la fonction, et le num?ro de la ligne o? est apparu le bogue, ici la ligne 8. La ligne suivante est carr?ment la ligne de code qui est fautive.
Maintenant, on a de vraies informations sur l’erreur. Normalement, quand on voit que c’est une affectation qui plante, on doit tout de suite penser que c’est un probl?me de m?moire pas allou?e, mais bon, pour la d?monstration, on va continuer ? osculter cet exemple. Mais comment est-on arriv? l???
C’est bien beau de savoir quelle ligne provoque le Segmentation Fault, mais ce n’est pas forc?ment l? qu’est le bogue.
Premi?re question ? se poser?: d’o? vient-on?? Pour le savoir, on utilise la commande bt (pour « backtrace ») ou where?:
(gdb) where
#0 ?0x1067c in lire_fichier (f=0xff33c304) at bugger.c:8
#1 ?0x106f4 in main (argc=2, argv=0xffbefb94) at bugger.c:18
(gdb)
qui affiche la pile des appels de fonctions. Le format est le m?me que pr?c?demment, ? cela pr?s que chaque ligne est pr?c?d?e de #num?ro. Nous verrons ce que cela signifie plus tard.
Ceci apporte surtout qu’on sait comment on est arriv? ? la ligne contenant le bogue. Ici, on sait que la fonction main appelle lire_fichier ? la ligne 18 et que le bogue appara?t ligne 8. Et les variables dans tout ?a??
Maintenant, il serait int?ressant de savoir les valeurs des variables. La commande ? utiliser est?:
display
ou aussi?:
print
La diff?rence entre les deux fonctions est que display fait l’affichage apr?s chaque commande tandis que print ne fait qu’un seul affichage.
Nous avons trois variables?: c, i et str. Demandons gentiment ? GDB de nous dire leur valeur?:
(gdb) display i
1: i = 0
(gdb) display c
2: c = 35
(gdb) display str
3: str = 0x10034 ""
(gdb)
Cela est bien, mais str est un tableau, donc ce serait plus int?ressant de conna?tre la valeur de son contenu. Rien de plus facile?:
(gdb) display str[i]
4: str[i] = Cannot access memory at address 0x0.
Disabling display 4 to avoid infinite recursion.
(gdb)
Ce message d’erreur est bien clair?: il y a un probl?me avec la variable str. Il faut noter que cet affichage d?pend du syst?me sur lequel on travaille?: chez moi, je n’ai pas cette erreur, mais str[i] donne un affichage (un peu d?bile, certes). Si la variable est « normale », ?a marche. Et maintenant, qu’est-ce qu’on fait??
Tout ceci est bien, mais ce serait encore mieux si on pouvait suivre le d?roulement du programme dans tous ses d?tails. On va donc relancer le programme et faire du pas ? pas o? on veut.
Tout d’abord, il faut arr?ter notre programme. Bien qu’il soit plant?, GDB consid?re que le programme tourne encore (pour afficher les variables, la pile…). Pour arr?ter le programme, on fait?:
kill
.
(gdb) kill Kill the program being debugged?? (y or n) y (gdb)
Avant tout, on va placer un point d’arr?t au d?but de la fonction qui fait planter (le reste, on s’en fiche). Pour cela, on utilise la commande?:
break
(gdb) break 4
Breakpoint 1 at 0x105ec: file bugger.c, line 4.
(gdb)
Note?: si on d?bogue un programme utilisant plusieurs fichiers source, il faut pr?ciser le fichier. On taperait alors?:
break bugger.c:4
On peut aussi donner un nom de fonction?:
break lire_fichier
Maintenant, on relance le programme, qui va s’arr?ter au premier point d’arr?t trouv? (notons que GDB reprend automatiquement les arguments du pr?c?dent appel ? run lorsqu’on utilise run sans arguments.)?:
(gdb) run
Starting program: /users3/bchastag/bugger bugger /etc/hosts
Breakpoint 1, lire_fichier (f=0x3) at bugger.c:4
4 ? ? ? char *lire_fichier(FILE* f){
3: str = 0x10034 ""
2: c = 2010
1: i = -12906352
(gdb)
Remarquons au passage que les variables dont nous avons demand? l’affichage ? l’aide de display sont bien affich?es automatiquement apr?s chaque commande. Faisons du pas ? pas. La commande est?:
next
L?, il faut apporter quelques pr?cisions. La commande next ex?cute les instructions les une apr?s les autres, jusque l? rien de compliqu?. Et les appels de fonctions?? Et bien, la commande next les consid?re comme une seule instruction, et ex?cute donc son code sans rien d?tailler.
Si on veut entrer dans la fonction avec le d?bogueur pas ? pas, il faut utiliser la commande step. Il en va de m?me pour les boucles (while et for).
Voyons maintenant le r?sultat de next jusqu’? l’erreur (notons que quand on ne tape que sur entr?e, GDB ex?cute la commande pr?c?dente)?:
(gdb) next
lire_fichier (f=0x8049708) at bugger.c:5
5 ? ? ? ? ? ? ? int c,i=0;
3: str = 0x10034 ""
2: c = 2010
1: i = -12906352
(gdb)
7 ? ? ? ? ? ? ? while ((c=getc(f))!=EOF){
3: str = 0x10034 ""
2: c = 2010
1: i = 0
(gdb)
8 ? ? ? ? ? ? ? ? ? ? ? str[i++]=(char)c;
3: str = 0x10034 ""
2: c = 35
1: i = 0
(gdb)
Program received signal SIGSEGV, Segmentation fault.
0x080484cb in lire_fichier (f=0xff33c304) at bugger.c:8
8 ? ? ? ? ? ? ? ? ? ? ? str[i++]=(char)c;
3: str = 0x10034 ""
2: c = 35
1: i = 0
(gdb)
Il faut noter qu’en fonctionnement normal (hors plantage), la ligne de code affich?e par GDB est celle qui va s’ex?cuter. Elle n’a donc pas encore ?t? ex?cut?e.
Quelques commandes sont associ?es ? step et next?:
?ni et si?: comme next et step mais peuvent prendre un argument N pour ex?cuter N instructions d’un coup?;
?cont?: reprend l’ex?cution du programme jusqu’au prochain point d’arr?t.
Pour quitter GDB, la commande est tout simplement quit. Si votre programme est toujours en train de tourner, GDB vous demande confirmation pour quitter. Pour ?viter cela, il suffit de penser ? tuer son processus (commande kill) avant de quitter.
Pour aller plus loin
Quelques commandes utiles
Une commande tr?s utile de GDB est help. Elle affiche la liste des cat?gories de commandes. Un help commande donne davantage d’informations sur la commande. A utiliser sans mod?ration.
Une autre commande utile est info, suivi ou non d’un nom de commande, par exemple info threads affichera les informations concernants les threads courants.
GDB conna?t la commande make, tr?s utile pour reconstruire un ex?cutable sans avoir ? quitter puis relancer GDB
Enfin, GDB conna?t la compl?tion automatique pour les noms de fichers?: taper TAB affichera toutes les possibilit?s, comme dans un shell.
S?lectionner un bloc de m?moire
Quand on a des appels de fonctions compliqu?s, il n’est pas rare que l’on veuille des informations non pas sur le bloc de m?moire courant, mais sur un autre (un appelant). C’est souvent le cas quand on a un malloc qui plante?: GDB affiche fi?rement le num?ro de ligne fautif de malloc.c, mais on s’en fiche.
Rappelons la trace pour notre petit exemple?:
(gdb) where
#0 ?0x1067c in lire_fichier (f=0xff33c304) at bugger.c:8
#1 ?0x106f4 in main (argc=2, argv=0xffbefb94) at bugger.c:18
(gdb)
Les seules variables que l’on puisse afficher ici sont celles visibles par la fonction lire_fichier. Pour acc?der aux variables du main, il faut taper la commande suivante?:
frame
(gdb) frame 1
#1 ?0x106f4 in main (argc=2, argv=0xffbefb94) at bugger.c:18
18 ? ? ? ? ? ? ?chaine=lire_fichier(file);
(gdb)
Voil? ? quoi servent les fameux num?ros que nous donnaient la commande where. Et maintenant, on peut taper print chaine sans probl?me.
A quoi servent ces fichiers core??
Quand un programme plante, il n’est pas rare qu’un fichier core soit cr??. Ce fichier contient l’?tat de la m?moire au moment du plantage (ce qui explique qu’ils soient si gros).
Dans certains cas, le bogue n’est pas d?terministe. Dans d’autres cas, il y a un bogue en fonctionnement normal, mais il dispara?t quand on met des traces ou quand on utilise GDB. Ces deux derniers cas sont d?s au fait que la fonction printf et GDB modifient le tas (heap), et donc le programme obtenu n’est plus celui qui plante.
Dans ces cas, le fichier core va nous aider. Comme il contient l’?tat du syst?me au moment du plantage, on devrait pouvoir comprendre d’o? vient le plobl?me.
Reprenons notre exemple, et lan?ons GDB?:
$ gdb bugger
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. ?Type "show warranty" for details.
This GDB was configured as "sparc-sun-solaris2.8"...
(gdb) core core
warning: core file may not match specified executable file.
Core was generated by `./bugger /etc/hosts'.
Program terminated with signal 11, Segmentation Fault.
Reading symbols from /usr/lib/libc.so.1...done.
Reading symbols from /usr/lib/libdl.so.1...done.
Reading symbols from /usr/platform/SUNW,Sun-Fire/lib/libc_psr.so.1...done.
#0 ?0x1067c in lire_fichier (f=0xff33c304) at bugger.c:8
8 ? ? ? ? ? ? ? ? ? ? ? str[i++]=(char)c;
(gdb)
La commande core core dit qu’on va utiliser le fichier de type core qui s’appelle « core ».
On constate que le r?sultat est le m?me que celui obtenu par un run. Magique…
Cependant, les bogues n?cessitant l’usage du fichier core… je n’en n’ai jamais vu. Ces fichiers sont assez gourmands en espace m?moire (80?Ko pour cet exemple basique, mais j’ai d?j? vu des fichiers core de 2?Go), mieux vaut donc ne pas les garder. Pour cela, plusieurs moyens?:
?les enlever r?guli?rement ? la main. Pas tr?s pratique, ni tr?s s?r (un oubli est vite arriv?)?;
?limiter leur taille au niveau du shell en ajoutant au .tcshrc (respectivement le .bashrc) la ligne suivante?:
limit coredumpsize 0
(ou, en bash?: ulimit -c 0)?;
?ajouter ? la fin de votre .xsession la commande?:
find -name ‘core’ -exec /bin/rm -f {} ;
Le mieux ?tant d’utiliser les deux derni?res solutions ensemble.
Pour plus de documentation
La documentation compl?te de GDB peut ?tre trouv?e sur le site de GNU ? l’adresse suivante?:
http://www.gnu.org/manual/gdb-5.1.1
Extract from ensigogne : writted by laurent.goujon@ensimag.fr