GDB¶
Liste des commandes¶
gdb(1) permet de débugger vos programmes plus facilement en permettant d’analyser l’état durant l’exécution. Pour pouvoir analyser votre exécutable avec gdb(1), vous devez ajouter les symboles de débuggage lors de la compilation en utilisant l’option -g
de gcc(1):
gcc -g gdb.c -o my_program
L’option -g de gcc(1) place dans l’exécutable les informations sur les noms de variables, mais aussi tout le code source.
Lancez gdb avec la commande gdb my_program
. Ceci va vous ouvrir la console de gdb
qui vous permet de lancer, le programme et de l’analyser. Pour démarrer le programme, tapez run
. gdb va arrêter l’exécution au premier problème trouvé. Votre programme tourne encore pour l’instant. Arrêtez-le avec la commande kill
.
Breakpoint¶
Pour analyser un programme, vous pouvez y placer des breakpoints. Un breakpoint permet de mettre en pause l’exécution d’un programme à un endroit donné pour pouvoir afficher l’état des variables et faire une exécution pas-à-pas. Pour mettre un breakpoint, vous avez plusieurs choix:
break [function]
met en pause l’exécution à l’appel de la fonction passée en argument à la commandebreak [filename:linenumber]
spécifie le fichier du code source et la ligne à laquelle l’exécution doit s’arrêterdelete [numberbreakpoint]
supprime le breakpoint spécifiéNote : Chaque breakpoint est caractérisé par un numéro. Pour obtenir la liste des breakpoints utilisés
info break
Informations à extraire¶
Une fois un breakpoint placé, plusieurs informations peuvent être extraites via gdb(1) :
print [variablename]
affiche la valeur de la variable dans son format de base. Il est possible de connaître la valeur pointée en utilisant*
ainsi que l’adresse de la variable avec&
.Il est aussi possible de modifier une variable avec ``set variable [nom_variable] = [valeur]``. De façon similaire avec ``print [nom_variable] = [valeur]``.
info reg [registre]
affiche les informations sur tous les registres si aucun registre n’est explicitement spécifié.info reg eax
donne le même résultat queprint $eax
.Il est intéressant de noter qu'il est possible d'afficher une variable sous le format spécifié. Pour cela, remplacer ``print`` par : * ``p/x`` - affiche en format hexadécimal la variable spécifiée * ``p/d`` - en format entier signé * ``p/f`` - en format floating point * ``p/c`` - affiche un caractère.
backtrace
oubt
affiche la pile des appels de fonctions.Il est possible de naviguer dans la pile des appels à l'aide de ``up`` et ``down``. Ces deux commandes montent et descendent respectivement dans la pile. C'est très utile car il est possible de modifier le contexte dans lequel on se trouve pour afficher les variables.
info frame
donne des informations sur la frame actuelle.list
affiche les lignes de codes entourant le break. On peut donc facilement voir le code posant un problème ou analyser le code avant de faire une avancée pas à pas.show args
affiche les arguments passés au programme.info breakpoints
affiche les breakpointsinfo diplays
affiche les displaysinfo func [fonctionname]
affiche le prototype d’une fonction
Avancement de l’exécution¶
Quand vous avez acquis suffisamment d’informations sur le programme, vous avez plusieurs choix pour continuer son exécution :
next
exécute la prochaine instruction de votre code source, mais sans rentrer dans des fonctions externes.step
exécute la prochaine instruction de votre code source, mais en entrant dans le code des fonctions appelées.continue
continue le reste de l’exécution jusqu’au prochain breakpoint.
Automatisation¶
Lors d’un débuggage long et fastidieux, il est parfois nécessaire d’exécuter certaines commandes à chaque breakpoint.
commands [numerobreakpoint]
définit une liste de commandes associées à un breakpoint. Celles ci seront exécutées quand on s’arrêtera sur ce breakpoint. Il suffit de taper les commandes à effectuer les unes après les autres et de terminer parend
. Si vous ne fournissez pas de numéro, les commandes sont assignées au dernier breakpoint créé.display [variablename]
affiche la variable à chaque breakpoint.
Gestion des Signaux¶
En plus des breakpoints, gdb(1) interrompt l’exécution du programme en cours lorsqu’il intercepte certains signaux d’erreurs comme les signaux SIGSEGV
et SIGINT
. gdb(1) permettra alors de corriger plus facilement certaines erreurs comme les erreurs de segmentation ou les problèmes de deadlocks.
Il est possible de gérer le comportement de gdb(1) lorsque des signaux sont interceptés. Tout d’abord, la commande info signals
permet d’afficher la liste des signaux reconnus par gdb(1) ainsi que la façon dont il les traite (par exemple interrompre le programme en cours ou non). On peut changer la façon de traiter un signal avec la commande handle [SIGNAL] [HANDLING...]
où [SIGNAL]
est le signal à intercepter (son numéro ou son nom complet) et [HANDLING]
la façon de traiter ce signal par gdb(1) [1]. Par exemple, la commande handle SIGALRM stop print
permet d’interrompre le programme et d’afficher un message quand gdb intercepte le signal SIGALRM
.
Localiser un signal¶
Avec gdb(1), il est possible de localiser un signal et de débugger certaines erreurs comme une erreur de segmentation. En effet, lorsque gdb(1) interrompt le programme en cours après l’interception d’un signal d’erreur comme SIGSEGV
, il est possible de trouver la ligne du programme à laquelle le signal a été intercepté en tapant le mot-clé where
une fois le programme interrompu (il est cependant nécessaire d’avoir compilé le programme avec l’option -g
de gcc
pour trouver la ligne précise). Ensuite, grâce aux commandes expliquées plus tôt, il est possible de vérifier les valeurs des variables lors de l’interception du signal pour trouver l’origine du problème.
En plus de localiser facilement les erreurs de segmentation dans un programme, vous pourrez analyser plus aisément les problèmes de deadlock des threads. En effet, lorsque le programme est lancé sur le shell et que vous remarquez un deadlock, vous pouvez appuyer sur CTRL + C
pour lancer le signal SIGINT
au programme. Cela permettra de trouver les endroits où bloquent les différents threads du programme à l’aide des commandes décrites dans la section de débuggage des threads ci-dessous.
Extraction de code assembleur¶
disas
affiche le code assembleurdisas /m blah
met en correspondance le code assembleur et le code source
Pour arrêter la console de gdb, tapez quit
.
Illustration avec des exemples¶
Premier programme¶
Le premier programme est
src/calc.c
. Compilez-le et exécutez le pour vous apercevoir que le programme est erroné. A priori vous avez peu, ou pas, d’informations sur l’erreur. Lancez donc gdb à l’aide degdb calc
puis lancez le programme avecrun
.Program received signal SIGFPE, Arithmetic exception. => Exception arithmetique 0x0000000000400553 in calc (a=165, b=4) at calc.c:10 => Dans la fonction calc du fichier calc.c à la ligne 10 10 res = (a*5 -10) / (b-i); => Affichage de la ligne problématiqueLe premier réflexe doit être
list
pour observer le code. Puisque le problème vient de la ligne 10 dans la boucle, nous allons nous arrêter à la ligne 10 avecbreak 10
et relancer le programme. Le programme va s’arrêter avant le début de la boucle. Utilisezprint a
etprint b
pour connaître les arguments reçus par calc.Il est intéressant de noter une particularité du language C par rapport à java : une variable déclarée n'est pas initialisée à 0 par défaut, elle reprend juste la valeur de la mémoire avant son affectation. ``print i`` et ``print res`` vous donneront donc des résultats aléatoires.
Puisque le problème vient du calcul arithmétique, placez un break sur cette ligne pour pouvoir observer à chaque itération les variables.
break 9
puiscommands
qui permet d’automatiser des commandes. Nous rajouterons comme commandes :* ``echo i : `` * ``print i`` * ``echo b : `` * ``print b`` * ``echo numerateur : `` * ``print a*5 -10`` * ``echo denominateur : `` * ``print b-i`` * et enfin ``end`` pour terminer la liste de commandes.Il ne reste plus qu’à avancer avec
continue
pour aller de breakpoint en breakpoint et d’observer les variables pour comprendre le problème. On va pouvoir deviner que le problème vient d’un dénominateur nul. Pour résoudre ce problème, il faut passer une valeur plus grande que 6 à calc lors de son appel depuis la fonction main.list main
suivi de plusieurslist
permet de visualiser la main. On peut repérer l’appel de la fonction calc à la ligne 18.Supprimez les anciens break avec
delete [numerobreakpoint]
le numéro du breakpoint est connu viainfo break
. Rajoutez un break à la ligne 18,break 18
et lancez le programme.set variable m = 10
pour assigner la valeur 10 à la variable m. Puis continuez l’exécution du programme. Celui se terminera normalement puisque il n’y a plus de division par zéro.
Deuxième programme¶
Le deuxième programme est appelé
src/recursive.c
. Celui ne présente aucun bug et se déroulera normalement. Toutefois, il est intéressant d’utiliser gdb(1) pour bien comprendre les différents contextes au sein d’un programme. Mettez un break sur la fonctionfactTmp avec
break factTmp
et ajoutez automatiquement à ce breakpoint la commandebacktrace
, viacommands
. Ensuite, lancez le programme.backtrace
vous permet de visualiser les appels de fonction effectués. Nous pouvons voir que la fonctionfactTmp
a été appelée parfactTerminal
, elle même appelée par la fonctionmain
.#0 factTmp (acc=1, nbr=6) at recursive.c:8 #1 0x000000000040057d in factTerminal (a=6) at recursive.c:17 #2 0x0000000000400598 in main (argc=1, argv=0x7fffffffe1b8) at recursive.c:23Essayez d’afficher les variable
globalVar
puislocalVar
. Vous remarquerez qu’il n’est pas possible d’afficherlocalVar
puisque cette variable ne fait pas partie de l’environnement contextuel de factTmp. Pour afficher cette variable, il faut remonter la liste des appels.up
permettra de remonter les appels pour pouvoir afficherlocalVar
. Une fois la variable affichée, redescendez avecdown
et continuez 4 fois le programme après le breakpoint. Vous remarquerez que la liste des appels s’allonge à chaque appel récursif, ce qui est tout à fait normal.Naviguez dans les appels récursifs de factTmp en affichant les valeur de
globalTmp
,tmp
,acc
etnbr
. Il est important de bien comprendre que la variable statiqueglobalTmp
est commune à tous les appels de la fonctionfactTmp
et un changement de cette variable dans un des appels récursifs modifie la variable des autres appels. A contrario, la variable local ainsi que les arguments sont propres à chaque appel.Vous pouvez maintenant terminer le programme.
Troisième programme¶
Le troisième programme est
src/tab.c
. Compilez-le. Ce programme s’exécute correctement, et pourtant, il y contient une erreur. Lancez le programme avec gdb et mettez un breakpoint sur la première instruction, à savoir la ligne 9. Pour comprendre un problème sans savoir où commencer, il est utile de suivre l’évolution des variables.Il est important de savoir que ``print``, ainsi que ``display``, supportent les expressions telles que : * tab[1], tab[i],... * &i, *i,...Avancez instruction par instruction, avec
step
ounext
et portez attention aux valeurs de tab[i] par rapport à i. Une fois le problème trouvé avec gdb, solutionnez le.
Plus d’informations sur gdb(1) peuvent être trouvées sur:
Débuggage des threads avec GDB¶
gdb(1) est aussi utile pour débugger des programmes avec des threads. Il permet de faire les opérations suivantes sur les threads:
- Recevoir une notification lors de la création d’un nouveau thread.
- Afficher la liste complète des threads avec
info threads
.- Placer un breakpoint dans un thread. En effet, si vous placez un breakpoint dans une certaine fonction, et un thread passe lors de son exécution à travers ce breakpoint,
gdb
va mettre l’exécution de tous les threads en pause et changer le contexte de la console gdb(1) vers ce thread.- Lorsque les threads sont en pause, vous pouvez manuellement donner la main à un thread en faisant
thread [thread_no]
avecthread_no
étant l’indice du thread comme indiqué parinfo threads
- D’autres commandes pour utiliser gdb(1) avec les threads:
Footnotes
[1] | Une liste plus complète des mots-clés utilisables pour modifier le comportement de gestion des signaux peut-être consultée ici : ftp://ftp.gnu.org/old-gnu/Manuals/gdb/html_node/gdb_38.html . |