1. Introduction▲
Le débogage est une tâche complexe. Comme une grande partie du travail des développeurs consiste à déboguer des programmes, il est important de se familiariser avec les outils disponibles.
Sous Linux, le débogueur maison est GDB. D'aspect rebutant et primitif, il s'utilise en ligne de commande. Nombreux sont les développeurs, surtout ceux qui viennent du monde Windows et sont familiers avec Visual Studio, qui ne tentent même pas d'utiliser GDB.
GDB utilise des commandes en ligne. Il est donc indispensable de bien connaître ces commandes. Bien sûr, ça ressemble au débogage tel qu'on le pratiquait dans les années quatre-vingt, mais il y a des choses que l'on ne peut faire qu'avec GDB.
On utilisera dans la suite de ce tutoriel le code suivant :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
#include<stdio.h>
#define addnum(a,b) ((a)+(b))
#define max(a,b) (a)>(b)?(addnum(a,100)):(addnum(b,50))
int
a1=
10
,a2=
20
;
void
f2
(
)
{
printf
(
"
X
"
);
}
void
print_dbg
(
)
{
char
buf[15
];
printf
(
"
a1=%d a2=%d
\n
"
,a1,a2);
strcpy
(
buf, "
hello world
"
);
}
int
add
(
int
a, int
b)
{
int
c;
c=
max
(
a+
90
,b+
20
);
c+=
b;
return
c;
}
void
f1
(
)
{
int
i;
int
*
p=
NULL
;
print_dbg
(
);
i=
add
(
80
,90
);
for
(
i=
0
;i<
100
;i++
)
{
if
(
i %
20
==
0
)
a1++
;
f2
(
);
}
*
p=
100
;
}
int
arr[1000
];
void
main
(
)
{
arr[0
]=
1
;
f1
(
);
printf
(
"
hello %d
\n
"
,arr[0
]);
}
2. Appel de fonctions à partir du débogueur▲
Quand GDB est bloqué sur un point d'arrêt ou en mode pas à pas, on peut appeler une fonction en lui passant ou non des paramètres :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
(gdb) b add
Breakpoint 1 at 0x400654: file ./a.c, line 21.
(gdb) r
Starting program: myapp
a1=10 a2=20
Breakpoint 1, add (a=80, b=90) at ./a.c:21
21 int c= a+90;
(gdb) call print_dbg()
a1=10 a2=20
(gdb) delete breakpoints
Delete all breakpoints? (y or n) y
(gdb) b a.c:34
Breakpoint 2 at 0x4006bd: file ./a.c, line 34.
(gdb) c
Continuing.
Breakpoint 2, f1 () at ./a.c:34
34 a1++;
(gdb) call add(2,3)
$1 = 95
3. Points de surveillance▲
L'une des fonctionnalités remarquables de GDB est son aptitude à suspendre un programme lorsque quelque chose est modifié, que ce soit une variable, une adresse ou un registre.
3-A. Surveillance d'une variable▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
(gdb) watch a1
Hardware watchpoint 1: a1
(gdb) r
Starting program: /home/developer/examples/debuggingExamples/debugtests/app
a1=10 a2=20
Hardware watchpoint 1: a1
Old value = 10
New value = 11
f1 () at ./a.c:35
35 f2();
3-B. Surveillance d'une adresse▲
On commence par afficher l'adresse du tableau :
2.
(gdb) p &arr
$1 = (int (*)[1000]) 0x601080 <arr>
puis on demande à GDB de s'arrêter dès que les données situées à cette adresse sont modifiées :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
(gdb) watch *0x601080
Hardware watchpoint 1: *0x601080
(gdb) r
Starting program: /home/developer/examples/debuggingExamples/debugtests/app
Hardware watchpoint 1: *0x601080
Old value = 0
New value = 1
main () at ./a.c:43
43 f1();
3-C. Surveillance d'un registre▲
La même approche est possible avec un registre du processeur :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
(gdb) watch $rax
Watchpoint 1: $rax
(gdb) c
Continuing.
Watchpoint 1: $rax
Old value = 4196067
New value = 0
0x00000000004006f6 in main () at ./a.c:43
43 f1();
4. Débogage de macros du préprocesseur C▲
Déboguer les macros est une tâche complexe. Quand c'est possible, il est préférable de travailler avec des fonctions en ligne. Sinon, lors de la compilation, on peut utiliser l'option « ‑E » pour visualiser la sortie du préprocesseur. On peut aussi demander à GDB de convertir une macro en code final pour voir comment elle est interprétée lors de l'exécution :
2.
3.
4.
5.
6.
7.
8.
9.
10.
(gdb) b add
Breakpoint 1 at 0x400654: file ./a.c, line 28.
(gdb) r
Starting program: /home/developer/examples/debuggingExamples/debugtests/app
a1=10 a2=20
Breakpoint 1, add (a=80, b=90) at ./a.c:28
28 c= max(a+90,b+20);
(gdb) macro expand max(a+90,b+20)
expands to: (a+90)>(b+20)?(((a+90)+(100))):(((b+20)+(50)))
5. Inspection de code sans exécution▲
Si vous avez mis en place des gestionnaires d'erreurs dans votre code et que le programme se crashe, l'adresse fautive est affichée dans le journal :
2.
3.
signal 11 received
EIP address = 0x400702
Segmentation fault (core dumped)
Vous pouvez charger le programme sans l'exécuter avec GDB :
# gdb ./app
puis demander à GDB d'afficher la ligne de l'adresse fautive :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
(gdb) l *0x400702
0x400702 is in f1 (./a.c:45).
40 {
41 if(i % 20 == 0)
42 a1++;
43 f2();
44 }
45 *p=100;
46 }
47 int arr[1000];
48
49 void main()
C'est très utile si le programme s'exécute sur une autre plate-forme, par exemple un système embarqué. On peut dans ce cas utiliser un débogueur croisé (arm-linux-gdb).
6. Analyse de fichier core ▲
Quand un programme s'interrompt suite à une erreur, le système peut sauvegarder son état dans un fichier core si on a exécuté préalablement :
# ulimit -c unlimited
Lancez le programme d'exemple, il s'arrête en erreur et un fichier core est créé.
2.
3.
# ./app
a1
=
10
a2
=
20
Segmentation fault (
core dumped)
Chargez ensuite le programme et le fichier core associé avec GDB :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
# gdb ./app ./core
GNU gdb (
Ubuntu 7
.11
.1
-0ubuntu1~16
.5
) 7
.11
.1
Copyright (
C) 2016
Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3
or later <
http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty"
for
details.
This GDB was configured as "x86_64-linux-gnu"
.
Type "show configuration"
for
configuration details.
For
bug reporting instructions, please see:
<
http://www.gnu.org/software/gdb/bugs/>
.
Find the GDB manual and other documentation resources online at:
<
http://www.gnu.org/software/gdb/documentation/>
.
For
help, type "help"
.
Type "apropos word"
to search for
commands related to "word"
...
Reading symbols from ./app...done
.
[New LWP 34577
]
Core was generated by `./app'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x0000000000400706 in f1 () at ./a.c:45
45 *p=100;
(gdb)
On peut voir le type d'erreur (erreur de segmentation) et connaître l'état du programme au moment du crash :
2.
3.
(gdb) bt
#0 0x0000000000400706 in f1 () at ./a.c:45
#1 0x0000000000400727 in main () at ./a.c:52
6-A. Registres▲
Le contenu des registres du processeur peut être visualisé :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
(gdb) info registers
rax 0x0 0
rbx 0x0 0
rcx 0xfbad2a84 4222429828
rdx 0x58 88
rsi 0x7fce21600780 140523299932032
rdi 0x7fce215ff620 140523299927584
rbp 0x7fff73ecdd90 0x7fff73ecdd90
rsp 0x7fff73ecdd80 0x7fff73ecdd80
r8 0x7fce21600780 140523299932032
r9 0x7fce21807700 140523302057728
r10 0x1d6 470
r11 0x7fce212ab290 140523296436880
r12 0x4004e0 4195552
r13 0x7fff73ecde80 140735138291328
r14 0x0 0
r15 0x0 0
rip 0x400706 0x400706 <f1+132>
eflags 0x10202 [ IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0
6-B. Pile▲
On affiche vingt mots à partir de l'adresse du pointeur de pile :
2.
3.
4.
5.
6.
(gdb) x/20w $rsp
0x7fff73ecdd80: 4196176 100 0 0
0x7fff73ecdd90: 1944903072 32767 4196135 0
0x7fff73ecdda0: 4196176 0 556116016 32718
0x7fff73ecddb0: 0 0 1944903304 32767
0x7fff73ecddc0: 562207904 1 4196111 0
7. Automatisation▲
GDB permet d'automatiser des tâches. On peut, par exemple, lui demander de faire quelque chose de particulier lorsqu'un point d'arrêt est atteint :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
(gdb) b add
Breakpoint 1 at 0x400654: file ./a.c, line 28.
(gdb) commands
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>print a1
>call print_dbg()
>end
(gdb) r
Starting program: /home/developer/examples/debuggingExamples/debugtests/app
a1=10 a2=20
Breakpoint 1, add (a=80, b=90) at ./a.c:28
28 c= max(a+90,b+20);
$1 = 10
a1=10 a2=20
Ceci peut être défini dans le fichier « .gdbinit » (situé dans le répertoire de débogage) que GDB chargera et exécutera automatiquement.
Fichier « .gdbinit » :
2.
3.
4.
5.
6.
echo "hi"
b add
commands
print a1
call print_dbg()
Quand on lance le débogueur, le fichier « .gdbinit » est sourcé :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
# gdb ./app
GNU gdb (
Ubuntu 7
.11
.1
-0ubuntu1~16
.5
) 7
.11
.1
Copyright (
C) 2016
Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3
or later <
http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty"
for
details.
This GDB was configured as "x86_64-linux-gnu"
.
Type "show configuration"
for
configuration details.
For
bug reporting instructions, please see:
<
http://www.gnu.org/software/gdb/bugs/>
.
Find the GDB manual and other documentation resources online at:
<
http://www.gnu.org/software/gdb/documentation/>
.
For
help, type "help"
.
Type "apropos word"
to search for
commands related to "word"
...
Reading symbols from ./app...done
.
"hi"
Breakpoint 1
at 0x400654: file ./a.c, line 28
.
8. Macro commandes GDB▲
À l'aide du fichier « .gdbinit », on peut aussi définir de nouvelles commandes (des macros) utilisables dans ses sessions de débogage. On peut par exemple écrire les définitions suivantes :
2.
3.
4.
5.
6.
7.
8.
9.
10.
define irg
info registers
info threads
end
define pdb
print a1
print a2
call print_dbg()
end
Ce fichier définit deux nouvelles macros réutilisables, « pdb » et « irg ».
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
(gdb) pdb
$2 = 10
$3 = 20
a1=10 a2=20
(gdb) irg
rax 0x0 0
rbx 0x0 0
rcx 0x6f77206f6c6c6568 8031924123371070824
rdx 0x7ffff7dd3780 140737351858048
rsi 0x5a 90
rdi 0x50 80
rbp 0x7fffffffdb50 0x7fffffffdb50
rsp 0x7fffffffdb50 0x7fffffffdb50
r8 0x0 0
r9 0xd 13
r10 0x0 0
r11 0x246 582
r12 0x4004e0 4195552
r13 0x7fffffffdc60 140737488346208
r14 0x0 0
r15 0x0 0
rip 0x400654 0x400654 <add+10>
eflags 0x246 [ PF ZF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
Id Target Id Frame
* 1 process 35330 "app" add (a=80, b=90) at ./a.c:28
9. Définition de variables GDB▲
Vous pouvez définir des variables dans la session de débogage puis les utiliser avec d'autres commandes.
2.
3.
(gdb) set $myvar = 100
(gdb) call add($myvar,20)
$4 = 310
10. Astuces pour déboguer simultanément plusieurs processus et threads▲
Quand vous déboguez un processus qui utilise l'appel système fork(2), vous pouvez choisir de continuer dans le processus enfant ou dans le processus parent :
2.
(gdb) set follow-fork-mode child
(gdb) set detach-on-fork on
Quand vous déboguez une application multithreadée, par défaut, lorsqu'un point d'arrêt est atteint, le débogueur gèle tous les threads. Vous pouvez demander au débogueur de laisser les autres threads continuer à s'exécuter à l'aide cette commande :
(gdb) set non-stop on
11. Intégration avec les environnements de développement intégrés▲
Une dernière bonne raison d'utiliser GDB est son aptitude à s'intégrer avec d'autres programmes. En premier lieu, vous pouvez utiliser GDB avec l'option « -tui » qui permet de visualiser lors de l'exécution le code source et d'autres fenêtres, le tout en mode texte.
# gdb -tui ./app
Vous pouvez aussi trouver des frontaux qui donnent accès aux commandes en ligne de GDB. Vous disposez ainsi des deux méthodes en parallèle (ligne de commande et interface graphique). Vous en trouverez une liste ici.
Une dernière note
En tant que développeurs, on utilise le clavier en permanence. Apprendre les commandes de GDB ne prend qu'une demi-heure et en se servant d'un aide mémoire, on finit vite par ne plus avoir besoin de documentation.
12. Notes de la rédaction▲
La rédaction remercie Liran B.H pour l'autorisation de traduction de son article :
« 10 Things You Can Only Do With GDB »
La rédaction remercie également :
jlliagre pour sa traduction ;
Chrtophe pour sa relecture technique ;
Claude Leloup pour sa relecture orthographique.