Les outils de développement sous FreeBSD : Guide de l'utilisateur
Précédent |  | Suivant

# 5. Déboguer

## 5.1. Le débogueur

Le débogueur qui est fourni avec FreeBSD s'appelle `gdb` (GNU débogueur). Vous le lancez en tapant:

    % gdb nom_du_programme

bien que la plupart des gens préfèrent l'exécuter sous Emacs. Ce qui se fait avec:

    M-x gdb RET progname RET

Se servir d'un débogueur vous permet d'exécuter le programme sous contrôle. Vous pouvez typiquement l'exécuter pas à pas, inspecter les valeurs des variables, les modifier, dire au débogueur d'exécuter le programme jusqu'à un certain endroit et de s'y arrêter, et ainsi de suite. Vous pouvez même le rattacher à un programme qui est déjà en cours d'exécution, ou charger une image mémoire - ``core''. Il est même possible de déboguer le noyau, bien que cela soit un peu plus compliqué que dans le cas des programmes utilisateurs, dont nous parlerons dans cette section.

`gdb` dispose d'une assez bonne aide en ligne, ainsi que d'un jeu de pages ``info'', cette section se concentrera donc sur quelques commandes de base.

Enfin, si le mode de fonctionnement en ligne de commande vous rebute, il existe une interface graphique appelée xxgdb au catalogue des logiciels portés.

Cette section est destinée à servir d'introduction à l'utilisation du débogueur et ne couvre pas les questions spécialisées comme le débogage du noyau.

## 5.2. Exécuter un programme sous le débogueur

Il faudra que vous ayez compilé le programme avec l'option `-g` pour tirer le meilleur parti de `gdb`. Cela fonctionnera sans cela, mais vous ne verrez que le nom de la fonction dans laquelle vous êtes, au lieu du code source. Si vous avez un message du genre:

    ... (no debugging symbols found) ...

au démarrage de `gdb`, 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ébogueur d'exécuter le code préliminaire de mise en oeuvre dans le programme et de s'arrêter au début de votre programme. Tapez maintenant `run` pour lancer le programme - il commencera au début du code préliminaire et sera interrompu par le débogueur sur l'appel de `main()`. (Si vous vous étiez jamais demandé d'où la fonction `main()` était appelée, vous le savez maintenant!).

Vous pouvez maintenant exécuter le programme une ligne à la fois, en appuyant sur `n`. Si vous arrivez sur un appel de fonction, vous pouvez passer dans la fonction en appuyant sur `s`. Une fois dans la fonction, vous pouvez terminer son exécution et en sortir en tapant `f`. Vous pouvez aussi utiliser `up` et `down` pour jeter un coup d'oeil au code appelant.

