Introduction à GNU GDB
Table of Contents
Salut à toi ! Bienvenue dans la bidouille !
Ce fichier est disponible à http://sed.bordeaux.inria.fr/la-bidouille
1 Méthodes de déverminage
- utiliser une instruction d'affichage :
printf
,cout <<
,write *
→ bricolage - simuler l'exécution du programme pas-à-pas avec un dévermineur → gdb, ddt, etc…
- utiliser un simulateur automatique qui recherche les écritures et lectures invalides, les fuites de mémoire, etc… → valgrind memcheck
2 Présentation de GDB
2.1 points d'arrêt
2.1.1 Compilez le programme suivante g++ -g -std=c++11 ex1.cpp
#include <iostream> #include <cstdlib> #include <algorithm> #include <vector> using namespace std; int sum(int size, const int * x) { int sum = 0; for(int i = 0; i < size; ++i) sum += x[i]; return sum; } void cumSums(const vector<int> & x, vector<int> & res) { res.resize(x.size()); int i = 0; for (auto & z : x) res[i++] = sum(i, x.data()); } int main() { int N = 10; vector<int> x(N, 0), res; generate(x.begin(), x.end(), [] { static int z = 0; return z++;}); cumSums(x, res); for (auto z : res) { cout << z << endl;} return 0; }
et passez-le en arguments à GDB : gdb ./a.out
2.1.2 voir les sources
(gdb) l main
Notes:
- on peut aussi utiliser une plage numeros de lignes e.g.
l 28,38
- pour separer les sources , on peut utiliser une fenêtre pour les sources
(gdb) wh src + 10
2.1.3 mettre un point d'arrêt (sur un ligne)
(gdb) b 21
Note : si il y a plusieurs fichiers la syntaxe est b nomFichier:numeroDeLigne
2.1.4 ou sur une fonction
(gdb) b sum
Note : En commençant le nom de fonction par ' et en utilisant tab on peut completer le nom des fonctions
2.1.5 obtenir les info sur les points d'arrêt
(gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000400c36 in main() at ex1.cpp:21 2 breakpoint keep y 0x0000000000400b01 in sum(int, int const*) at ex1.cpp:8
2.1.6 lancer l'exécution et stopper sur un point d'arrêt
(gdb) run Starting program: /home/fux/sources/sed-bso/web/org/a.out , main () at ex1.cpp:21 21 int N = 10;
2.1.7 point d'arrêt conditionnel
On peut utiliser un point d'arrêt en ajoutant une condition. On peut souhaiter
s'arrêter à une iteration précise dans une boucle. La syntaxe conditionelle
est la suivante b ligne if condition
. Par exemple, si l'on veut stopper dans la fonction
cumSums
à l'iteration où i
vaut 3
- on va a ligne 16 dans la fonction
(gdb) disable 2 (gdb) tb 16 (gdb) c Continuing.
- et l'on pose un point d'arrêt conditionel
(gdb) b 17 if i==3 Breakpoint 4 at 0x400bc0: file ex1.cpp, line 17. (gdb) c Continuing. Breakpoint 4, cumSums (x=std::vector of length 10, capacity 10 = {...}, res=std::vector of length 10, capacity 10 = {...}) at ex1.cpp:17 (gdb) p i $1 = 3
- on nettoie et on redémarre
(gdb) delete 4 (gdb) enable 2 (gdb) run
2.2 Exécution pas à pas
Pour simuler l'exécution du programme dans GDB, nous disposons de diverses commandes pour se déplacer dans le code
2.2.1 passer à l'instruction suivante
(gdb) n 22 vector<int> x(N, 0), res;
Note: l'instruction step
(raccourci s), permet quant à elle de tracer
l'intérieur d'une fonction qui serait sur la ligne courante
2.2.2 continuer jusqu'au prochain point d'arrêt
(gdb) c Continuing. Breakpoint 2, sum (size=1, x=0x616c20) at ex1.cpp:8 8 int sum = 0;
2.2.3 continuer jusqu'à une ligne
(gdb) u 11 sum (size=2, x=0x616c20) at ex1.cpp:11 11 return sum;
2.2.4 aller à la fin d'une fonction
(gdb) fin Run till exit from #0 sum (size=1, x=0x616c20) at ex1.cpp:11 0x0000000000400bfa in cumSums (x=std::vector of length 10, capacity 10 = {...}, res=std::vector of length 10, capacity 10 = {...}) at ex1.cpp:17 17 res[i++] = sum(i, x.data()); Value returned is $2 = 0
2.3 examiner la pile d'appel
on peut examiner la pile d'appel avec la commande backtrace
(raccourci bt)
(gdb) c Continuing. Breakpoint 2, sum (size=2, x=0x616c20) at ex1.cpp:8 8 int sum = 0; (gdb) bt #0 sum (size=2, x=0x616c20) at ex1.cpp:8 #1 0x0000000000400bfa in cumSums (x=std::vector of length 10, capacity 10 = {...}, res=std::vector of length 10, capacity 10 = {...}) at ex1.cpp:17 #2 0x0000000000400cc5 in main () at ex1.cpp:24
où #0
, #1
, #2
correspondent aux appels de fonctions.
Note: On peut utiliser bt full
pour avoir les arguments en même temps
2.3.1 on peut lister les arguments
(gdb) info args size = 2 x = 0x616c20
2.3.2 ou les variables locales
(gdb) info locals sum = 32767
2.3.3 et se déplacer dans la pile d'appel (up
and down
)
(gdb) up #1 0x0000000000400bfa in cumSums (x=std::vector of length 10, capacity 10 = {...}, res=std::vector of length 10, capacity 10 = {...}) at ex1.cpp:17 17 res[i++] = sum(i, x.data()); (gdb) info args x = std::vector of length 10, capacity 10 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} res = std::vector of length 10, capacity 10 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
2.4 examiner les variables et la mémoire
Afin de comprendre les dysfonctionnements du programme, il est nécessaire de pouvoir connaître des différentes valeurs des variables du programme.
2.4.1 affichage ponctuel
(gdb) down #0 sum (size=2, x=0x616c20) at ex1.cpp:8 8 int sum = 0; (gdb) p x $4 = (const int *) 0x616c20
2.4.2 affichage réccurent (tant que la variable est dans la portée)
(gdb) display x 1: x = (const int *) 0x616c20 (gdb) n 9 for(int i = 0; i < size; ++i) 1: x = (const int *) 0x616c20 (gdb) undisplay 1
2.4.3 données contiguës
(gdb) p x[0]@10 $5 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
Note: on peut aussi utiliser la syntaxe p *x@10
2.4.4 affichage de la mémoire
On peut aussi afficher directement le contenu de la mémoire avec une
instruction x /FMT addresse
où format contient un nombre de répétitions
et une lettre de format (d
(gdb) x /10d 0x616c20 0x616c20: 0 1 2 3 0x616c30: 4 5 6 7 0x616c40: 8 9
ou une lettre de taille (b,h,w,g)
(gdb) x /8h 0x616c20 0x616c20: 0 0 1 0 2 0 3 0
2.4.5
2.5 points de surveillance
On peut stopper un programme selon le fait qu'une variable ou qu'un emplacement mémoire change de valeur, on utilise des points de surveillance «watchpoint». On peut poser plusieurs types de points de surveillance
type | commande |
---|---|
lecture | rwatch |
écriture | awatch |
général | watch |
2.5.1 exemple avec watch,
On va chercher quand la \(6^{ème}\) valeur de res
change de valeur :
- on remonte la pile d'appel dans
cumSums
(gdb) up
- on affiche la valeur du pointeur du tableau
(gdb) p res._M_impl._M_start $8 = (std::_Vector_base<int, std::allocator<int> >::pointer) 0x616c50
- on pose un point de surveillance sur le \(6^{ème}\) élément
(gdb) watch *((int *)(0x616c50) + 5) Hardware watchpoint 7: *((int *)(0x616c50) + 5)
- on désactive le deuxième point d'arrêt et on rédemarre
(gdb) dis 2 (gdb) c Continuing. Hardware access (read/write) watchpoint 6: *((int *)(0x616c50) + 5 ) Old value = 0 New value = 15 cumSums (x=std::vector of length 10, capacity 10 = {...}, res=std::vector of length 10, capacity 10 = {...}) at ex1.cpp:16 16 for (auto & z : x)
3 Bonus
3.1 scripts
On peut «donner à manger» des scripts de commande à gdb sous la forme
gdb -x ./monScript
une application de ce principe concerne le déverminage parallèle d'un programme MPI avec le multiplexeur de terminal tmux.
3.1.1 Soit le programme C suivant
#include <mpi.h> #include <stdio.h> int main(int argc, char* argv[]) { int world_rank; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); printf("%d-tik egun on!\n", world_rank); MPI_Finalize(); return 0; }
3.1.2 et le script suivant
file ./a.out b 7 run
3.1.3 en utilisant tmux-mpi on
peut exécuter en parallèle le script gdb
tmux-mpi 2 gdb -x gdb_scr
3.2 affichage personnalisé («pretty-printing»)
Certains objets ou structures peuvent présenter une certaine complexité et leur affichage standard par gdb peut être pénibles.
3.2.1 Soit le code suivant
#include <iostream> using namespace std; struct point { int x; int y; int index; point(int _x = 0 , int _y = 0, int _index = 0):x(_x),y(_y),index(_index){} }; int globalIndex=0; //beurk! struct triangle { point t[3]; triangle(int values[6]) { t[0] = point(values[0], values[1],globalIndex++); t[1] = point(values[2], values[3],globalIndex++); t[2] = point(values[4], values[5],globalIndex++); } }; int main() { globalIndex = 0; int coords[6]= {0, 0, 0, 1, 1, 0}; triangle t1(coords); cout << "coucou" << endl; return 0; }
3.2.2 Si on fait un affichage classique d'un triangle
(gdb) p t1 $1 = {t = {{x = 0, y = 0, index = 0}, {x = 0, y = 1, index = 1}, {x = 1, y = 0, index = 2}}}
3.2.3 si on veut afficher seulement les indices, e.g. on peut rajouter le code
suivant à ~/.gdbinit
define pTriangle if $argc == 0 help pTriangle else set $size = 3 end if $argc == 1 printf "Triangle : [%d, %d, %d]\n", $arg0.t[0].index, $arg0.t[1].index, $arg0.t[2].index end end document pTriangle Prints the list of index of a triangle Syntax: pTriangle vector end
3.2.4 On peut dorénavant utiliser la commande pTriangle
pour afficher un
triangle
(gdb) pTriangle t1 Triangle : [0, 1, 2]
Note: il y a une méthode qui utilise des scripts Python et qui a l'avantage de detecter automatiquement l'afficheur dedié à utiliser en fonction de l'objet doc gdb Note: ceci marche avec la version 8.0 de gdb, pour les versions anterieurs voir, la méthode ici pretty-print