Page suivante Page précédente Table des matières

2. gdb sous Emacs/XEmacs

2.1 La compilation 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!

2.2 Premiers pas avec 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).

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.

2.3 Core dumped!

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.


Page suivante Page précédente Table des matières