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

#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

Date: 09 janvier 2018

Author: Marc Fuentes

Created: 2018-01-09 ar. 12:31

Validate