5.6. Déverminer

5.6.1. Le dévermineur

Le dévermineur fourni avec FreeBSD est appelé gdb (GNU debugger). Vous pouvez le démarrer en tapant

% gdb nomprog
     

bien que la plupart des gens préfèrent le démarrer au sein d'Emacs. Vous pouvez faire cela avec:

M-x gdb RET nomprog RET
     

Utiliser un dévermineur vous permet d'exécuter le programme dans des circonstances plus contrôlées. Typiquement, vous pouvez exécuter le programme ligne à ligne, inspecter la valeur des variables, changer cette dernière, dire au dévermineur d'exécuter jusqu'à un certain point puis de s'arrêter etc... Vous pouvez même vous brancher sur un programme en fonctionnement, ou charger un fichier core pour enquêter sur le plantage du programme. Il est même possible de déverminer le noyau, quoique ce soit un peu plus rusé que de déverminer des applications utilisateur dont nous discuterons dans cette section.

gdb dispose d'une assez bonne aide en ligne comme d'un ensemble de pages d'info, aussi cette section va se concentrer sur quelques commandes basiques.

Finalement, si vous trouvez son interface texte non fonctionnelle, il y a une interface graphique pour celui-ci, xxgdb, dans la collection des logiciels portés.

Cette section a pour but d'être une introduction à l'utilisation de gdb et ne couvre pas les sujets très spécialisés comme le déverminage du noyau.

5.6.2. Exécuter un programme dans le dévermineur

Vous devrez avoir compilé le programme avec l'option -g pour avoir la meilleure utilisation de gdb. Il fonctionnera sans mais vous ne verrez que le nom de la fonction dans laquelle vous vous trouvez plutôt que son code source. Si vous voyez une ligne comme:

… (no debugging symbols found) …
     

quand gdb démarre, vous saurez que le programme n'a pas été compilé avec l'option -g.

A l'invite de gdb, tapez break main. Cela dira au dévermineur de passer le code préliminaire d'initialisation du programme et de démarrer au début de votre code. Maintenant tapez run pour démarrer le programme—cela va démarrer au début du code d'initialisation et ensuite s'arrêtera lors de l'appel à main(). (Si vous vous êtes toujours demandé où main() était appelé, maintenant vous le savez !).

Vous pouvez maintenant vous déplacer dans le programme ligne par ligne en pressant n. Si vous arrivez à l'appel d'une fonction, vous pouvez entrer dans celle-ci en appuyant sur s. Une fois que vous êtes dans l'appel de la fonction, vous pouvez retourner dans le code appelant en appuyant sur f. Vous pouvez aussi utiliser up et down pour avoir une vue rapide de l'appelant.

Voici un exemple simple de comment détecter une erreur dans un programme avec gdb. Voici notre programme (avec une erreur délibérée):

#include <stdio.h>

int bazz(int anint);

main() {
	int i;

	printf("C'est mon programme\n");
	bazz(i);
	return 0;
}

int bazz(int anint) {
	printf("Vous m'avez fourni %d\n", anint);
	return anint;
}

Le programme met i à 5 et le passe à une fonction bazz() qui imprime le nombre que nous lui avons donné.

Puis nous compilons et exécutons le programme obtenu

% cc -g -o temp temp.c
% ./temp
C'est mon programme
Vous m'avez fourni 4231
     

Ce n'était pas ce que nous attendions ! Il est temps de voir ce qui se passe !

% gdb temp
GDB is free software and you are welcome to 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.
GDB 4.13 (i386-unknown-freebsd), Copyright 1994 Free Software Foundation, Inc.
(gdb) break main				passe le code d'initialisation
Breakpoint 1 at 0x160f: file temp.c, line 9.	gdb met un point d'arrêt sur main()
(gdb) run					Exécute jusqu'à main()
Starting program: /home/james/tmp/temp		Le programme démarre

Breakpoint 1, main () at temp.c:9		gdb s'arrête à main()
(gdb) n						Va à la ligne suivante
C'est mon programme				Le programme écrit
(gdb) s						entre dans bazz()
bazz (anint=4231) at temp.c:17			gdb montre la pile
(gdb)
     

Arrêtons-nous une minute! Comment anint a eu la valeur 4231? Ne l'avons-nous pas mis à 5 dans main()? Remontons dans main() et regardons.

(gdb) up					Remonte la pile des appels
#1  0x1625 in main () at temp.c:11		gdb montre la pile
(gdb) p i					Montre la valeur de i
$1 = 4231					gdb montre 4231
     

Oh ! En regardant dans le code, nous avons oublié d'initialiser i. Nous aurions dû mettre

