Welcome to Coding : Sécurité Programmation Réseaux

Search   in  

 Create an Account Home | Submit News Your Account Content | Topics | Top 10  


Accueil
· Home
· Listing des Articles
· Top 10
· Repository des Exploits

Les sujets / parties
· C / C ++
· Visual Basic
· Asm
· Reseaux
· Java
· Securite
· Divers

Utile
· Listing des Articles

· Telecharger
· Le Forum
· Liens
· Proposer un article

Top20 des Downloads
· 1: Etude des reseaux generalites et protocoles
· 2: Cheval de troie en VB avec sources
· 3: Netcat 1.1
· 4: Keylogger
· 5: Etudes des reseaux hauts debits architectures et protocoles
· 6: Ecoute de port
· 7: Etude du Smart Spoofing
· 8: Win Packet Capture Utils
· 9: Tutorial on Traffic Interception on Switched Lan using ARP spoofing
· 10: Cours de C

User Info
Welcome, Anonymous
Nickname
Password
(Register)
Membership:
Latest: trapcodien
New Today: 1
New Yesterday: 0
Overall: 2207

People Online:
Visitors: 42
Members: 1
Total: 43

Online Now:
01: trapcodien

  
News: Tutorial sur le fonctionnement de GDB (debugger sous linux)
Posted on Thursday, December 18 @ 17:54:23 CET
Topic: Divers
Divers

	GDB est le débogeur GNU (Gnu DeBugger). Il permet de trouver les bogues des programmes en C/C++ ou en assembleur, et plus généralement de voir le déroulement d'un programme quand il s'exécute. 

GDB sait déboguer les exécutables écrits en C, C++ ou assembleur . Pour donner des informations intéressantes, GDB utilise les symboles qu'il trouve dans les exécutables. Ces symboles sont générés par le compilateur... à condition de lui dire de le faire ! 

GDB est le débogeur GNU (Gnu DeBugger). Il permet de trouver les bogues des programmes en C/C++ ou en assembleur, et plus généralement de voir le déroulement d'un programme quand il s'exécute.


Les commandes les plus utilisées de GDB sont :  run  break  where et bt  step et next  display et print  help  kill  quit (utile...) et dans une moindre mesure file et core. Qu'est-ce qu'on débogue ? Comme mentionné plus haut, GDB sait déboguer les exécutables écrits en C, C++ ou assembleur (cf. cours de logiciels de base). Pour donner des informations intéressantes, GDB utilise les symboles qu'il trouve dans les exécutables. Ces symboles sont générés par le compilateur... à condition de lui dire de le faire !


