Démarche de débogage

Après des heures de travail sans relâche, parfois même des jours, vous vous retrouvez à "enfin" compiler votre programme. Et là, c'est le drame, ça ne fonctionne pas... Ou pire, cela fonctionne mais les résultats renvoyés sont faux ...

C'est le moment où vous allez vous rejeter sur toutes vos lignes de codes, passer des heures à les relire sans voir les erreurs passer. Demander l'aide de google ou de tous vos collègues, et finir par vous arracher les cheveux car vous passerez plus de 80% de votre temps de codage total à débogueur votre code.

C'est pourquoi nous vous présentons ici une méthode pour trouver facilement dans votre code les erreurs les plus classiques . Ceci n'est pas une recette miracle qui fonctionne à tous les coups, mais pourra quand même vous faire gagner quelques heures, et des moments de crispations inutiles.

 

Dans un premier temps, notre premier conseil est de compiler votre code à chaque étape. N'attendez pas d'avoir tout codé pour compiler et vous rendre compte qu'il y a des centaines de problèmes, les résoudre au fur-et-à-mesure est bien moins stressant, et vous permettra en plus de vous rendre compte si les données que vous récupérez sont exactes ou pas.

 

Nous vous fournissons ici un programme tout bugué qu'il va falloir corriger. Il y a de nombreuses erreurs, certaines n'ont pas forcément été étudiées dans les cas précédents.


Le programme

Notre programme est assez simple, il est composé de deux fichiers. Le premier prog.f90 contient le programme principal qui fait appel à deux subroutines contenues dans le fichier subroutine.f90.

Le but de notre programme est de calculer le volume de n cône(s) ayant un rayon variant d'un $r_{min}$ à un $r_{max}$ et une hauteur variant de $h_{min}$ à $h_{max}$.

Notre programme fait ensuite appel à une subroutine qui calcule le volume total de ces cônes.

​En lisant le code vous remarquerez sûrement à vu d'oeil quelques erreurs. Mais le but ici est de les tracker à l'aide des options de compilation et des débogueurs donc inutile de les corriger immédiatement. Les fichiers corrigés vous seront fournis à la fin de ce tutoriel.

Contrairement à la partie sur l'analyse des différents bug, nous ne ferons pas ici l'analyse des bugs et nous vous donnerons que la solution que nous considérons comme optimale pour la résoudre, mais qui n'est pas forcément la plus évidente. Il en existe certainement d'autres.


Démarche

Tous les compilateurs ne sont pas identiques. Certains peuvent détecter des erreurs sans option de compilation que d'autres ne pourraient pas. Nous vous rappelons que nous utilisons ici gfortran dans sa version 4.6.3.

Il est essentiel de comprendre qu'il est possible d'agir pour déboguer sur 3 niveaux différents:

  • Quand on code,
  • Lors de la compilation,
  • Après l'exécution du code.

 

La démarche que nous vous proposons s'effectue donc en 3 parties:

  • Compiler de manière méthodique: c'est à dire compiler régulièrement étape par étape le plus souvent possible lors de l'écriture. Ne pas attendre d'avoir codé le programme complet. Ceci permet de mieux cibler les erreurs. Aussi utiliser l'option -g qui permet d'utiliser les débogueurs gdb et ddd. (plus d'infos sur les débogueurs ici).
  • Effectuer des vérifications usuelles et systématiques après la compilation: les typos, les dépassements de tableaux, les erreurs de précision.
  • Acquérir certains réflexes devant les erreurs les plus courantes: NaN ou valeur incohérente, segmentation fault, erreur d'allocation. (Plus d'infos ici).
     

Attention cependant à ne pas se fier à 100% à un code qui n'a pas été validé au préalable. Pensez à garder un sens critique et prendre du recul en considérant la situation physique afin d'évaluer la cohérence des résultats. Si possible tester votre code pour des situations particulières dont vous connaissez les résultats, cela peut vous permettre si ce n'est de la validé au moins de l'invalidé.

 

Aussi un large panel d'outils existe. Il ne faut pas hésiter à chercher à les utiliser ainsi que les aides car selon la complexité du code cela peut faire gagner beaucoup de temps.


Correction

Vous trouverez ici le programme corrigé pour pouvoir vous exercer. Nous n'avons cependant pas laissé toutes les options de débogage dans le makefile sinon la compilation est ralentie, pensez à les enlever quand votre programme est fonctionnel. 

Cette correction vous sera présentée en deux parties. Dans un premier temps nous ferons les tests usuels, et nous feront ensuite une analyse pour corriger les bugs restant.

 

Vérifications usuelles :

