Le débogueur qui est fourni avec FreeBSD s'appelle gdb (GNU débogueur). Vous le lancez en tapant:
% gdb nom_du_programmebien 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.
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 demain()
(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 dansbazz()
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.)
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.coreSi 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()
.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 pidsous 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!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>.