Le compilateur est generalement utilise est gcc. L'option à donner est -g (ou éventuellement -ggdb), à la fois à la production des fichiers objets (.o) et à l'édition de liens (production de l'exécutable proprement dit).


Les commandes Lancer GDB Tout d'abord, voyons comment lancer GDB. Il y a trois manières de le faire : gdb (sans argument) gdb gdb la plus usuelle étant la deuxième. Le fichier à déboguer est un exécutable ; en ce qui concerne le fichier core, nous y reviendrons plus tard. Ces commandes lancent GDB. Pour les deux premières versions de la ligne de commande, GDB affiche un message de bienvenue puis l'invite qui est (gdb).


Pour la suite, nous supposerons que le fichier à déboguer s'appelle bugger et qu'il est le résultat de la compilation de bugger.c.


Les commandes de gdb

La permière chose à faire est de donner à manger à ce pauvre GDB, sans quoi il serait bien en peine de trouver des bogues. Si vous n'avez pas spécifié de fichier à déboguer sur la ligne de commande lançant GDB, il est temps de taper :


file bugger


ce qui a pour effet de charger le fichier code en mémoire. Si tout se passe bien, GDB répond :


Reading symbols from bugger...done.


On va enfin pouvoir faire des choses...


Trouver où est le bug

Quand on n'a aucune idée de l'endroit où est le méchant bogue, on laisse travailler GDB. Ou plutôt, on le met au travail, parce que, c'est pas pour dire, mais il n'a pas fait grand chose pour le moment, le bougre.

On utilise la commande :


run


Si le programme a besoin d'arguments, on les tape après le run, comme si on tapait bugger à partir du shell.


GDB lance le programme, qui s'exécute normalement jusqu'à ce que le bug remonte à la surface. Si votre programme fait des lectures au clavier ou des sorties à l'écran, GDB les fera également. Tout se passe comme en ligne de commande, jusqu'à la terminaison du programme, qui peut être :


 normale (GDB dit Program exited normally.) ;  anormale (GDB dit Program exited abnormally., ce qui est souvent provoqué par la commande abort du C) ;  avec un code de retour (GDB dit Program exited with code XX., ce qui est provoqué par la commande exit(XX) ;  avec un signal, le plus souvent SIGSEGV (GDB dit Program received signal SIGSEGV, Segmentation fault.), suivi d'informations exploitables.

Au point où on en est, il est temps de travailler sur un exemple concret. Prenons le programme suivant :



#include #include char *lire_fichier(FILE* f){         int c,i=0;         char* str;         while ((c=getc(f))!=EOF){                 str[i++]=(char)c;         }         str[i]=' ';         return str; } int main(int argc, char **argv){         char *chaine;         FILE* file;         file=fopen(argv[1],"r");         chaine=lire_fichier(file);         printf("Contenu du fichier: %s ",chaine);         return 0; }
Deux mots sur le programme. Il ouvre le fichier dont le nom est passé en argument, la fonction lire_fichier lit le contenu du fichier f et le met dans la chaîne str.


Le bug, c'est (évidemment) qu'aucune mémoire n'est allouée pour le pointeur str. C'est une erreur classique, et qui permet de bien voir les fonctionnalités de GDB, parce que, après tout, on est là pour ça.


On reprend à zéro. L'invite de mon shell est $ et je commence par compiler le programme :



$ gcc -g -o bugger bugger.c Puis, lançons GDB :



$ gdb GNU gdb 4.18 Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB.  Type "show warranty" for details. This GDB was configured as "sparc-sun-solaris2.8". (gdb) file bugger Reading symbols from bugger...done. (gdb) run /etc/hosts Starting program: /users3/bchastag/bugger /etc/hosts Program received signal SIGSEGV, Segmentation fault. 0x1067c in lire_fichier (f=0xff33c304) at bugger.c:8 8                       str[i++]=(char)c; (gdb)
On a déjà beaucoup d'informations. Décortiquons la ligne qui suit Segmentation fault.


0x1067c, c'est l'adresse de l'instruction qui a tout fait planter. En général, c'est assez illisible, alors on l'oublie.

in lire_fichier désigne la fonction qui contient le vilain bogue.

f=0xff33c304 est la valeur du paramètre de la fonction lire_fichier. Ici, c'est un sale pointeur assez incompréhensible, mais cela peut déjà donner des informations. Par exemple si la valeur est 0x0, c'est un pointeur nul, et c'est louche.


at bugger.c:8 est le nom du fichier source qui contient la fonction, et le numéro de la ligne où est apparu le bogue, ici la ligne 8. La ligne suivante est carrément la ligne de code qui est fautive.


Maintenant, on a de vraies informations sur l'erreur. Normalement, quand on voit que c'est une affectation qui plante, on doit tout de suite penser que c'est un problème de mémoire pas allouée, mais bon, pour la démonstration, on va continuer à osculter cet exemple. Mais comment est-on arrivé là ?


C'est bien beau de savoir quelle ligne provoque le Segmentation Fault, mais ce n'est pas forcément là qu'est le bogue.


Première question à se poser : d'où vient-on ? Pour le savoir, on utilise la commande bt (pour "backtrace") ou where :



(gdb) where #0  0x1067c in lire_fichier (f=0xff33c304) at bugger.c:8 #1  0x106f4 in main (argc=2, argv=0xffbefb94) at bugger.c:18 (gdb)
qui affiche la pile des appels de fonctions. Le format est le même que précédemment, à cela près que chaque ligne est précédée de #numéro. Nous verrons ce que cela signifie plus tard.


Ceci apporte surtout qu'on sait comment on est arrivé à la ligne contenant le bogue. Ici, on sait que la fonction main appelle lire_fichier à la ligne 18 et que le bogue apparaît ligne 8. Et les variables dans tout ça ?


Maintenant, il serait intéressant de savoir les valeurs des variables. La commande à utiliser est :



display

ou aussi :



print

La différence entre les deux fonctions est que display fait l'affichage après chaque commande tandis que print ne fait qu'un seul affichage.


Nous avons trois variables : c, i et str. Demandons gentiment à GDB de nous dire leur valeur :



(gdb) display i 1: i = 0 (gdb) display c 2: c = 35 (gdb) display str 3: str = 0x10034 "" (gdb) Cela est bien, mais str est un tableau, donc ce serait plus intéressant de connaître la valeur de son contenu. Rien de plus facile :



(gdb) display str[i] 4: str[i] = Cannot access memory at address 0x0. Disabling display 4 to avoid infinite recursion. (gdb)
Ce message d'erreur est bien clair : il y a un problème avec la variable str. Il faut noter que cet affichage dépend du système sur lequel on travaille : chez moi, je n'ai pas cette erreur, mais str[i] donne un affichage (un peu débile, certes). Si la variable est "normale", ça marche. Et maintenant, qu'est-ce qu'on fait ?


Tout ceci est bien, mais ce serait encore mieux si on pouvait suivre le déroulement du programme dans tous ses détails. On va donc relancer le programme et faire du pas à pas où on veut.


Tout d'abord, il faut arrêter notre programme. Bien qu'il soit planté, GDB considère que le programme tourne encore (pour afficher les variables, la pile...). Pour arrêter le programme, on fait :



kill . (gdb) kill Kill the program being debugged ? (y or n) y (gdb) Avant tout, on va placer un point d'arrêt au début de la fonction qui fait planter (le reste, on s'en fiche). Pour cela, on utilise la commande :



break (gdb) break 4 Breakpoint 1 at 0x105ec: file bugger.c, line 4. (gdb) Note : si on débogue un programme utilisant plusieurs fichiers source, il faut préciser le fichier. On taperait alors :


break bugger.c:4 On peut aussi donner un nom de fonction :



break lire_fichier

Maintenant, on relance le programme, qui va s'arrêter au premier point d'arrêt trouvé (notons que GDB reprend automatiquement les arguments du précédent appel à run lorsqu'on utilise run sans arguments.) :




(gdb) run Starting program: /users3/bchastag/bugger bugger /etc/hosts Breakpoint 1, lire_fichier (f=0x3) at bugger.c:4 4       char *lire_fichier(FILE* f){ 3: str = 0x10034 "" 2: c = 2010 1: i = -12906352 (gdb)
Remarquons au passage que les variables dont nous avons demandé l'affichage à l'aide de display sont bien affichées automatiquement après chaque commande. Faisons du pas à pas. La commande est :



next
Là, il faut apporter quelques précisions. La commande next exécute les instructions les une après les autres, jusque là rien de compliqué. Et les appels de fonctions ? Et bien, la commande next les considère comme une seule instruction, et exécute donc son code sans rien détailler.



Si on veut entrer dans la fonction avec le débogueur pas à pas, il faut utiliser la commande step. Il en va de même pour les boucles (while et for).


Voyons maintenant le résultat de next jusqu'à l'erreur (notons que quand on ne tape que sur entrée, GDB exécute la commande précédente) :


(gdb) next lire_fichier (f=0x8049708) at bugger.c:5 5               int c,i=0; 3: str = 0x10034 "" 2: c = 2010 1: i = -12906352 (gdb) 7               while ((c=getc(f))!=EOF){ 3: str = 0x10034 "" 2: c = 2010 1: i = 0 (gdb) 8                       str[i++]=(char)c; 3: str = 0x10034 "" 2: c = 35 1: i = 0 (gdb) Program received signal SIGSEGV, Segmentation fault. 0x080484cb in lire_fichier (f=0xff33c304) at bugger.c:8 8                       str[i++]=(char)c; 3: str = 0x10034 "" 2: c = 35 1: i = 0 (gdb) Il faut noter qu'en fonctionnement normal (hors plantage), la ligne de code affichée par GDB est celle qui va s'exécuter. Elle n'a donc pas encore été exécutée.


Quelques commandes sont associées à step et next :


 ni et si : comme next et step mais peuvent prendre un argument N pour exécuter N instructions d'un coup ;  cont : reprend l'exécution du programme jusqu'au prochain point d'arrêt.


Pour quitter GDB, la commande est tout simplement quit. Si votre programme est toujours en train de tourner, GDB vous demande confirmation pour quitter. Pour éviter cela, il suffit de penser à tuer son processus (commande kill) avant de quitter.


Pour aller plus loin

Quelques commandes utiles

Une commande très utile de GDB est help. Elle affiche la liste des catégories de commandes. Un help commande donne davantage d'informations sur la commande. A utiliser sans modération.


Une autre commande utile est info, suivi ou non d'un nom de commande, par exemple info threads affichera les informations concernants les threads courants.


GDB connaît la commande make, très utile pour reconstruire un exécutable sans avoir à quitter puis relancer GDB


Enfin, GDB connaît la complétion automatique pour les noms de fichers : taper TAB affichera toutes les possibilités, comme dans un shell.

Sélectionner un bloc de mémoire

Quand on a des appels de fonctions compliqués, il n'est pas rare que l'on veuille des informations non pas sur le bloc de mémoire courant, mais sur un autre (un appelant). C'est souvent le cas quand on a un malloc qui plante : GDB affiche fièrement le numéro de ligne fautif de malloc.c, mais on s'en fiche.


Rappelons la trace pour notre petit exemple :


(gdb) where #0  0x1067c in lire_fichier (f=0xff33c304) at bugger.c:8 #1  0x106f4 in main (argc=2, argv=0xffbefb94) at bugger.c:18 (gdb)
Les seules variables que l'on puisse afficher ici sont celles visibles par la fonction lire_fichier. Pour accéder aux variables du main, il faut taper la commande suivante :


frame (gdb) frame 1 #1  0x106f4 in main (argc=2, argv=0xffbefb94) at bugger.c:18 18              chaine=lire_fichier(file); (gdb)
Voilà à quoi servent les fameux numéros que nous donnaient la commande where. Et maintenant, on peut taper print chaine sans problème.


A quoi servent ces fichiers core ?

Quand un programme plante, il n'est pas rare qu'un fichier core soit créé. Ce fichier contient l'état de la mémoire au moment du plantage (ce qui explique qu'ils soient si gros).

Dans certains cas, le bogue n'est pas déterministe. Dans d'autres cas, il y a un bogue en fonctionnement normal, mais il disparaît quand on met des traces ou quand on utilise GDB. Ces deux derniers cas sont dûs au fait que la fonction printf et GDB modifient le tas (heap), et donc le programme obtenu n'est plus celui qui plante.


Dans ces cas, le fichier core va nous aider. Comme il contient l'état du système au moment du plantage, on devrait pouvoir comprendre d'où vient le ploblème.


Reprenons notre exemple, et lançons GDB :



$ gdb bugger GNU gdb 4.18 Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB.  Type "show warranty" for details. This GDB was configured as "sparc-sun-solaris2.8"... (gdb) core core warning: core file may not match specified executable file. Core was generated by `./bugger /etc/hosts'. Program terminated with signal 11, Segmentation Fault. Reading symbols from /usr/lib/libc.so.1...done. Reading symbols from /usr/lib/libdl.so.1...done. Reading symbols from /usr/platform/SUNW,Sun-Fire/lib/libc_psr.so.1...done. #0  0x1067c in lire_fichier (f=0xff33c304) at bugger.c:8 8                       str[i++]=(char)c; (gdb)
La commande core core dit qu'on va utiliser le fichier de type core qui s'appelle "core".


On constate que le résultat est le même que celui obtenu par un run. Magique...


Cependant, les bogues nécessitant l'usage du fichier core... je n'en n'ai jamais vu. Ces fichiers sont assez gourmands en espace mémoire (80 Ko pour cet exemple basique, mais j'ai déjà vu des fichiers core de 2 Go), mieux vaut donc ne pas les garder. Pour cela, plusieurs moyens :


 les enlever régulièrement à la main. Pas très pratique, ni très sûr (un oubli est vite arrivé) ;
 limiter leur taille au niveau du shell en ajoutant au .tcshrc (respectivement le .bashrc) la ligne suivante :

limit coredumpsize 0
(ou, en bash : ulimit -c 0) ;
 ajouter à la fin de votre .xsession la commande :

find -name 'core' -exec /bin/rm -f {} ;


Le mieux étant d'utiliser les deux dernières solutions ensemble.


Pour plus de documentation


La documentation complète de GDB peut être trouvée sur le site de GNU à l'adresse suivante :
http://www.gnu.org/manual/gdb-5.1.1
Extract from ensigogne : writted by laurent.goujon@ensimag.fr

 
Liens connexes
· Plus à propos de Divers
· Nouvelles transmises par Romain_Le_Guen


L'article le plus lu à propos de Divers:
Tutorial sur le fonctionnement de GDB (debugger sous linux)


Article Rating
Average Score: 3
Votes: 2


Please take a second and vote for this article:

Excellent
Very Good
Good
Regular
Bad


Options

 Format imprimable Format imprimable


Associated Topics

AsmLinuxSecurité

PHP-Nuke Copyright © 2005 by Francisco Burzi. This is free software, and you may redistribute it under the GPL. PHP-Nuke comes with absolutely no warranty, for details, see the license.
Page Generation: 0.81 Seconds