Vous exécuter pour la première fois votre programme, on vous demande pour combien de cônes vous voulez faire le calcul, vous entrez un nombre que vous voulez, mais la le programme vous renvoie ceci :

En fait si vous regarder à la ligne 13 de votre programme : 

Vous remarquer qu'on à écrit "nn" au lieu de mettre juste un "n". Votre ordinateur ne connaît pas cette variable qui n'a pas été initialiser. Pour palier à ce problème il est important d'utiliser un "implcit none" en début de chaque program, module, subroutine, ...etc....

Vous pouvez aussi utiliser l'option -fimplicit-none dans votre makefile qui imposera un implicit none à chaque début de program, function, subroutine etc...

On corrige notre erreur en ajoutant un implicit none au début de notre programme principal, et on recompile notre programme. On obtient alors l'erreur suivante : 

Notre programme détecte bien l'erreur dont on vous à parlé plus haut, vous pouvez donc facilement la corrigé car vous avez ici la ligne et la colonne où se situe l'erreur. On remplace donc "nn" par "n" dans notre programme.

 

Ensuite, nous testons les dépassements de tableau qui sont une erreur classique. Nous vous conseillons de faire toutes les premières compilations avec l'option -fbounds-check afin de les détecter le plus tôt possible.

On recompile notre programme, et on l'exécute.

L'erreur de dépassement de tableau est alors facilement détectée lors de l'exécution de notre programme, et on obtient une information très précise sur l'endroit où se situe l'erreur. Dans notre cas le dépassement se situe à la ligne 20 dans le fichier subroutine.f90. Nous essayons en faite de faire une boucle jusqu'à n+1 alors que notre tableau ne possède que n valeur. Nous corrigeons notre erreur. 

 

La dernière vérification usuelle à effectuer est l'encodage en simple, double ou triple précision. Pour cela nous réalisons une interface. Notons que cette interface sera capable de détecter d'autres erreurs en plus de simple, double et triple precisions comme par exemple les segmention fault qui proviennent d'une erreur dans le nombre d'arguments appelés dans une subroutine ou fonction. 

Nous faisons le choix d'intégrer nos interfaces dans un module afin de ne pas surcharger notre programme principal.

Nous pensons également à appeler notre module dans notre programme principal et à l'ajouter à la copilation dans le makefile :

Lors de la compilation, l'erreur suivante nous est renvoyée: 

L'interface détecte alors que nous avons un problème sur des nombres déclaré en double précision et passés en simple précision dans la subroutine à deux endroits dans notre code. Ici la double précision n'est pas utile, nous modifions donc le programme principal pour mettre tous nos nombre en simple précision.

On recompile : 

Comme dit précédemment il s'agit d'une erreur dans le nombre d'arguments appelés pour la subroutine calcul_1volume. Ici il manque un argument dans l'appel, nous le corrigeons.

 

Vous avez fait le plus gros du chemin, vous avez fait toutes les vérifications usuelles, passons alors aux réflexes à avoir face à une erreur.

Réflexes à avoir face à une erreur courante

Nous recompilons notre programme. Tout va bien. Nous exécutons notre programme. Super ça fonctionne. Ah oui, vraiment? Enfin non pas vraiment, votre programme vous renvoi NaN (Not a Number). Votre programme n'est donc pas valide, car il ne vous renvoi pas un résultat viable physiquement.

 

Un programme qui vous renvoi NaN doit vous faire penser que votre programme n'estime pas bien un nombre. C'est-à-dire une divions par zéro, calcul un réel à partir d'entiers, erreur dans le domaine de définition d'une fonction (log, asin, acos, atan etc...)

La solution à envisager est donc d'ajouter l'option de compilation -ffpe-trap. Cette option permet justement de détecter les erreurs de floating point exeption, les problèmes d'estimation d'un nombre.

Le plus souvent, utilisez l'argument invalid qui détecte les problèmes de domaine de définitions:

L'erreur est bien détectée lors de l'exécution. Cependant cette option ne donne pas plus d'information sur l'endroit où le problème se situe. Il faut utiliser un débogueur pour trouver l'erreur.

>> gdb ./prog.exe

Vous obtiendrez après avoir lancé le programme le message suivant :  

Le débogueur vous signal une erreur à la ligne 10 du fichier subroutine. En fait il n'arrive pas à faire le calcul avec  le nombre $\pi$ qui a mal été défini. Nous corrigeons alors notre erreur.

 

Nous recompilons encore, et relançons notre programme : 

Notre programme fonctionne enfin, je vous laisse vérifier le résultat par vous même.

 

Ne vous reste plus qu'à vous souhaiter bon courage dans vos futurs codes, et un bon débogage.