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.
  • 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.
  • 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


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>
#include <stdlib.h>

char *buffer=0;

int lecture(const char *file)
{
  FILE *fp=0;
  int fsize=0;
#define TAILLE_MAX 1024*1024

  fp=fopen(file,\"rb\");
  if (fp) {
    fseek(fp,0,SE"...
0x602318:        "EK_END);
    fsize=ftell(fp);
    fseek(fp,0,SEEK_SET);

    if (fsize>TAILLE_MAX) {
      printf(\"Taille fichier trop important: %d octets.\n\
", ' ' <repeats 14 times>, "Taille maximum autorisée: %d octets.\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
  • delete n : efface le breakpoint numéro n . Si n 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
  • 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 de set
  • 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.