# GDB 101 - Introduction à GDB Rédigé par Sébastien Darche `` Ce document est une rapide introduction au déboguage en ligne de commande avec `gdb`, le débogueur GNU. Il est normalement fourni dans la plupart des distributions Linux. ## Compiler avec les symboles La plupart des compilateurs n'intègrent pas par défaut les symboles de déboguage à la compilation. Il faut explicitement passer l'argument `-ggdb` (ou simplement `-g`) à la compilation. Le plus courant est le standard [DWARF](https://en.wikipedia.org/wiki/DWARF). GDB peut se débrouiller sans les symboles de déboguage mais vous aurez moins d'informations, comme le fichier source, etc. ### CMake On peut demander à CMake de compiler en mode déboguage à la configuration (ou pour un projet déjà configuré) : ```bash cmake -DCMAKE_BUILD_TYPE=Debug .. ``` Ou alternativement, le passer en paramètre explicite : ```bash cmake -DCMAKE_CXX_FLAGS=-ggdb .. ``` ## Lancer `gdb` GDB permet de contrôler un programme en exécution, mais le plus courant est de le lancer directement depuis `gdb`. Pour cela, on tape simplement : ```bash gdb [PROGRAMME] ``` Si le programme que vous lancez requiert des arguments en ligne de commande, vous pouvez utiliser l'argument `--args` : ```bash gdb --args [PROGRAMME] [ARGS...] ``` ## Usage de base ### Rouler le programme Au démarrage, le programme n'est pas lancé, GDB se contente de lire le contenu de l'ELF. On peut lancer le programme avec la commande `run` (abbréviation `r`). Le programme se lance alors. Si vous voulez mettre en pause le programme, il suffit de faire CTRL + C. GDB intercepte le signal et retourne en mode commande, vous pouvez alors exécuter n'importe quelle commande. On relance l'exécution normale du programme avec `continue` (`c`). On peut afficher le code source de la fonction courante avec `list`. Si à tout moment vous voulez arrêter le programme, utilisez la commande `kill` (`k`), ou simplement le relancer du début avec `run`. On quitte `gdb` avec les commandes `quit` (`q`) ou `exit`, ou en faisant CTRL + D. Si un programme est en train d'être executé, on vous demandera si vous êtes bien sûr de vouloir quitter GDB. ### Mettre un *breakpoint* L'usage principal d'un débogueur est de placer des points d'arrêt (*breakpoints*) à un endroit stratégique, inspecter une valeur et continuer. On peut ajouter des breakpoints à n'importe quel moment, avant de lancer le programme ou bien en pleine exécution. On place un breakpoint avec la commande `break`, suivie d'un argument qui peut être un fichier source et un numéro de ligne, un nom de fonction, une adresse mémoire, ... : ```gdb break pipeline-pthread.c:20 # Mettre un breakpoint à la ligne 20 du fichier pipeline-pthread.c break pipeline_serial # Au début de la fonction pipeline_serial break ImageLoader::ImageLoader(image_dir*) # À chaque construction d'un objet ImageLoader ``` GDB est très complet, on peut aussi mettre des *watchpoints*, qui arrêtent l'exécution quand une expression est validée, quand une variable est modifiée, etc. mais cet usage est au delà du niveau de ce rapide tutoriel. Une fois en mode "arrêt", on peut continuer l'exécution avec `c`, avancer ligne par ligne avec `step` (`s`), instruction par instruction avec `si` (on peut aussi préciser de combien de ligne ou d'instructions on veut avancer). `next` permet de sauter une exécution de fonction, tandis que `finish` continue l'exécution jusqu'à la fin de la fonction courante. ### Afficher une variable Avec les informations de déboguage, on peut demander à GDB d'afficher la valeur des variables locales d'une fonction : ```gdb print i # Affiche le contenu de i, formatté selon son type p i + 10 # Calcule l'expression et l'affiche x ptr # Affiche le premier octet pointé par la variable `ptr` x/4 ptr # Les 4 premiers octets x/c ptr # Le premier octet, sous forme de char ``` Le calcul d'expression est assez puissant, vous pouvez directement afficher les membres d'une structure, déréferencer un tableau, etc. ### Afficher la pile d'appel Il peut être utile de savoir de quelle fonction on a été appelé. Pour cela, on peut demander à GDB d'afficher la pile d'appel (*stacktrace*) avec la commande `backtrace` (`bt`). On peut ainsi voir les arguments passés à chaque fonction. Si vous voulez, par exemple inpecter les variables locales d'une fonction plus bas dans la pile d'appel, il est possible de changer le focus de GDB avec la commande `frame ` (`f `), où `` est l'indice de la fonction dans la pile d'appel. ### Changer de thread Une fois qu'ils sont lancés, vous pouvez afficher les threads du processus avec `info threads`. Si ils sont arrêtés, vous pouvez en changer avec la commande `thread ` (`t `), où `` est le numéro du thread tel qu'affiché avec la commande précédente. ## Scénarios classiques Quelques problèmes classiques où un débogueur peut aider : ### La classique `segmentation fault` Simplement lancer le programme. GDB interceptera le signal `SIGSEGV` et vous montrera où l'accès fautif a été fait. Il suffit alors de remonter la pile d'appel pour savoir qui vous a transmis le mauvais pointeur, éventuellement afficher sa valeur avec `p`, etc. ### Interblocage (*deadlock*) Le programme atteint un interblocage au bout d'un moment. On arrête alors l'exécution avec CTRL + C, et on peut voir en regardant les threads bloqués sur quelle mutex ils arrêtent (par exemple pour le tp01, dans quelle `queue` ils tentent de récupérer une image) avec l'argument passé à la fonction qui bloque. ## Usage avancé Vous ne devriez pas avoir besoin de ces fonctionnalités pour INF8601, mais cela peut être utile de savoir qu'elles existent ! ### Désassemblage, `si` La commande `disass` permet d'afficher le code désassemblé de la fonction courante, ainsi que la position actuelle du compteur de programme. On peut alors avancer instruction par instruction avec `si`. ### Registres On peut inspecter la valeur des différents registres avec `info registers`, selon l'architecture sur laquelle vous roulez vos programmes. ### Autre Il y a énormément de features dans GDB, un débogueur est un programme très complexe mais qui est un élément indispensable de la trousse à outils d'un programmeur. Ça prend quelques temps à s'habituer à la ligne de commande mais vous vous rendrez rapidement compte de l'accélération que ça vous permet. GDB peut être utilisé dans énormément de contextes différents, allant des programmes graphiques très haut-niveau jusqu'au déboguage de programmes en assembleur pour systèmes embarqués par JTAG avec GDB server (fun times). Vous vous en servirez sûrement un jour!