Introduction à GNU GDB

Table of Contents

Salut à toi ! Bienvenue dans 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

Pour les commandes de base que l'on veut charger à chaque fois, on peut par exemple les placer dans ~/.gdbinit. Par exemple pour éviter de demander confirmation on peut écrire

set confirm off

3.1.1 une application de ce principe concerne le déverminage parallèle d'un programme MPI

  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
  1. on peut lancer plusieurs terminaux qui exécutent chacun en parallèle le script gdb
    mpirun -np 4 xterm -e gdb -x gdb_src
    
  2. ou mieux, si l'implementation de MPI est openmpi, on pourra utiliser le

    multiplexeur de terminal avec le projet tmpi

    tmpi 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 Vieille méthode en script GDB : 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}}}
  1. si on veut afficher seulement les indices, e.g. on peut rajouter le code

    suivant à ~/.gdbinit

    define pTriangle
    if $argc == 0
        help pTriangle
    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
    
  2. On peut dorénavant utiliser la commande pTriangle pour afficher un triangle
    (gdb) pTriangle t1
    Triangle : [0, 1, 2]
    

3.2.3 Nouvelle méthode basé sur des scripts Python : exemple en Fortran

program print_triangle
    type point
        integer ::x, y, index
    end type point

    type triangle
        type(point) :: t(3)
    end type triangle

    integer :: global_index
    type(point) :: z
    type(triangle) :: t1
    z = point(2,2, 3)
    t1 = init_triangle( [0, 0, 0, 1, 1, 0] , [1, 2, 3])
    print *, "coucou"

contains
    pure function init_triangle(values, indexes) result(t)
        type(triangle) :: t
        integer, intent(in) :: values(6), indexes(3)
           t%t(1) = point(values(1), values(2), indexes(1))
           t%t(2) = point(values(3), values(4), indexes(2))
           t%t(3) = point(values(5), values(6), indexes(3))
    end function init_triangle
end program print_triangle
  1. On peut écrire le code d'afficheur suivant dans le fichier pretty.py
    class PointPrinter(object):
        def __init__(self, val):
            self.val = val
        def to_string(self):
            return ("("+str(self.val["x"])+", "+str(self.val["y"])+")")
           # return (str(self.val["index"]))
    
    def Point_lookup(val):
        if str(val.type) == 'Type point':
           return PointPrinter(val)
        return None
    
    class TrianglePrinter(object):
        def __init__(self, val):
            self.val = val
        def to_string(self):
            return "".join(map((lambda x : str(self.val["t"][x]["index"])+" "), [1, 2, 3]))
    
    def Triangle_lookup(val):
        if str(val.type) == 'Type triangle':
           return TrianglePrinter(val)
        return None
    
    gdb.pretty_printers.append(Point_lookup)
    gdb.pretty_printers.append(Triangle_lookup)
    
  2. en utilisant le script d'execution suivant si on compile le programme

    avec gfortran -o fo -g prettry_print.f90

    file fo
    b 15
    run
    python execfile("pretty.py")
    p z
    p t1
    

    Note: ceci marche avec la version 8.0 de gdb, pour les versions anterieurs voir, la méthode ici pretty-print

Date: 16 Janvier 2019

Author: Marc Fuentes

Created: 2019-01-16 az. 20:36

Validate