This repository has been archived on 2019-08-08. You can view files and clone it, but cannot push or open issues or pull requests.
s6-pa-tp/TP6/gdb-tutorial2.c
2017-03-27 15:59:08 +02:00

224 lines
7.1 KiB
C

/*gdb-tutorial.c, Matthieu Moy pour ENSIMAG - Modif by LG pour Polytech Lille*/
#include <stdio.h>
void f(void);
void calcul_et_imprime_fact(int n);
int fact(int n);
/* Ce fichier est un tutorial pas-à-pas pour utiliser le déboggeur gdb.
Un débogueur est un outil précieux pour comprendre ce qui ne va pas
dans vos programmes. Les quelques minutes que vous allez passer à
apprendre à l'utiliser seront largement rentabilisées par la suite !
Commencez par le compiler. Pour pouvoir utiliser gdb, il faut
utiliser l'option -g de votre compilateur à la compilation :
$ clang -g gdb-tutorial3.c -o gdb-tutorial
(clang vous met un warning que vous ignorerez).
Vous pouvez lancer l'exécutable gdb-tutorial dans gdb en ligne de
commande comme ceci :
$ gdb ./gdb-tutorial
Commençons par quelque chose de simple : démarrer le programme. On
tape "run", ou simplement la lettre "r" dans la ligne de commande
gdb (dans votre terminal) L'exécution s'arrete sur un "segmentation
fault à la ligne 50 de ce fichier, on d'y retrouve !*/
int main (int argc, char ** argv) {
unsigned i;
int j;
int * ptr = NULL; /* NULL est une adresse invalide, on n'a pas le
droit de déréférencer un pointeur NULL */
printf ("Bonjour\n");
printf ("Je vais faire un acces memoire interdit\n");
/*
La ligne suivante va faire un "Segmentation fault".
Le déboggeur va s'arrêter sur cette ligne.
*/
*ptr = 0;
/*
Bon, le bug était facile à trouver. Mettez la ligne ci-dessus en
commentaire pour éliminer le problème, recompilez, et lisez la
suite.
On va maintenant faire une execution pas-à-pas. Il faut commencer
par donner à gdb un endroit du code où on veut
s'arrêter : un point d'arrêt.
Dans la ligne de commande gdb : "break gdb-tutorial3.c:55"
puis "run"
*/
printf ("On avance ...\n"); /* mettre le point d'arrêt ici */
/*
Maintenant, vous devriez avoir votre programme arrêté sur la ligne
qui précède. Tapez "next", ou simplement "n" pour avancer dans
l'execution du programme, plusieurs fois. Petite astuce, après
avoir tapé "n" une fois, il suffit d'appuyer sur Entrée pour
refaire un "next".
*/
printf ("On avance encore ...\n");
printf ("On avance encore un peu ...\n");
printf ("On avance encore un peu ...\n");
printf ("On avance une derniere fois ...\n");
printf ("Et voila !\n");
/*
Il y a deux commandes pour executer un programme pas à pas :
- next, pour exécuter complètement l'instruction suivante
- step, "rentre" dans la fonction si l'instruction suivante est un
appel de fonction.
Essayez successivement "step" et "next" sur les lignes suivantes.
Au passage "up" permet de revenir un cran plus haut dans la pile d'exec
*/
f(); /*step 1 fois, puis next pour avancer a l'interieur de f*/
f();
f();
/*
Bon, l'execution pas à pas, c'est bien, mais c'est pénible quand
il y a beaucoup de code à exécuter. Par exemple, la boucle
suivante prendrait trop de temps, on veut la sauter. Pour cela,
poser un point d'arrêt sur la ligne qui suit la boucle (98), puis
faites "continue" (ou juste "c") dans gdb.
*/
j = 0;
for (i = 0; i <= 1000; ++i) {
j = j + i;
}
printf ("Fin de la boucle\n"); /* mettre le point d'arrêt ici */
/*
On peut bien sûr visualiser le contenu d'une variable,
avec la commande "print", ou simplement "p". On peut
maintenir l'affichage d'une variable avec "display".
Par exemple, faites maintenant "display i", puis exécutez la
boucle suivante pas à pas. Faites "p j" pour connaitre la valeur
de j. Ensuite mettez un breakpoint apres la boucle (ligne 130)
et "c" pour y aller.
*/
for (i = 1; i <= 1000; i = i * 2) {
j = j + i;
}
/*
On peut aussi afficher des expressions C un peu plus complexes.
Essayez par exemple
(gdb) p i+j
(gdb) p &i
(gdb) p &j
(gdb) p dire_bonjour("Monsieur")
(dans le dernier cas, gdb va effectivement appeler la fonction
dire_bonjour, définie plus bas)
On peut aussi s'amuser un peu avec les pointeurs. Faites:
(gdb) p &i
$2 = (unsigned int *) 0xbf9bfde8
La valeur 0xbf9bfde8, adresse de i, sera a priori différente chez
vous. Affichez maintenant l'expression *(unsigned int *)0xbf9bfde8
(en remplaçant 0xbf9bfde8 par la valeur que vous avez eu ci-dessus).
Vous devriez avoir 1024, qui est bien la valeur de i.
Avant de passer à la suite, on va supprimer cette impression de i
display, puis undisplay n avec n le numéro du display concernant i
*/
printf ("i=%d\n", i);
/*
Tout ça se complique un peu quand on a des appels de fonctions.
Entrez dans la fonction fact appelée à la ligne suivante avec la
commande "s", on se retrouve là bas !
*/
calcul_et_imprime_fact(10);
/*
Une dernière chose : il arrive que le programme parte dans une
boucle infinie, et on voudrait savoir où est cette boucle.
Rien de plus simple : lancez le programme ("c" pour "continue"),
puis tapez Control-c pour arrêter son execution et reprendre la
main dans gdb (dans votre terminal)
Utilisez cette technique pour voir la(les)quelle(s) de ces boucles
est/sont une(des) boucle(s) infinie(s). Commentez les boucles infinies et
recompilez avant de passer à la suite.
*/
j = 1;
while (j > 0) {
++j;
}
i = 10;
while (i >= 0) {
--i;
}
i = 0; j = 0;
while (i == j) {
++i; ++j;
}
/* Voilà, vous avez terminé et vous savez à peu près tout ce que vous
devez savoir pour une utilisation courante. Les plus curieux
pourront lire le manuel de gdb :
http://sourceware.org/gdb/download/onlinedocs/ */
}
void f(void) {
printf ("Appel de f()\n");
printf ("Fin de f()\n");
}
void calcul_et_imprime_fact(int n) {
/*
Re-bonjour,
À ce stade, n doit être égal à 10. Vérifiez avec un "p n". Entrez
dans la fonction fact avec un "step", on s'y retrouve.
*/
int res = fact(n);
printf ("fact(%d) = %d\n", n, res);
}
int fact(int n) {
//printf ("Calcul de fact(%d)\n", n);
/*
Si c'est la première fois que vous passez ici, n doit être égal à
10. Vérifiez avec un "p n". Continuez l'exécution pas à pas avec
"step" (et non "next", pour rentrer dans les appels récursifs
successifs de fact). Faites par exemple 5 appels puis lisez le
commentaire suivant.
*/
/*
Voilà, je suppose que vous en êtes au 5ème appel récursif. Donc,
"n" doit valoir 5. Vérifiez avec "p n".
Mais dans la pile, il y a toujours les autres appels, avec les
valeurs de "n" comprises entre 10 et 5. Faites "where" pour voir
l'état de la pile.
Pour naviguer dans la pile, utilisez les commandes "up" et "down".
Par exemple, faites deux fois "up", puis "p n". Vous voyez la
valeur de "n" au 3ème appel, c'est à dire 7.
Faites deux fois "down", et vous voilà revenu où vous êtiez.
Faites maintenant "finish" pour terminer
l'appel de fonction courant, plusieurs fois, jusqu'à revenir à la fonction
"main" (ligne 167)
*/
if (n <= 1) {
return 1;
} else {
return n * fact(n - 1);
}
}
void dire_bonjour(char * nom) {
printf("Bonjour, %s\n", nom);
}