Livre de chevet du développeur FreeBSD
Précédent | Chapitre 5. Outils de programmation | Suivant

# 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 !

Précédent | Sommaire | Suivant
Make | Niveau supérieur | Utiliser Emacs comme environnement de développement

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 <questions@FreeBSD.org>.
Pour les questions sur cette documentation, contactez <doc@FreeBSD.org>.