gdb sous Emacs/XEmacs
Pour illustrer cette section, nous travaillerons sur ce fichier :
/* essai.c */
/* functions */
int fact(int i)
{
if (i == 1)
return(1)
else
return(i*fact(i-1);
}
/* main */
void main()
{
fact(10);
}
Pour compiler ce fichier, il suffit de taper la commande
M-x compile.
Emacs/XEmacs vous demande alors la ligne de commande à exécuter (par
défaut : make -k). Dans notre cas, tapez :
gcc -o essai essai.c
Si vous n'avez pas corrigé les erreurs que j'avais glissées dans ce programme, vous devriez avoir le message suivant :
gcc -o essai essai.c essai.c: In function `fact': essai.c:8: parse error before `else' essai.c:9: parse error before `;' essai.c:17: parse error at end of input Compilation exited abnormally with code 1 at Thu Sep 30 18:59:42
Pour vous rendre à l'endroit de la première erreur, faites
C-x `.
Rajoutez le ; qu'il manque à la ligne 7.
Passez ensuite à l'erreur suivante (C-x `). Vous devriez
la trouver tout(e) seul(e)....
Une fois que toutes les erreurs sont corrigées, refaites une compilation
(M-x compile). Cette fois-ci le message devrait
être :
gcc -o essai essai.c essai.c: In function `main': essai.c:13: warning: return type of `main' is not `int' Compilation finished at Thu Sep 30 19:07:23
La compilation est achevée sans erreur!
gdb
Reprenons l'exemple de la factorielle.
Essayons à présent de calculer et d'afficher factorielle de 0 à l'écran.
/* essai.c */
/* functions */
int fact(int i)
{
if (i == 1)
return(1);
else
return(i*fact(i-1));
}
/* main */
void main()
{
printf("fact(0) = %d\n",fact(0));
}
La première chose à faire pour déboguer un programme est de la compiler
avec l'option -g.
Cette option permet de rajouter au fichier compilé des marques qui seront utiles pour arrêter le programme en cours d'exécution et/ou pour éditer le contenu des variables.
Évidemment, le code exécutable ainsi généré est plus gros et plus lent que si cette option n'était pas positionnée. Par conséquent, lors de la compilation ordinaire, il est plutôt conseillé de ne pas mettre cette option.
Compilons notre exemple (M-x compile).
N'oubliez pas de modifier les options de compilation en ajoutant
l'option -g :
gcc -g -o essai essai.c
Comme vous vous en doutez sûrement, ce programme ne finit jamais, mais regardons pourquoi.
Pour cela nous allons lancer gdb (M-x gdb).
Emacs/XEmacs vous demande alors quel exécutable utiliser.
En fait, gdb a aussi besoin du fichier source (non compilé).
Mais lors de la compilation avec l'option -g, le compilateur a
écrit le chemin du fichier source dans l'exécutable. gdb
devrait donc retrouver tout ce qu'il faut tout seul (sauf si vous avez
effacé, déplacé ou changé de nom le fichier source).
Répondez-lui que vous désirez l'exécutable essai.
Si tout se passe bien, il devrait apparaître un message de ce type :
Current directory is /import/fleury/home/projets/docs/c/prog/ GNU gdb 4.17.0.4 with Linux/x86 hardware watchpoint and FPU support 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 "i386-redhat-linux"... (gdb)
Le (gdb) de la dernière ligne étant le prompt de gdb.
Commençons par poser un point d'arrêt sur la fonction
main :
break main
Remarque : Nous aurions pu mettre le point d'arrêt sur la
fonction factorielle (break fact) ou sur une ligne particulière
(break 12).
Exécutons le programme :
run
Une autre frame Emacs s'ouvre avec, à l'intérieur, le code source du programme qui s'exécute et un pointeur sur la ligne courante.
Comme vous le voyez, le code s'est interrompu sur la première
instruction de la fonction main car nous avons placé un point
d'arrêt dessus.
Il est possible, à partir d'un point d'arrêt, de faire exécuter le
programme pas à pas. Pour cela, deux commandes existent :
step (ou s) et next (ou n).
next permet de passer à la ligne suivante
sans entrer dans les fonctions.step permet de passer à ligne suivante en
entrant dans le code des fonctions.
Attention! Si vous rentrez dans le code d'une fonction dont
vous n'avez pas le code, il risque de se passer des choses bizarres.
Si vous êtes dans ce cas, repartez du début en faisant run.
Dans notre cas, faisons un step, le débogueur va rentrer dans
le code de fact et se positionner sur la première instruction
de la fonction fact.
Que ce serait-il passé si nous avions fait next? Le débogueur
aurait essayé d'exécuter la ligne suivante dans main et aurait
donc essayé de le terminer (vu qu'il ne contient qu'une ligne). Nous
n'aurions pas été beaucoup plus avancé pour déboguer ce programme.
Faisons afficher le contenu de la variable i.
print i
Le résultat devrait être :
$1 = 0
Ce qui veut dire que i=0 (ce qui est normal).
Comme nous allons souvent consulter cette variable, nous allons rendre
son affichage permanent grâce à la commande display.
display i
Le résultat devrait être :
1: i = 0
Faisons quelques étapes de calcul.
(gdb) s 1: i = 0 (gdb) s fact (i=-1) at essai.c:6 1: i = -1 (gdb) s 1: i = -1 (gdb) s fact (i=-2) at essai.c:6 1: i = -2 (gdb) s 1: i = -2 (gdb)
Uhoh! i ne devrais pas prendre de valeur négative! Évidemment,
il faut changer le test d'arrêt de la fonction récursive. On remplace
if (i == 1) par if (i == 0), pour permettre le calcul
de fact(0).
Quittons gdb :
quit
Modifions le programme et recompilons le tout. Relançons gdb
sur essai et vérifions que la fonction fact marche
bien :
(gdb) break main Breakpoint 1 at 0x80485da: file essai.c, line 15. (gdb) run Starting program: /home/fleury/home/projets/docs/c/prog/essai Breakpoint 1, main () at essai.c:15 (gdb) p fact(0) $1 = 1 (gdb) p fact(1) $2 = 1 (gdb) p fact(5) $4 = 120 (gdb) p fact(10) $5 = 3628800 (gdb) p fact(-1) Program received signal SIGSEGV, Segmentation fault. 0x80485b8 in fact (i=-698893) at essai.c:11 The program being debugged stopped while in a function called from GDB. When the function (fact) is done executing, GDB will silently stop (instead of continuing to evaluate the expression containing the function call).
Ouf, la fonction fact semble correcte à présent.
Toujours avec notre chère factorielle, on va rajouter la saisie d'un entier par l'utilisateur :
/* essai.c */
#include <stdio.h>
/*functions */
int fact(int i)
{
if (i == 0)
return(1);
else
return(i*fact(i-1));
}
/* main */
void main()
{
int i;
printf("Entrez un entier : ");
scanf("%d",i);
printf("\n\nfact(%d) = %d\n",i,i);
}
Oups, à l'exécution, on obtient : Segmentation fault (core dumped)
Autrement dit, le programme a tenté d'accéder à une adresse mémoire qui ne lui appartenait pas (cela arrive souvent avec les pointeurs).
Un fichier core a été généré.
Pour information, ce fichier
contient une image de la mémoire occupée par le programme au moment de
l'exécution. Il est aussi possible d'en tirer quelques informations
utiles grâces à gdb.
Pour cela, ouvrez un xterm et tapez :
gdb essai core
Vous devriez obtenir :
...
Core was generated by `essai'.
Program terminated with signal 11, Erreur de segmentation..
Reading symbols from /lib/libc.so.6...done.
Reading symbols from /lib/ld-linux.so.2...done.
#0 0x400490fb in _IO_vfscanf (s=0x400a52a0, format=0x80493e0 "%d",
argptr=0xbffff9c0, errp=0x0) at vfscanf.c:892
vfscanf.c:892: Aucun fichier ou répertoire de ce type..
vfscanf.c? Ligne 892?
Mais ce n'est pas dans mon programme tout ça! Faisons un :
where
Nous obtenons :
#0 0x400490fb in _IO_vfscanf (s=0x400a52a0, format=0x80493e0 "%d",
argptr=0xbffff9c0, errp=0x0) at vfscanf.c:892
#1 0x4004cac6 in _IO_vscanf (format=0x80493e0 "%d", args=0xbffff9c0)
at vscanf.c:35
#2 0x4004a14d in scanf (format=0x80493e0 "%d") at scanf.c:41
#3 0x80485f5 in main () at essai.c:20
Voila qui est déjà plus explicite! Le problème vient de la fonction
scanf que j'appelle à la ligne 20 de mon programme.
Regardons le contenu de i.
(gdb) p i $1 = 133819092
Bizarre, j'avais mis 10!!! Regardons l'adresse de i :
(gdb) print &i $2 = (int *) 0xbf800010
Bon sang, mais c'est bien sûr, on doit passer la référence de i
(&i) et non i tout court.