développeur, gdb est ton ami !🔗
Pendant des années, j'ai toujours refusé d'utiliser gdb ou même un débugueur. Je me disais que c'était pour les grands (comprendre les professionnels), que si un programme plantait point d'avoir besoin d'un outil spécifique, c'est que ça me dépassait et que c'était un problème que je n'étais pas capable de régler, un truc pas à mon niveau.
En plus, l'interface de gdb semble super austère comme premier contact. De plus, le processus de débogage demande d'acquérir de nouvelles compétences et on pense, à tort, que ça va être compliqué. Après tout, c'est un outil de wizard.
Combien je me suis trompé pendant toutes ces années…
Comment je me suis mis à gdb ?
Vers la fin de l'année 2021, j'ai commencé à coder des choses sur Flightgear. Je voulais simplement savoir quelle était la partie de code qui gérait les tuiles et leur préparation dynamique au fur et à mesure du déplacement de l'avion dans l'espace. J'avais mis la main sur la partie du code incriminé, mais je voulais savoir ce qui rentrait et sortait dans certaines fonctions.
Flightgear, codé en C++, utilise pas mal d'objets complexes qui font appels à plein de définitions. Si on ajoute l'héritabilité de certaines classes, je me suis vite rendu compte que j'allais devoir lire tout le code de simgear (la bibliothèque qui gère la représentation spatiale des objets) pour m'en sortir. Sauf que ce n'était pas mon objectif. Plutôt que d'abandonner bêtement, j'ai commencé à placer des printfs dans le code (en fait des fonctions de log internes à Flightgear) pour "faire parler" le code. C'est la méthode des développeurs primitifs comme moi avant. Au bout de quelques heures, j'avais un petit peu avancé avec une meilleure compréhension mais ça m'avait pris vraiment beaucoup plus de temps que prévu.
Puis, une fois les mécanismes de tuiles à peu près compris, j'ai voulu modifier leur comportement pour voir si une de mes idées initiales avait une possibilité d'être codée. Et là, j'ai vraiment galéré. Je devais pratiquement sortir une ligne de log à chaque fois que j'ajoutais une ligne de code pour m'assurer que je ne faisais pas une connerie. Un jour, je suis tombé sur un os: malgré mes tentatives, j'avais toujours un segfault systématique sur une instruction mais qui n'arrivait que si j'utilisais une tuile et pas une autre (les tuiles sont des données externes, lues par Flightgear). Après plusieurs heures de printf/log, pas moyen de trouver d'où ça venait: il y avait trop de cas à tester ou des objets complexes à faire afficher en chaîne de caractères et il me fallait systématiquement au moins 5 minutes entre le build, le lancement du programme et le positionnement de l'avion sur l'endroit qui posait problème.
Désespéré, mais pas encore résigné, même après y avoir passé vraiment trop de mon précieux temps libre, je me suis demandé si au final je n'allais pas essayer gdb pour essayer de voir ce qui n'allait pas. Donc, j'ai lu quelques articles et tutoriels sur gdb. J'ai fini par lire une partie du manuel dans le train et puis je me suis lancé dans ma première session de débogage qui a duré… …5 minutes ! En 5 minutes, j'avais trouvé mon problème: une valeur de données que je n'avais tout simplement pas prise en compte dans mon modèle amont et qui amenait un null là où il n'en fallait pas ! Et en plus, Flightgear est multi-thread et je n'ai eu aucun problème avec ça.
Voilà l'histoire. Depuis cet épisode, dès que j'ai un problème en C/C++, je lance gdb et je règle mes problèmes bien plus rapidement qu'avant. J'avoue, je me sens con d'avoir perdu tout ce temps à faire des printf et à regarder gdb avec un à-priori négatif.
Alors pourquoi on utilise gdb ?
Voilà pourquoi on utilise gdb à la place de mettre des printf: selon vous qu'est-ce-qui est plus rapide ? Faire ça:
- compiler le programme.
- lancer gdb sur le programme avec les arguments qui vont bien.
- poser un breakpoint sur un truc qu'on pense être un problème.
- afficher le contenu des valeurs avant ou après.
- dynamiquement changer les valeurs sans recompiler.
- ou tout simplement: laisser le programme planter tout seul et remonter la pile à partir du segfault.
ou alors faire ça:
- écrire un printf/log d'une valeur qu'on pense être problématique.
- compiler le programme avec les arguments qui vont bien.
- le faire planter.
- analyser les logs.
- ré-écrire un printf/log d'une autre valeur qu'on pense être problématique.
- compiler le programme avec les arguments qui vont bien.
- le faire planter.
- analyser les logs.
- ré-écrire une valeur forcée pour vraiment vérifier que ce n'est pas une variable qui fait planter.
- compiler le programme avec les arguments qui vont bien.
- le faire planter.
- analyser les logs.
- refaire cette boucle pendant des heures…
On voit bien que la deuxième situation n'est rentable que si on est certain de travailler sur une erreur triviale, dans un environnement complètement maîtrisé, sur une seule variable qui peut poser problème. Dans tous les autres cas, gdb (ou un autre débogueur) est tout simplement plus rapide.
Et surtout, on va dire que probablement 80% des sessions gdb consiste à poser des breakpoints et regarder des valeurs de variables en faisant défiler le code ligne par ligne. C'est un truc qui s'apprend en moins d'une heure. Avec une autre heure en plus, on peut même déclencher des
Comment utiliser gdb ?
Je vais faire comme à mon habitude pour proposer des tutoriels sur un programme qui a au moins une vingtaine d'années de développement derrière lui: RTFM!
Le manuel de gdb est vraiment bien fait et il se lit assez facilement. Il faut quelques heures pour explorer les éléments de base (et encore, si on suit la session d'introduction qui dure 30 minutes, on peut régler beaucoup de problèmes).
Vous pouvez aussi lire quelques articles d'introduction à gdb (je n'ai pas de recommandations). Par contre, évitez les vidéos Youtube sur le sujet, même faites par les pontes de gdb: c'est super long, c'est lent et très passif. La session d'introduction sera bien plus productive.
Le moyen le plus efficace à mon sens, c'est de travailler directement sur votre programme qui plante et suivre le manuel progressivement pour pouvoir vous initier facilement et explorer des commandes sur un cas que vous maîtrisez plus qu'un programme lambda ou un truc théorique.
Dans la majorité des cas, si vous avez compris ce qu'est un breakpoint, et une probe et que vous savez naviguer dans l'exécution de votre programme, vous pouvez régler facilement 2/3 de vos problèmes de code.
Si vous avez un programme avec beaucoup d'arguments de lancement, ça peut être aussi très rentable que vous-vous renseignez sur comment lancer gdb sur un processus en cours de fonctionnement (c'est très facile, je l'utilisais tout le temps pour Flightgear pour aller directement sur l'endroit qui plantait mais il me fallait un script bash pour gérer toutes les options de lancement).
Emacs et gdb, le meilleur des couples
Soudain, au détour du manuel, je tombe sur ça: "Using GDB under GNU Emacs". Moi qui utilise Emacs comme IDE depuis quelques années déjà, je me dis qu'il faut que je teste. Et là (c'est le drame), après quelques manipulations, je débogue mon problème dans Flightgear et Emacs me donne la main directement dans le code.
Avec le mode gdb d'Emacs, j'ai l'accès au débogueur et aussi au code, le tout dans un univers que je maîtrise totalement. Toutes les conditions, sont réunies pour faciliter la pose de breakpoints, les aller-retours entre le code et gdb et vice-versa, 100% au clavier.
On voit le code, les breakpoints directement dans le code, gdb nous pose automatiquement à l'endroit qui pose problème. On oublie carrément l'interface austère de gdb: il s'efface et on a l'impression de comprendre encore plus vite ce qui ne va pas.
Franchement, si vous utilisez Emacs, n'hésitez pas à utiliser gdb avec. Ça va vous réconcilier avec l'interface de gdb et vous allez vraiment augmenter votre productivité.
Conclusions
Voilà, si vous développez en C ou C++ (ou autre langage géré par gdb), même de temps en temps, si vous avez goût à ça, je vous invite à consacrer une dizaine d'heures à apprendre à utiliser gdb. Croyez-moi c'est très vite rentabilisé, surtout pour les langages compilés.
Avec gdb, terminés les incertitudes, terminés les séries de printf qui dégueulent tout un tas de valeurs absconses. Fini les logs longs comme le bras qu'il faut "grepper" pour voir ce qui pose problème.
Si vous utilisez Emacs en plus, alors c'est du pain béni !
Avec le recul, je crois qu'il faudrait recommander à ceux qui apprennent le C ou le C++ de systématiquement faire une session d'instruction sur gdb dans la foulée. Ça devrait faire partie du paquet de base de développeur.