GNU Debugger — Traque les bugs. Lis la mémoire.
GDB (GNU Debugger) est un outil qui te permet d'exécuter ton programme en mode ralenti, de l'arrêter à n'importe quelle ligne, d'inspecter les variables, la mémoire et la pile d'appels. C'est ton meilleur allié contre les segfaults et les bugs impossibles à trouver avec des printf.
GDB c'est le mode debug d'un jeu — tu peux mettre le jeu en pause (breakpoint) à n'importe quel moment, avancer image par image (step), regarder la valeur de chaque variable comme si tu avais accès aux stats cachées, et même voyager dans le passé pour voir comment un bug s'est produit (backtrace).
Pause le programme à une ligne précise
Lire variables, mémoire, registres
Avancer ligne par ligne dans le code
# Vérifier si GDB est déjà installé
which gdb
# /usr/bin/gdb → déjà installé ✅
# (rien) → pas installé, à installer
# Installer GDB
sudo apt-get update
sudo apt-get install gdb
# Vérifier la version
gdb --version
GNU gdb (Ubuntu 12.1) 12.1 ...
command not found, lance le apt-get install ci-dessus.Sans -g, GDB ne peut pas afficher les noms de variables, les numéros de lignes ou le code source. C'est le premier réflexe à avoir.
# ❌ Sans -g : GDB travaille à l'aveugle (adresses hexadécimales seulement)
gcc -Wall -Werror -Wextra main.c -o prog
# ✅ Avec -g : GDB voit le code source, les variables, les lignes
gcc -Wall -Werror -Wextra -g main.c -o prog
# Convention Holberton complète avec -g
gcc -Wall -Werror -Wextra -pedantic -std=gnu89 -g main.c -o prog
# Vérifier que les symboles de debug sont bien inclus
file prog
# prog: ELF 64-bit ... with debug_info, not stripped ← ✅
-g rend le binaire plus lourd et plus lent — ne jamais l'utiliser en production. C'est uniquement pour le debug en local.# Lancer GDB avec ton programme
gdb ./prog
# Lancer avec des arguments (argv)
gdb --args ./prog arg1 arg2
# Lancer et exécuter directement (sans prompt interactif)
gdb -batch -ex run ./prog
# Analyser un core dump (fichier de crash)
gdb ./prog core
# Mode quiet (sans le message de bienvenue)
gdb -q ./prog
GNU gdb (Ubuntu 12.1) 12.1
Reading symbols from ./prog...
(gdb) ← le prompt GDB, tu peux taper tes commandes ici
break → b, run → r, next → n, etc. Appuyer sur Entrée vide répète la dernière commande.# ── LANCER / ARRÊTER ───────────────────────────────────
(gdb) run # r — lancer le programme
(gdb) run arg1 arg2 # lancer avec des arguments
(gdb) quit # q — quitter GDB
(gdb) kill # arrêter le programme en cours
# ── AVANCER DANS LE CODE ────────────────────────────────
(gdb) next # n — ligne suivante (sans entrer dans les fonctions)
(gdb) step # s — ligne suivante (ENTRE dans les fonctions)
(gdb) continue # c — reprendre jusqu'au prochain breakpoint
(gdb) finish # fin — terminer la fonction courante et s'arrêter
(gdb) until 25 # avancer jusqu'à la ligne 25
(gdb) next 5 # avancer de 5 lignes
next (n) exécute une fonction en entier sans y entrer. step (s) entre à l'intérieur de la fonction pour debugger ligne par ligne dedans. En débutant, commence par next.# ── CRÉER UN BREAKPOINT ────────────────────────────────
(gdb) break main # b main — pause au début de main()
(gdb) break 42 # b 42 — pause à la ligne 42
(gdb) break main.c:42 # pause ligne 42 du fichier main.c
(gdb) break my_function # pause au début de my_function()
# ── BREAKPOINT CONDITIONNEL ─────────────────────────────
(gdb) break 42 if i == 5 # pause ligne 42 seulement si i vaut 5
(gdb) break loop if ptr == NULL # pause si ptr est NULL
# ── GÉRER LES BREAKPOINTS ──────────────────────────────
(gdb) info breakpoints # i b — voir tous les breakpoints
(gdb) delete 1 # d 1 — supprimer le breakpoint #1
(gdb) delete # supprimer TOUS les breakpoints
(gdb) disable 1 # désactiver (sans supprimer)
(gdb) enable 1 # réactiver
# Exemple — session typique de debugging
(gdb) break main
Breakpoint 1 at 0x401130: file main.c, line 8.
(gdb) run
Breakpoint 1, main () at main.c:8
8 int i = 0;
(gdb) ← programme pausé à la ligne 8 !
# ── PRINT (p) — afficher une valeur ────────────────────
(gdb) print i # p i — valeur de la variable i
$1 = 42
(gdb) print ptr # afficher un pointeur
$2 = (int *) 0x602260
(gdb) print *ptr # déréférencer — valeur pointée
$3 = 7
(gdb) print str # afficher une string
$4 = 0x402010 "Hello"
(gdb) print arr[3] # valeur à l'index 3 d'un tableau
(gdb) print node->n # champ d'une struct via pointeur
(gdb) print 2 + 2 # GDB peut évaluer des expressions
# ── FORMATS D'AFFICHAGE ─────────────────────────────────
(gdb) print/x i # en hexadécimal
$5 = 0x2a
(gdb) print/d i # en décimal (défaut)
(gdb) print/b i # en binaire
(gdb) print/c 65 # comme caractère → 'A'
(gdb) print/s ptr # comme string (char*)
(gdb) print/f x # comme float
# display — affiche automatiquement à chaque step
(gdb) display i # affiche i après chaque next/step
(gdb) display *ptr # surveille la valeur pointée
(gdb) undisplay 1 # arrêter la surveillance #1
(gdb) info display # voir les surveillances actives
# watch — pause quand la valeur CHANGE (watchpoint)
(gdb) watch i # pause quand i est modifié
(gdb) watch *ptr # pause quand *ptr change
(gdb) rwatch i # pause quand i est LU
# Exemple : display i pendant une boucle
(gdb) display i
(gdb) next
1: i = 0
9 while (i < 5)
(gdb) next
1: i = 1 ← i s'affiche automatiquement !
display est ton meilleur ami pour les boucles — tu n'as pas à retaper print i à chaque step. Il affiche automatiquement.# ── AFFICHER UNE STRUCT ENTIÈRE ────────────────────────
# Afficher tous les champs d'une struct via pointeur
(gdb) print *node
$1 = {n = 42, next = 0x602280}
# Afficher une variable struct (pas un pointeur)
(gdb) print user
$2 = {name = 0x402010 "Alice", age = 25, next = 0x0}
# ── PTYPE — VOIR LE TYPE D'UNE VARIABLE ────────────────
(gdb) ptype node
type = struct node_s {
int n;
struct node_s *next;
} *
(gdb) ptype i
type = int
(gdb) ptype argv
type = char **
# ── WHATIS — TYPE COURT (sans les détails) ──────────────
(gdb) whatis node
type = node_t *
# ── AFFICHER UN TABLEAU COMPLET ─────────────────────────
(gdb) print *arr@5 # afficher 5 éléments depuis arr
$3 = {1, 2, 3, 4, 5}
print *node (avec l'étoile) est indispensable pour les structs — il déréférence le pointeur et affiche tous les champs d'un coup. Sans l'étoile, tu ne vois que l'adresse.La commande x (examine) te permet de lire directement des octets en mémoire. Syntaxe : x/NFS ADRESSE où N=nombre, F=format, S=taille.
# x/NFS addr — N=nombre, F=format, S=taille d'unité
# ── FORMATS (F) ────────────────────────────────────────
# x=hex d=decimal s=string i=instruction c=char b=binary
# ── TAILLES (S) ────────────────────────────────────────
# b=byte(1) h=halfword(2) w=word(4) g=giant(8)
# Exemples concrets :
(gdb) x/4xb ptr # 4 octets en hex depuis l'adresse ptr
0x602260: 0x48 0x65 0x6c 0x6c ← "Hell"
(gdb) x/s str # lire une string depuis cette adresse
0x402010: "Hello World"
(gdb) x/4dw arr # 4 ints depuis le début du tableau arr
0x7fff...: 1 2 3 4
(gdb) x/10i main # 10 instructions assembleur de main
0x401130 <main>: push %rbp
0x401131 <main+1>: mov %rsp,%rbp
(gdb) x/1xg &i # 1 giant (8 bytes) à l'adresse de i
# ── VARIABLES ET PORTÉE ────────────────────────────────
(gdb) info locals # TOUTES les variables locales de la fonction
i = 3
ptr = 0x602260
str = 0x402010 "hello"
(gdb) info args # les arguments de la fonction courante
(gdb) info variables # toutes les variables globales
# ── PILE D'APPELS ──────────────────────────────────────
(gdb) backtrace # bt — pile d'appels complète
#0 ft_strlen (str=0x0) at ft_strlen.c:7 ← fonction actuelle
#1 0x00401155 in main () at main.c:12 ← qui a appelé
(gdb) backtrace full # bt full — avec les variables locales
(gdb) frame 1 # aller dans le frame #1 (main)
(gdb) up # remonter d'un frame
(gdb) down # descendre d'un frame
# ── REGISTRES ─────────────────────────────────────────
(gdb) info registers # i r — tous les registres CPU
(gdb) info registers rax # un registre précis
(gdb) print $rsp # valeur du stack pointer
(gdb) print $rip # instruction pointer (ligne courante)
# ── CODE SOURCE ───────────────────────────────────────
(gdb) list # l — afficher les 10 lignes autour de la position
(gdb) list main # code autour de main()
(gdb) list 1,50 # lignes 1 à 50
(gdb) where # où suis-je ? (alias de backtrace)
main() jusqu'à la fonction fautive.# Voir un registre précis
(gdb) print $rip # adresse de l'instruction courante
(gdb) print $rax # valeur de retour (après un call)
(gdb) info registers # tous les registres d'un coup
GDB te permet de modifier des variables pendant l'exécution pour tester différents scénarios sans recompiler.
# Changer la valeur d'une variable
(gdb) set variable i = 10 # forcer i à 10
(gdb) set var ptr = 0 # mettre ptr à NULL
# Appeler une fonction manuellement
(gdb) call printf("hello\n") # appeler printf depuis GDB
(gdb) call ft_strlen("test") # tester une de tes fonctions
# Changer un registre
(gdb) set $rax = 0 # forcer la valeur de retour
# Retourner d'une fonction avec une valeur précise
(gdb) return 42 # quitter la fonction et retourner 42
Le mode TUI affiche le code source en temps réel dans une fenêtre séparée, avec une flèche qui indique la ligne courante. Bien plus confortable que de taper list à chaque fois.
# Activer le mode TUI au lancement
gdb -tui ./prog
# Activer/désactiver pendant une session
(gdb) tui enable # activer la fenêtre de code
(gdb) tui disable # revenir au mode normal
# Raccourci clavier
Ctrl + X, puis A # toggle TUI on/off
Ctrl + X, puis 2 # split : code + assembleur
# Naviguer dans la fenêtre TUI
# Les flèches ↑↓ scrollent dans le code source
# Ctrl+L rafraîchit l'affichage si l'écran est corrompu
list en permanence. Lance toujours GDB avec gdb -tui ./prog.No debugging symbols found
Reading symbols from ./prog...
(No debugging symbols found in ./prog)
# → Tu n'as pas compilé avec -g !
# Solution : recompiler avec -g
gcc -g main.c -o prog
No symbol table is loaded
(gdb) break main
No symbol table is loaded. Use the "file" command.
# → GDB a été lancé sans programme
# Solution : charger le programme
(gdb) file ./prog
# Ou relancer : gdb ./prog
?? () dans le backtrace
(gdb) backtrace
#0 0x00007f8... in ?? ()
#1 0x0000000... in ?? ()
# → Fonction sans symboles de debug (bibliothèque externe)
# → OU programme compilé sans -g
# → Recompile avec -g pour voir les noms
Cannot access memory at address 0x0
(gdb) print *ptr
Cannot access memory at address 0x0
# → ptr est NULL ! Tu essaies de déréférencer NULL
(gdb) print ptr # afficher l'adresse d'abord
$1 = (int *) 0x0 ← confirme que ptr = NULL
Inferior 1 exited normally
Inferior 1 (process 12345) exited normally.
# → Le programme a terminé sans crash — c'est normal !
# Relancer avec : run
# Ou poser un breakpoint AVANT de lancer
Ton programme accède à une adresse mémoire invalide — pointeur NULL, pointeur non initialisé, tableau hors limites, stack overflow...
# Ton programme crash → lancer GDB
gdb ./prog
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
0x0000000000401145 in ft_strlen (str=0x0) at ft_strlen.c:7
7 while (*str)
# → Le crash est à ft_strlen.c ligne 7, str vaut 0x0 (NULL) !
# Voir la pile d'appels pour comprendre qui a appelé ft_strlen
(gdb) backtrace
#0 ft_strlen (str=0x0) at ft_strlen.c:7 ← la fonction crashée
#1 0x00401165 in main () at main.c:12 ← main a appelé ft_strlen
# Aller dans main pour voir ce qui a été passé
(gdb) frame 1
(gdb) info locals
str = 0x0 ← str est NULL ! Voilà le bug.
# Checklist quand tu vois un segfault :
# 1. run → lire le message d'erreur (fichier + ligne)
# 2. backtrace → voir la chaîne d'appels
# 3. frame N → aller dans chaque frame
# 4. info locals / print VAR → trouver la valeur invalide
# Ton programme est bloqué (boucle infinie)
# → Lance GDB, run, puis Ctrl+C pour l'interrompre
gdb ./prog
(gdb) run
# [le programme tourne et ne revient pas...]
# Appuie sur Ctrl+C
^C
Program received signal SIGINT, Interrupt.
0x0000000000401155 in my_loop (head=0x602260) at main.c:23
23 while (node != NULL)
(gdb) backtrace
#0 my_loop (head=0x602260) at main.c:23
(gdb) print *node
$1 = {n = 42, next = 0x602260} ← next pointe sur lui-même ! Cycle !
# → next pointe vers le nœud lui-même → boucle infinie
# Solution : vérifier la logique d'ajout de nœuds
# Quelque part, node->next = node au lieu de node->next = NULL
Program received signal SIGABRT, Aborted.
__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
(gdb) backtrace
#0 __GI_raise ...
#1 __GI_abort ...
#2 __libc_message ...
#3 malloc_printerr ... ← malloc détecte une corruption
#4 free (mem=0x602260) at ... ← le double free est ici
#5 0x00401188 in main () at main.c:35
# SIGABRT = le système a détecté une corruption du heap
# Causes : double free, écriture hors des limites d'un malloc...
# → Utilise Valgrind pour identifier précisément le double free
(gdb) frame 5
(gdb) info locals # voir les variables au moment du crash
valgrind --leak-check=full ./prog. GDB et Valgrind sont complémentaires.gcc -Wall -Werror -Wextra -g main.c -o prog
gdb ./prog
(gdb) break main # pause au début de main
(gdb) run
(gdb) list # voir le code autour
(gdb) info locals # voir toutes les variables locales
(gdb) display i # surveiller i
(gdb) next # avancer ligne par ligne
(gdb) next # Entrée seule = répéter next
(gdb) step # entrer dans la fonction
(gdb) print *ptr # inspecter le pointeur
(gdb) finish # sortir de la fonction
(gdb) kill # arrêter l'exécution
(gdb) run # relancer depuis le début
(gdb) quit # quitter GDB
Appuyer sur Entrée sans rien taper répète la dernière commande. Parfait pour avancer rapidement avec next ou continue.
b = break r = run n = next
s = step c = continue p = print
bt = backtrace l = list q = quit
i b = info breakpoints i l = info locals
d = delete fin = finish k = kill
Crée un .gdbinit à la racine de ton projet pour ne plus retaper les mêmes commandes à chaque fois :
# .gdbinit — exécuté automatiquement au démarrage de GDB
set confirm off # supprimer les confirmations
set print pretty on # structs affichées joliment
set print array on # tableaux sur plusieurs lignes
tui enable # activer le mode TUI par défaut
break main # poser un breakpoint sur main
run # lancer automatiquement
.gdbinit non sécurisé. Si tu vois un avertissement, ajoute dans ~/.gdbinit : set auto-load safe-path /(gdb) info symbol 0x401130
main in section .text ← cette adresse correspond à main()
(gdb) run hello 42 "avec espace"
# Équivalent à ./prog hello 42 "avec espace"
# Avec redirection
(gdb) run < input.txt
(gdb) help # liste des catégories
(gdb) help break # aide sur la commande break
(gdb) help all # toutes les commandes
| Commande | Abrév. | Ce que ça fait |
|---|---|---|
| run [args] | r | Lancer le programme (avec args optionnels) |
| quit | q | Quitter GDB |
| kill | k | Arrêter le programme en cours |
| break <loc> | b | Poser un breakpoint (ligne, fonction, fichier:ligne) |
| break <loc> if <cond> | Breakpoint conditionnel | |
| info breakpoints | i b | Lister tous les breakpoints |
| delete [N] | d | Supprimer le breakpoint N (ou tous) |
| next [N] | n | Ligne suivante (sans entrer dans les fonctions) |
| step [N] | s | Ligne suivante (entre dans les fonctions) |
| continue | c | Reprendre jusqu'au prochain breakpoint |
| finish | fin | Terminer la fonction courante et s'arrêter |
| until <N> | Avancer jusqu'à la ligne N | |
| print <expr> | p | Afficher la valeur d'une expression / variable |
| print/x /d /s /c | Afficher en hex / décimal / string / char | |
| display <expr> | Afficher automatiquement après chaque step | |
| watch <var> | Pause quand la variable change | |
| x/NFS <addr> | Examiner N unités de mémoire brute | |
| info locals | i l | Toutes les variables locales du frame courant |
| info args | Arguments de la fonction courante | |
| info registers | i r | Tous les registres CPU |
| backtrace | bt | Pile d'appels complète |
| frame <N> | Aller dans le frame N de la pile | |
| list [loc] | l | Afficher le code source autour de la position |
| set variable <v> = <val> | Modifier une variable à la volée | |
| call <func(args)> | Appeler une fonction manuellement | |
| print *struct_ptr | Afficher tous les champs d'une struct | |
| print *arr@N | Afficher N éléments d'un tableau | |
| ptype <var> | Afficher le type complet d'une variable | |
| whatis <var> | Afficher le type court d'une variable | |
| tui enable | Activer l'interface visuelle avec code source | |
| help [cmd] | Aide sur une commande |
╔══════════════════════════════════════════════════════════════════╗
║ ÉTAPE 0 — TOUJOURS compiler avec -g ║
╚══════════════════════════════════════════════════════════════════╝
gcc -g -Wall -Werror -Wextra main.c -o prog
╔══════════════════════════════════════════════════════════════════╗
║ FACE À UN CRASH (segfault, etc.) ║
╚══════════════════════════════════════════════════════════════════╝
gdb ./prog
(gdb) run # laisser crasher
(gdb) backtrace # voir où et comment
(gdb) info locals # inspecter les variables locales
(gdb) frame 1 # remonter d'un niveau
╔══════════════════════════════════════════════════════════════════╗
║ POUR TRACER L'EXÉCUTION LIGNE PAR LIGNE ║
╚══════════════════════════════════════════════════════════════════╝
(gdb) break main # pause au début
(gdb) run
(gdb) display ma_var # surveiller une variable
(gdb) next # [Entrée] pour répéter
╔══════════════════════════════════════════════════════════════════╗
║ RÈGLES D'OR GDB ║
╚══════════════════════════════════════════════════════════════════╝
# ✅ Toujours compiler avec -g avant de lancer GDB
# ✅ Lancer GDB avec -tui pour voir le code en temps réel
# ✅ Commencer par backtrace (bt) sur tout crash
# ✅ Utiliser info locals pour voir toutes les variables
# ✅ Utiliser print *ptr pour inspecter une struct entière
# ✅ Utiliser display pour surveiller une var dans une boucle
# ✅ Ctrl+C pour interrompre une boucle infinie
# ✅ Utiliser break conditionnel si la boucle a 1000 itérations
# ✅ Combiner GDB + Valgrind pour les bugs mémoire (double free...)
# ✅ Entrée vide = répéter → avancer vite
# ❌ Ne pas utiliser GDB sans -g → tu vois que des adresses hex
# ❌ Ne pas confondre next (reste dans la fonction) et step (y entre)