Débogueur

Les conseils des sujets précédents visent à vous donner de bons réflexes de programmation qui peuvent vous éviter de commettre des erreurs. Nul n'étant parfait, vous laisserez parfois passer quelques fautes. Le compilateur vous en révèlera la majorité, et avec l'habitude corriger une erreur vue par le compilateur devient très aisé.

Pour les fautes qui ne sont révélées que par des tests du programme, il devient plus difficile de discerner la cause du problème. C'est pour les erreurs coriaces qu'un débogueur devient utile. Celui-ci permet de faire des pauses dans l'exécution du programme, tout en montrant les valeurs des variables, des registres et des zones mémoires pertinentes. Vous pouvez ainsi observer en détail le déroulement du programme, et trouver le moment précis où l'erreur se produit.

Le débogueur que nous allons utiliser est gdb, pour GNU Project Debugger. Il s'agit d'un programme qui s'utilise dans une console, et dans un premier temps nous allons nous en contenter.

Pour pouvoir déboguer un programme dans les meilleures conditions, il faut inclure dans l'exécutable un certain nombre d'informations supplémentaires. Cela nécessite de passer au compilateur une option en plus :

bob@box:~$ gcc -g -ansi -pedantic -o exemple exemple.c

L'exécutable ainsi produit peut être lancé normalement, mais vu qu'il est plus gros que la version habituelle, on ne l'utilise généralement que comme argument de gdb.

bob@box:~$ gdb exemple
...
(gdb)

La dernière ligne est une invite. gdb est capable d'une variété d'actions, et attend une commande de votre part pour les déclencher. Voici une liste abrégée des commandes les plus utiles.

Commande Explication Exemple
quit termine gdb quit
help affiche de l'aide sur un sujet help run
run démarre l'exécution du programme avec les arguments fournis run 12 + 27
break crée un point d'arrêt quelque part dans le programme, où l'exécution sera mise en pause break 12
clear retire un point d'arrêt clear 12
backtrace durant une exécution, affiche la pile des appels backtrace
frame durant une exécution, sélectionne l'un des appels en cours comme contexte pour les commandes qui suivront frame 1
continue poursuit l'exécution après un arrêt continue
step avance l'exécution jusqu'à changer de ligne step
next avance l'exécution jusqu'à la ligne suivante next
print durant une exécution, affiche la valeur d'une expression C print tab[2]
x durant une exécution, affiche le contenu de la mémoire à l'adresse indiquée x /5wd tab
list affiche quelques lignes tirées du code source list 6

La plupart des commandes ont des raccourcis. Par exemple p veut dire print, et bt veut dire backtrace. Pour plus de détails sur chaque commande, utilisez help.

Il existe une liste plus complète créée par Adnan Aziz que vous pouvez imprimer sur une page recto-verso.

  1. Tutoriel. Compilez le programme suivant (sans le modifier) de façon à pouvoir le déboguer :

    #include <stdlib.h>
    #include <stdio.h>
     
    int somme(int n, int m) {
      return n+m;
    }
     
    int main(void) {
      int valeur;
      int* p = NULL;
      printf("Entrez un entier : ");
      scanf("%d", p);
     
      printf("Le double vaut %d\n", somme(*p, *p));
      return EXIT_SUCCESS;
    }

    Version téléchargable : doubleur.c

    Pour commencer, testez ce programme en l'exécutant directement dans le terminal. Que constatez-vous ?

    À l'aide du débogueur, nous allons trouver plus d'informations sur le problème. Lancez gdb avec votre exécutable en argument. Une fois le débogueur démarré, passez-lui la commande suivante :

    (gdb) run
    

    Nous rencontrons la même erreur que précédemment, mais avec plus de détails. Cela dit, ils sont un peu cryptiques. Notez que l'exécution du programme n'est pas terminée. Nous pouvons donc en profiter pour examiner l'état de la machine au moment du crash. Observez la liste des fonctions en cours d'exécution avec la commande :

    (gdb) backtrace
    

    Pouvez-vous en déduire quelle ligne a provoqué l'erreur ? Pour en savoir plus, nous choisissons l'appel qui nous intéresse (ici, main) :

    (gdb) frame 2
    

    Et nous pouvons ensuite voir la valeur des variables correspondantes :

    (gdb) print p
    

    Comprenez-vous maintenant quelle erreur s'est produite ? Vous pouvez alors quitter gdb.

    (gdb) quit
    

    Modifiez une seule ligne du code source pour corriger l'erreur (pas forcément celle où le programme s'est arrêté). Testez votre solution.

  2. Tutoriel (suite). Compilez le programme suivant (sans le modifier) de façon à pouvoir le déboguer :

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
     
    void envers(const char texte[]) {
      unsigned position;
      for(position = strlen(texte)-1; position >= 0; position--) {
        printf("%c", texte[position]);
      }
      printf("\n");
    }
     
    int main(int argc, char** argv) {
      if (argc < 2) {
        printf("usage : %s <texte>\n", argv[0]);
        return EXIT_FAILURE;
      }
      envers(argv[1]);
      return EXIT_SUCCESS;
    }

    Version téléchargable : envers.c

    Pour analyser le problème, nous commencons par les mêmes étapes que dans l'exercice précédent :

    (gdb) run toto
    ...
    (gdb) backtrace
    ...
    (gdb) print position
    ...
    

    La valeur affichée n'a aucun sens ! Pour savoir comment la variable a évolué, nous allons suivre l'exécution de la boucle étape par étape.

    (gdb) break 8
    ...
    (gdb) run toto
    ...
    (gdb) print position
    ...
    (gdb) continue
    ...
    (gdb) print position
    ...
    (gdb) continue
    ...
    (gdb) print position
    ...
    

    On continue ainsi et on constate que position diminue correctement jusqu'à atteindre 0. Malheureusement, la boucle ne s'arrête pas et position change brutalement au tour suivant.

    Pour voir plus en détail quand le changement a lieu, on peut avancer pas à pas plutôt que de sauter de point d'arrêt en point d'arrêt.

    (gdb) break 8
    ...
    (gdb) run toto
    ...
    (gdb) next
    ...
    (gdb) next
    ...
    

    Modifiez une seule ligne du code source pour corriger l'erreur (pas forcément celle où le programme s'est arrêté). Testez votre solution.

  3. Étapes. Reprenez le programme du cinquième exercice du sujet sur les adresses. Dans le débogueur, exécutez pas-à-pas ce programme et observez l'évolution de chacune des variables.

  4. Coefficients. Récupérez le fichier binomial.c ; compilez-le et testez-le. Vous constaterez que le résultat promis n'est pas affiché. À l'aide du débogueur, trouvez dans la mémoire le résultat voulu et modifiez uniquement les arguments de printf dans la partie «affichage du résultat» du programme pour qu'il soit affiché.

  5. Miroir. Reprenez le programme du deuxième exercice du sujet sur les fonctions. Dans le débogueur, exécutez pas-à-pas ce programme et observez l'évolution du contenu du tableau.

retour à la page d'accueil

retour au sommet