…
main() {
	int i;

	i = 5;
	printf("C'est mon programme\n");
…

mais nous n'avions pas mis la ligne i=5;. Comme nous n'avons pas initialisé i, il a pris le nombre se trouvant dans la zone de mémoire quand le programme a démarré, ce qui dans ce cas était 4231.

Note : gdb montre la pile chaque fois que nous entrons ou sortons d'une fonction, même si nous avons utilisé up et down pour nous déplacer dans la pile des appels. Cela montre le nom de la fonction et les valeurs de ses arguments, ce qui nous aide à garder une trace d'où nous sommes et de ce qui se passe. (La pile est une zone de stockage où le programme stocke les informations sur les arguments passés aux fonctions et où il doit aller quand il revient d'une fonction).

5.6.3. Examiner un fichier core

Un fichier core est basiquement un fichier qui contient l'état complet du processus quand il s'est planté. Dans “le bon vieux temps”, les programmeurs devait imprimer des listings en hexadécimal de fichiers core et transpirer sur leur manuels de code machine, mais la vie est maintenant un peu plus facile. Par chance, sous FreeBSD et les autres systèmes 4.4BSD, un fichier core est appelé nomprog.core plutôt que juste core, pour mieux savoir à quel programme appartient un fichier core.

Pour examiner un fichier core, démarrez gdb de façon habituel. Plutôt que de taper break ou run, tapez

(gdb) core nomprog.core
     

Si vous n'êtes pas dans le même répertoire que le fichier core, vous devrez faire dir /path/to/core/file d'abord.

Vous devriez voir quelque chose comme cela:

% gdb a.out
GDB is free software and you are welcome to 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.
GDB 4.13 (i386-unknown-freebsd), Copyright 1994 Free Software Foundation, Inc.
(gdb) core a.out.core
Core was generated by `a.out'.
Program terminated with signal 11, Segmentation fault.
Cannot access memory at address 0x7020796d.
#0  0x164a in bazz (anint=0x5) at temp.c:17
(gdb)
     

Dans ce cas, le programme a été appelé a.out, aussi le fichier core s'appelle a.out.core. Nous pouvons voir que le programme s'est planté car il a essayé d'accèder à une zone dans la mémoire qui n'était pas disponible dans la fonction appelée bazz.

Quelquefois il est utile de pouvoir voir comment une fonction a été appelée car le problème peut avoir eu lieu bien avant dans la pile des appels dans un programme complexe. La commande bt demande à gdb d'afficher une trace inverse de la pile des appels:

(gdb) bt
#0  0x164a in bazz (anint=0x5) at temp.c:17
#1  0xefbfd888 in end ()
#2  0x162c in main () at temp.c:11
(gdb)
     

La fonction end() est appelée lorsque le programme se plante; dans ce cas, la fonction bazz() a été appelée main().

5.6.4. Se brancher sur un programme en cours d'exécution

Une des plus belles caractéristiques de gdb est qu'il peut se brancher sur un programme qui s'exécute déjà. Bien sûr, cela suppose que vous ayez les privilèges suffisants pour le faire. Un problème habituel est quand vous vous déplacez dans un programme qui se dédouble et que vous voulez tracer le programme fils cependant le dévermineur ne vous laissera seulement tracer le père.

Ce que vous devez faire est de démarrer un autre gdb, utiliser ps pour trouver l'ID du processus fils et faire

(gdb) attach identifiant_processus
     

dans gdb, et déverminer ensuite comme d'habitude.

“C'est tout simple,” pensez-vous certainement,“ mais pendant le temps que je faisais ça, le processus fils sera déjà parti loin”. Ne vous en faites pas, noble lecteur, voici comment faire (avec l'appui des pages d'info de gdb):

…
if ((pid = fork()) < 0)		/* _Toujours_ verifier cela */
	error();
else if (pid == 0) {		/* le fils */
	int PauseMode = 1;

	while (PauseMode)
		sleep(10);	/* Attendre jusqu'a ce que quelqu'un se brache sur nous */
	…
} else {			/* le pere */
	…
     

Maintenant tout ce que nous avons à faire est de nous brancher sur le processus fils, de mettre PauseMode à 0 et d'attendre que l'appel à la fonction sleep() retourne !

Ce document, ainsi que d'autres peut être téléchargé sur ftp.FreeBSD.org/pub/FreeBSD/doc/.

Pour toutes questions à propos de FreeBSD, lisez la documentation avant de contacter <[email protected]>.
Pour les questions sur cette documentation, contactez <[email protected]>.