JH rech. un bug mortel.
Par Jean-Seb le lundi 29 septembre 2008, 21:35 - Coding - Lien permanent
Vous recherchez un bug ?
Faites confiance à GDB, spécialiste de la mise en relation.
GDB
- C'est le debugger GNU.
- Il n'y a pas vraiment de concurrence, à part peut être IDA-Pro (mais c'est payant).
- GDB est rebutant au premier abord, ne vous découragez pas. Il devient rapidement très agréable à utiliser.
- Si votre principal reproche est l'absence d'interface, vous pouvez
commencer par lire le paragraphe:
un secret bien gardé: le mode TUI
.
- Ici, je ne vais qu'effleurer le sujet, mais je mettrai à jour au fur et à mesure de mes expérimentations.
Trouver l'aide
- help ou help domaine : donne les commandes de domaine.
- apropos domaine : donne toutes les commandes liées à domaine (y compris les alias).
- info domaine : tout le paramétrage d'un
domaine. Exemple:
- info breakpoints : liste des breakpoints.
- info display : liste des variables en display (voir plus bas).
- info function : prototypage des fonctions + symboles.
- help commande : la documentation complète d'une commande appartenant à un domaine.
Fichier de configuration
- Il n'y a pas de configuration à proprement parler.
- Au démarrage, GDB recherche un fichier .gdbinit dans le home, puis dans le répertoire courant.
- Si le fichier existe dans les deux répertoires, il sera évalué à chaque fois.
- Ce fichier va contenir des commandes GDB.
- Vous pouvez vous en servir pour personnaliser votre GDB (type de base désirée, messages à afficher, etc..)
- Vous pouvez aussi l'utiliser pour démarrer plus rapidement une session de debuggage.
- Le fichier suivant change le prompt de GDB, charge le fichier a.out (compilé avec les symboles de debug), pose un point d'arrêt sur main, lance l'exécution et liste le programme à l'endroit où GDB a stoppé.
~/temp$ cat .gdbinit set prompt debug-test.elf > file a.out break main run test.c list
La base
- Compiler avec les symboles de debug:
gcc -ggdb foo.c- On peut également compiler avec le plus classique
-g
, mais -ggdb est spécifique à GDB.
- On peut également compiler avec le plus classique
- Lancer gdb:
gdb votre_programme
- Lancer le programme depuis gdb:
run "argument 1" ... "argument n"- on peut également jouer sur les arguments avec show / set args
liste arguments
- kill pour stopper GDB ou ctrl+c si vous êtes pris dans une boucle.
- continue pour continuer le programme à partir de la position courante, jusqu'à le terminer ou à atteindre le prochain breakpoint.
- quit pour sortir de GDB.
- on peut également jouer sur les arguments avec show / set args
- Exécuter le programme en mode run
- step : passe à l'instruction suivante, rentre dans les appels de fonctions.
- next : passe à l'instruction suivante, et exécute les fonctions d'un bloc.
- nexti et stepi : même chose que précédemment, mais au niveau désassemblage.
- On peut également ajouter un nombre d'occurrences pour next et step
- step 10 : va exécuter 10 fois step, éventuellement en partant dans un sous-programme.
- next 10 : avance 10 fois, les sous-programmes comptent pour une avancée.
- continue éventuellement suivi d'un nombre, qui correspondra au nombre de fois où GDB ignorera le breakpoint d'où nous sommes reparti.
- call fonction() : appeler directement un sous-programme (exemple: pour nettoyer et sortir).
- finish : appelle
step
de façon continue, jusqu'à la fin de la fonction, et retour à l'appelant (affiche la valeur de retour).
- list : lister le programme.
- list début,fin : lister de la ligne début à la ligne fin.
- list ,n : lister n lignes à partir de l'instruction courante.
- list fonction : lister une fonction.
- file foo.exe : pour charger foo.exe. Si ce n'est pas le premier chargement, il est rechargé. Dans ce dernier cas, le source sera également rechargé.
- Manipuler les variables
- print var : examiner une variable (le
$
suivi d'un nombre est juste un compteur de référence). - display var : même comportement que print, mais affiche la variable à chaque retour au prompt.
- undisplay var : annuler l'affichage automatique de var.
- set var = x : modifier une variable.
- whatis var : obtenir le type d'une variable.
- ptype struc : détail d'une structure
- show values : réafficher les 10 dernières valeurs affichées
- print var : examiner une variable (le
Les dumps
- Si on demande un print du pointeur char *buffer, on obtient une
sortie peu agréable.
- print buffer affichera l'adresse du buffe et un dump sour forme de char.
- print *buffer : pour afficher le premier caractère du buffer.
- Pour dumper une zone mémoire, il vaut mieux utiliser la commande x.
- La commande x :
x/(itérations)(format et taille) (adresse)- itérations : facultatif, 1 par défaut.
- Format = o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary), f(float), a(address), i(instruction), c(char), s(string).
- Taille = b(byte), h(halfword), w(word), g(giant, 8 bytes).
- Exemple (le mieux étant de faire quelques essais vous-même)
debug-test.elf > x/c buffer
0x602250: 35 '#'
debug-test.elf > x/8c buffer
0x602250: 35 '#' 105 'i' 110 'n' 99 'c' 108 'l' 117 'u' 100 'd' 101 'e'
debug-test.elf > x/2s buffer
0x602250: "#include <stdio.h>\n#include <stdlib.h>\n\nchar *buffer=0;\n\nint lecture(const char *file)\n{\n FILE *fp=0;\n int fsize=0;\n#define TAILLE_MAX 1024*1024\n\n fp=fopen(file,\"rb\");\n if (fp) {\n fseek(fp,0,SE"...
0x602318: "EK_END);\n fsize=ftell(fp);\n fseek(fp,0,SEEK_SET);\n\n if (fsize>TAILLE_MAX) {\n printf(\"Taille fichier trop important: %d octets.\\n\\\n", ' ' <repeats 14 times>, "Taille maximum autorisée: %d octets.\\n\",\n"...
debug-test.elf > x/2xw buffer
0x602250: 0x636e6923 0x6564756c
debug-test.elf > x/2xg buffer
0x602250: 0x6564756c636e6923 0x2e6f696474733c20
Les points d'arrêts
- help breakpoints : tout ce qu'il faut savoir sur les points d'arrêt.
- break nom fonction / num ligne : poser un
breakpoint sur l'entrée d'une fonction, ou sur un numéro de ligne (ligne en
cours, si pas de paramètre spécifié)
- on peut ajouter le nom du fichier où se trouve le code à interrompre :
break nom_fichier:nom_fonction
- on peut ajouter le nom du fichier où se trouve le code à interrompre :
- delete n : efface le breakpoint numéro
n
. Sin
est omis, tous les breakpoints sont effacés. - clear fonction/numero de ligne/adresse mémoire : efface le breakpoint en donnant sa ligne, le nom d'une fonction ou une adresse mémoire (précédée par *0x).
- info breakpoints : donne la liste des breakpoints.
- tbreak : même fonction que break, mais le breakpoint est temporaire.
- break line if cond : arrêt à la
ligne line si la condition cond est remplie. Conditions
multiples (avec des booléens) admises.
break pixel_test if x==1 && y==1- On remarquera que les comparaisons se font avec la double égalité == (comme en C).
Parcourons la pile d'appel
- La pile d'appel, ou call stack, sert à stocker les stacks frames (ou cadres de piles, traduction médiocre de l'espace utilisé pour sauver le contexte lors de l'appel d'une fonction).
- Une stack frame contient les registres sauvés lors de l'appel d'une fonction, les arguments passés à la fonction ainsi que les variables locales.
- where (ou backtrace , au choix) :
sort le contenu de la call stack.
- Attention au sens de lecture: la frame la plus récente se trouve à la position zéro.
- info frame
- info locals
- up , down (éventuellement suivi du nombre
de positions) : se déplacer dans la pile des appels.
- permet d'afficher les variables locales d'un contexte.
Gestion de l'historique des commandes
- Pour ne pas tout retaper à chaque session debug identique.
- Sauvegarde de l'historique de commandes
- set history filename fname : choisir le nom du fichier historique (par défaut: ./.gdb_history)
- set history save on/off : activer/désactiver la sauvegarde de l'historique (par défaut, on ne sauve rien).
- set history size taille : taille de l'historique (par défaut : 256 commandes)
- show history : état des paramètres ci-dessus.
- Charger et exécuter un fichier de commandes
- source commandes : commandes contient des commandes GDB
- avec l'option -v , on active le mode verbose :
source -v commandes - pas de demande de confirmation pour les commandes qui en demandent habituellement une.
readline en quelques mots
- GDB utilise la bibliothèque readline pour la gestion du clavier.
- Plutôt que de tout détailler, je vous renvoie à la doc de GDB.
- Voici le minimum vital:
- alt+f , alt+b : curseur en avant/arrière d'un mot.
- ctrl+f , ctrl+b : curseur en avant/arrière d'un caractère.
- ctrl+w : effacer un mot avant le curseur.
- alt+d : effacer un mot après le curseur.
- ctrl+k : effacer du curseur jusqu'à la fin de la ligne.
- ctrl+y :
yank
(coller) d'un mot ou d'une séquence de mots précédemment effacé(s). - tab : complétion automatique.
- ctrl+p , ctrl+n : historique arrière/avant des commandes.
- ctrl+r , ctrl+s : chercher en arrière/avant dans l'historique des commandes.
- La liste est longue, mais passer un peu de temps sur readline en vaut la peine, car le shell utilise également cette bibliotheque.
Un secret bien gardé: le mode TUI
- Un certain nombre de projets destinés à donner une interface à GDB
existent.
- Citons en vrac DDD, divers front-ends ncurses revampant la sortie de GDB, le mode natif pour Emacs etc.
- En réalité, même si certains de ces développements apportent un plus à GDB (notamment DDD avec ses graphes d'appels), la majorité des personnes les utilisant ne s'intéressent qu'au côté GUI.
- Réjouissez-vous, GDB possède un GUI intégré. Seul pré-requis: le compiler avec le support ncurses. Pour le build msys, vous pouvez oublier, par contre, il est linké par défaut avec ncurses dans Cygwin.
- Ce mode s'appelle le mode TUI (Text User Interface). Il permet d'afficher plusieurs fenêtres sur le même écran.
- Les fenêtres affichables sont: commandes, source, désassemblage, registres (avec possibilité de choisir la famille de registres)
- On ne peut pas afficher plus de 3 fenêtres, sachant que la fenêtre "commandes" est toujours présente.
- Pour activer/désactiver le mode TUI:
- ctrl-x puis a
- ou encore (équivalent): ctrl-xa (x, puis a sans relacher ctrl)
- Affichage des différentes fenêtres:
- ctrl-x 1 , ctrl-x 2 : afficher une ou deux fenêtres (en plus de la fenêtre commandes).
- ctrl-x o : pour cycler entre les fenêtres.
- help layout
- layout next / prev pour passer entre les différentes configurations d'affichage.
- Personnalisation de la fenêtre registres
- On peut choisir la famille de registres que l'on souhaite afficher :
help tui reg - Paramètres possibles à
tui reg
: float, general, system, next
- On peut choisir la famille de registres que l'on souhaite afficher :
- Focus sur une fenêtre:
- focus src / asm / regs / cmd
- note: si on n'a pas le focus sur la fenêtre commandes, on peut toujours rappeller les commandes avec ctrl+p et ctrl+n
- Changer la taille d'une fenêtre: winheight nom +-nbr lignes
- Mise à jour:
- de l'écran: ctrl+l
- de la fenêtre source:
update
- Cosmétique:
help set tui border-mode / active-border-mode / border-kind- On peut également voir les paramètres avec
show
à la place deset
- Mode
single
- ctrl-x s pour entrer dans le mode single. Toutes les commandes nécessitent désormais l'appui d'une seule touche.
- c : continue , d : frame down , u : frame up , f : finish ,s : step , n : next , r : run , v : affiche les locales, w : where
- q : quitter le mode single. (ctrl-x s à nouveau fonctionne également)
- Bien entendu, les raccourcis claviers de readline s'appliquent toujours, sauf en mode single.
Sortie d'un programme sur un tty différent
- Les sorties textes qui se mélangent aux données GDB ne sont pas pratiques. Il faut donc les rediriger vers un autre terminal.
- La commande tty dev positionne le stdin & stdout pour la prochaine exécution.
- Sous un Unix, pas de problème, il sufit de lancer la commande tty sous GDB, en indiquant le tty désiré (/dev/tty1 par exemple)
- Sous Cygwin, avant de pouvoir utiliser cette commande, il faut ajouter à la
variable locale CYGWIN le paramètre tty
- Cette opération est à faire avant de lancer le shell Cygwin.
- Le mieux est de copier cygwin.bat et d'ajouter dedans:
set CYGWIN=tty - Je vous conseille de copier le cygwin, car dans certains cas, il peut être préférable de ne pas utiliser le support termio de Cygwin.
- Notamment, il semble qu'il ne faille pas l'utiliser avec xterm ou rxvt.
- On peut très bien utiliser screen en mode tty : les sessions créées dans screen ont un tty tout à fait valide.
- Si tout s'est bien passé, une fois le shell lancé, vous pouvez tester:
~$ tty /dev/tty0
Je ne savais pas où coller ça
- -mno-cygwin : argument du gcc Cygwin pour ne pas
compiler avec les dépendances Cygwin.
- C'est dans la faq de Cygwin, mais ma bonté me perdra.
- N'hésitez pas à abuser des abréviations
- par exemple: info breakpoints s'abrège en i b
- en général, une seule lettre suffit pour les commandes les plus courantes.
- Choisir/afficher la base utilisée pour les entrées/sorties.
- set/show input-radix : pour les entrées, à faire suivre de la base désirée (8, 10 ou 16)
- set/show output-radix : idem que ci-dessus, pour les sorties de GDB.
- set/show radix : idem que ci-dessus, pour les entrées/sorties de GDB.
Coredump et puis s'en va
- C'est un billet qui a demandé pas mal de synthèse. Je pense avoir fait le tour du plus important. N'hésitez pas à compléter en commentaire si je suis passé à côté de quelque chose.
- GDB possède bien plus de possibilités que ce qui a été évoqué ici. Le mieux est de lire la doc GNU officielle, très claire et agréable. Près de 450 pages tout de même, et pas de blabla inutile.
Commentaires
Super article! J'ai jamais réussi à faire marcher le point d'arrêt conditionnel, mais grâce à toi je viens de comprendre la syntaxe. Merci! Sinon, je préfère "where full" à "where", ça affiche plus. Je sais pas si tu utilises, mais comme ça n'a pas l'air d'être dans l'article je me dis que ça valait le coup d'être mentionné pour l'internaute qui passe ici.