Voici un exemple simple de la manière de diagnostiquer une erreur dans un programme avec `gdb`. Voici notre programme (intentionnellement faux):

    #include <stdio.h>

    int bazz(int un_entier);

    main() {
          int i;

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

    int bazz(int un_entier) {
          printf("Vous m'avez donné %d\n", un_entier);
          return un_entier;
    }

Ce programme affecte à `i` la valeur `5` et la passe à la fonction `bazz()` qui affiche la valeur que nous lui donnons en paramètre.

Quand nous compilons et exécutons le programme, nous obtenons:

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

Ce n'est pas ce à quoi nous nous attendions! C'est le moment d'aller voir ce qui ce 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				Exécuter le code d'initialisation
    Breakpoint 1 at 0x160f: file temp.c, line 9.	gdb met un pont d'arrêt à l'appel de main()
    (gdb) run					Exécuter 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						Aller à la ligne suivante
    C'est mon programme				Le programme imprime
    (gdb) s						Aller dans bazz()
    bazz (un_entier=4231) at temp.c:17		gdb affiche la position dans la pile d'appel
    (gdb)

Une minute! Comment `un_entier` peut-il valoir `4231`? Ne l'avons-nous pas initialisé à `5` dans `main()`? Revenons à `main()` et jetons un oeil.

    (gdb) up					Remonter d'un cran dans la pile d'appel
    #1  0x1625 in main () at temp.c:11		gdb affiche la position dans la pile d'appel
    (gdb) p i					Afficher la valeur de i
    $1 = 4231					gdb affiche 4231

Et oui! Si nous regardons le code, nous avons oublié d'initialiser `i`. Nous voulions mettre:

    ...
    main() {
          int i;

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

mais nous avons oublié la ligne `i=5;`. Comme nous n'avons pas initialisé `i`, il prend la valeur qui se trouve à cet endroit de la mémoire quand le programme s'exécute, dans notre cas, il s'est trouvé que c'était `4231`.

> Note : `gdb` affiche l'endroit où nous nous trouvons dans la pile d'appel, chaque fois que nous entrons ou sortons d'une fonction, même si nous utilisons `up` et `down` pour nous déplacer dans la pile. Cela nous donne le nom de la fonction et les valeurs de ses paramètres, ce qui nous aide à repérer où nous sommes et ce qu'il se passe. (La pile d'appel est une zone de mémoire où le programme enregistre les informations sur les paramètres passés aux fonctions et où aller quand il ressort d'un fonction appelée.)

## 5.3. Examiner un fichier ``core''

Un fichier ``core'' est essentiellement un fichier qui contient l'état complet du programme au moment où il s'est ``planté''. Au ``bon vieux temps'', les programmeurs devaient imprimer le contenu en hexadécimal des fichiers ``core'' et transpirer sur des manuels de code machine, mais la vie est aujourd'hui un peu plus facile. Au passage, sous FreeBSD et les autres systèmes 4.4BSD, un fichier ``core'' s'appelle ``nom_du_programme`.core`, et non `core` tout court, de façon à ce que l'on sache à quel programme il correspond.

Pour examiner un fichier ``core'', lancez `gdb` comme d'habitude. Au lieu de taper `break` ou `run`, tapez:

    (gdb) core nom_du_programme.core

Si vous n'êtes pas dans le même répertoire que le fichier ``core'', vous devrez d'abord faire `dir /ou/se/trouve/le/fichier/core`.

Vous devriez voir quelque chose comme:

    % 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 (un_entier=0x5) at temp.c:17
    (gdb)

Dans ce cas, le programme s'appelait `a.out`, le fichier ``core'' s'appelle donc `a.out.core`. Nous constatons que le programme s'est terminé en erreur à cause d'une tentative d'accès à une zone de mémoire qui n'était pas accessible dans une fonction appelé `bazz`.

Il est parfois utile de pouvoir savoir comment une fonction a été appelée, parce que le problème peut s'être produit bien au-dessus dans la pile d'appel dans un programme complexe. La commande `bt` dit à `gdb` d'imprimer en remontant la pile d'appel:

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

La fonction `end()` est appelée quand un programme échoue; dans le cas présent, la fonction `bazz()` a été appelée par `main()`.

## 5.4. Prendre le contrôle d'un programme en cours d'exécution

Une des possibilités les plus intéressantes de `gdb` est qu'il peut se rattacher à un programme en cours d'exécution. Il faut bien sûr que vous ayez les autorisations suffisantes pour le faire. Le cas d'un programme qui ``fourche'' - fork - est un problème classique, lorsque vous voulez suivre le déroulement du processus fils, alors que le débogueur ne vous permet que de tracer le processus père.

Vous lancez alors un autre `gdb`, utilisez `ps` pour connaître l'IDentifiant de processus du fils, puis faites:

    (gdb) attach pid

sous `gdb`, et déboguez alors comme d'habitude.

``Tout cela est bien beau'', vous dites vous peut-être, ``mais le temps que j'ai fait tout ça, le processus fils aura déjà fait un bon bout de chemin''. Rien à craindre, aimable lecteur, voici ce qu'il faut faire (emprunté aux pages ``info'' de `gdb`):

    ...
    if ((pid = fork()) < 0)         /* _Toujours_ effectuer se contrôle */
          error();
    else if (pid == 0) {            /* C'est le processus fils */
          int PauseMode = 1;

          while (PauseMode)
                sleep(10);          /* Attendre que quelqu'un se rattache à nous */
          ...
    } else {                        /* parent */
          ...

Il n'y a plus qu'à se rattacher au fils, positionner `PauseMode` à `0`, et attendre que l'appel de `sleep()` nous rende la main!

Précédent | Sommaire | Suivant
Make |  | 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>.