GDB : Etat des lieux
Un article de GuruMed.
Sommaire
|
GDB - pourquoi vivre sans ?
La méthode de debugging de base est le printf().
Alors, pourquoi ne pas l'utiliser et se casser la tête avec un debugger ?
- on a souvent besoin de mettre et retirer un tas de printf() un peu partout et c'est casse bonbon, ça retire l'attention de là où on devrait la mettre,
- printf() ne peut pas changer le contenu d'une variable,
- printf() ne peut pas stopper le programme temporairement,
- printf() ne peut pas nous rappeler le type d'une variable,
- printf() ne peut pas sauter à une ligne de code arbitraire,
- printf() ne peut pas s'attacher à un processus en cours d'exécution,
- printf() ne protège pas la couche d'ozone,
- printf() rend stérile,
- printf() viole les grand-mères,
- ...
De là à dire que les masochistes raffinés utilisent printf() il n'y a qu'un pas que je vous laisse le loisir de franchir.
GDB - comment en profiter ?
GDB a besoin d'informations pour vous indiquer la ligne en cours d'exécution et pour pouvoir afficher ou modifier une variable. Ces infos sont ajoutées à l'exécutable au moment de la compilation.
GCC dispose de deux switches pour cela:
gcc -g -o monprog monprog.c
cette option fournit les infos de debugging dans le format natif de votre système.
gcc -ggdb -o monprog monprog.c
cette option va produire de l'info dans le format le plus détaillé pour GDB. Dans le cas de l'OS4.1, c'est cette option qui nous intéresse.
Le niveau de détail peut être spécifié, ce niveau allant de 1 à 3, le défaut étant 2:
gcc -ggdb3 -o monprog monprog.c
Bien que GDB soit un des rares debuggers capable de s'en sortir avec des exécutables optimisés, il n'est pas recommandé d'activer les optimisations pour le debugging, donc pas d'option -O.
GDB - peut-on en profiter ?
Bon, j'ai vendu le bazarre, il y a bien un GDB (6.3) dispo, mais à la date d'aujourd'hui (15/11/2009), avec une Sam utilisant l'OS4.1 et le SDK 53.15, GDB n'est pas pleinement opérationnel, il manque quelques fonctionnalités qui rendent le débuggage bien moins facile (possible ?).
Je vais donc faire le tour de ce qui est utilisable actuellement.
GDB - dans la pratique
Dans la pratique, toutes les commandes de GDB sont abréviables.
Pour obtenir la liste des commandes, utilisez la commande help qui affiche des catégories sur lesquelles ont peut appeler à nouveau help pour obtenir la listes des commandes concernées, lesquelles commandes dévoilent leurs détails à l'aide de la même commande help.
Autre point pratique, RETURN sans commande rappelle la commande précédente.
Chargement de gdb
Il y a deux façons de charger gdb, en l'appelant directement avec l'exécutable à débugger:
11.Apps:Dev/CodeBench/Projets/Debug/tests> gdb test GNU gdb 6.3 (AmigaOS build 20050719) Copyright 2004 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 "ppc-amigaos"... (gdb)
ou en chargeant ensuite le fichier à debugger:
11.Apps:Dev/CodeBench/Projets/Debug/tests> gdb GNU gdb 6.3 (AmigaOS build 20050719) Copyright 2004 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 "ppc-amigaos". (gdb) file test Reading symbols from /Apps/Dev/CodeBench/Projets/Debug/tests/test...done. (gdb)
Pour quitter utiliser quit (q).
Lancement et passage de paramètres
L'exécution est lancée à l'aide de la commande run (r):
(gdb) run Starting program: /Files/Docs/Amiga/Articles/Gurumed/test1 Hello world Program terminated with signal SIGQUIT, Quit. The program no longer exists. (gdb)
Si on a besoin de passer des paramètres sur la ligne de commandes, on les place derrière la comande run:
(gdb) run dumb et dumber Starting program: /Files/Docs/Amiga/Articles/Gurumed/test1 dumb et dumber Hello dumb et dumber Program terminated with signal SIGQUIT, Quit. The program no longer exists. (gdb)
Anomalie: Quand on relance plusieurs fois un programme sans quitter GDB, celui-ci nous dit qu'un programme est déjà en cours d'exécution avant de le lancer. Il ne faut pas le croire, ce sont des balivernes:
(gdb) run dumb et dumber
Starting program: /Files/Docs/Amiga/Articles/Gurumed/test1 dumb et dumber
A program is already running:
`/Files/Docs/Amiga/Articles/Gurumed/test1', file type elf32-amiga.
Entry point: 0x1000074
0x01000074 - 0x01000380 is .text
0x01001000 - 0x01001030 is .rodata
0x01010000 - 0x01010008 is .ctors
0x01010008 - 0x01010010 is .dtors
0x01010010 - 0x01010014 is .sdata
0x01010014 - 0x01010038 is .sbss
Hello dumb et dumber
Program terminated with signal SIGQUIT, Quit.
The program no longer exists.
(gdb)
Point particulier: GDB garde les derniers arguments qu'on lui a fourni. Pour les voir, utiliser la commande show args, pour s'en débarraser, utiliser set args.
Utilisation des points d'arrêt
Les points d'arrêt peuvent être positionnés au moyen de la commande break (b) suivie d'un nom de fonction, d'un numéro de ligne ou d'un numéro de ligne dans un fichier. On peut examiner les points d'arrêt avec info breakpoints (ib):
(gdb) b PrintArgs Breakpoint 1 at 0x6fc73208: file test1.c, line 4. (gdb) b 27 Breakpoint 2 at 0x6fc732e8: file test1.c, line 27. (gdb) b test1.c:9 Breakpoint 3 at 0x6fc73230: file test1.c, line 9. (gdb) i b Num Type Disp Enb Address What 1 breakpoint keep y 0x6fc73208 in PrintArgs at test1.c:4 2 breakpoint keep y 0x6fc732e8 in main at test1.c:27 3 breakpoint keep y 0x6fc73230 in PrintArgs at test1.c:9 (gdb)
Les points d'arrêts peuvent être retirés avec clear (cl) et leur localisation ou delete (d) et leur identifiant. Ils peuvent être désactivés et réactivés au moyen des commandes disable et enable suivies de leur identifiant.
Si on désire positionner un point d'arrêt utilisé une seule fois, on utilise tbreak (tb) qui positionne un point qui sera effacé automatiquement la première fois qu'il sera atteint.
Bon, GDB s'arrête sur un point d'arrêt, et après ? Ben pour repartir, il faut utiliser la commande continue (c).
Problème, avec le SDK 53.15 sur Sam440, dès que GDB s'arrête sur un point d'arrêt permanent, il oublie ensuite de s'arrêter sur quelque condition que ce soit, b, tb ou adv. Il reste donc la solution de ne travailler qu'avec des points d'arrêts temporaires ou des commandes advance abordées plus loin.
Exécution pas à pas
S'arrêter sur un point d'arrêt et repartir c'est bien, s'arrêter et poursuivre au ralenti, c'est mieux. GDB propose deux types de commandes pas à pas, next (n) et step (s).
next passe à la ligne suivante du source, sans descendre dans les appels de fonction.
step passe à l'instruction suivante, en descendant dans les appels de fonction.
finish sort de la fonction en cours et retourne au point d'appel.
Le problème, c'est qu'avec le SDK 53.15 sur Sam440, next et step boudent et font la même chose que continue. Un debugger sans mode pas à pas, c'est diablement moins intéressant.
Exécution jusque...
Pour contrôler l'exécution de notre programme, il y a encore la commande advance (adv) qui prend en argument une identification de ligne identique à la définition d'un point d'arrêt. On peut voir cette commande comme la combinaison du positionnement d'un point d'arrêt temporaire et la commande continue.
Bonne nouvelle, cette commande semble fonctionner presque correctement... à condition de ne pas utiliser un point d'arrêt permanent et à condition de ne pas jouer dans des boucles.
L'examen des variables
Le type des variables
On peut demander à GDB de nous rappeler le type d'une variable avec la commande ptype (pt):
(gdb) ptype idx type = int (gdb) t
Examen occasionnel
Quand on a besoin d'examiner une variable, on utilise la commande print (p).
(gdb) p argc $1 = 1 (gdb) p argv[1] $5 = 0x50dc1b20 "toto" (gdb)
Bonne nouvelle, il existe également une commande printf.
(gdb) printf "argv[%d]=\"%s\"\n", 0, argv[0] argv[0]="gdb" (gdb)
On peut également typer une variable à la volée si nécessaire ou spécifier le format d'affichage avec:
- /o pour octal,
- /x pour hexadécimal,
- /d pour décimal,
- /u pour unsigned decimal,
- /t pour binaire,
- /f pour flottant,
- /a pour adresse et
- /c pour caractère.
Examen permanent
Quand on a besoin de suivre une variable au cours d'une exécution, la commande display (disp ) permet d'afficher le contenu d'une variable à chaque arrêt d'exécution.
(gdb) disp idx 1: idx = 1 (gdb) c Continuing. BS 52e2c5d0 Current action: 2 PrintArgs (count=4, argv=0x52d8ab58) at test1.c:11 11 } 1: idx = 4 (gdb)
La modification de variable
Les variables peuvent également être modifiées à l'aide de la commande set variable abréviable par set (<expression>):
(gdb) p argc $3 = 4 (gdb) set variable argc = 1 (gdb) p argc $4 = 1 (gdb) set (argc = 2) (gdb) p argc $5 = 2 (gdb)
La surveillance de variable
La commande watch (wa) permet de surveiller une variable (ou une expression) et d'arrêter l'exécution si une instruction modifie la variable (ou vérifie l'expression), ce qui peut être très intéressant pour localiser la modification fortuite d'une variable. Bon, dans la pratique, ça ne fonctionne pas avec notre SDK, étonnant, non ?
Il existe des variantes rwatch et awatch qui surveillent respectivement l'accès en lecture ou l'accès en lecture ou écriture, mais elles ne sont pas supportées par notre plateforme.
Bref, point de salut ici.
Visualisation du source
La commande list (l) permet d'afficher 10 lignes centrées sur la ligne d'exécution courante. Les appels suivants à list affichent les 10 lignes suivantes et ainsi de suite.
Pour remonter dans le source, on utilise list -.
Pour lister une ligne précise, on l'indique derrière list.
Pour se localiser, la commande where affiche une pile d'appel simplifiée.
(gdb) where #0 PrintArgs (count=4, argv=0x56536b40) at test1.c:7 #1 0x6fc72344 in main (argc=4, argv=0x56536b40) at test1.c:34 (gdb)
La pile d'appel
En plus de where, la pile des appels de fonction en cours peut être affichée au moyen de la commande backtrace (bt).
(gdb) bt #0 PrintArgs (count=4, argv=0x49c30b48) at test1.c:9 trad_frame_addr_p: 1 trad_frame_realreg_p: 0 trad_frame_value_p: 0 alt 1 #1 0x6fc732d4 in main (argc=4, argv=0x49c30b48) at test1.c:20 (gdb)
Le niveau 0 est la fonction en cours (PrintArgs) qui a été appelée depuis le niveau 1 (main), et ainsi de suite.
Les commandes up et down permettent de se déplacer dans la pile d'appel afin d'avoir accès au contexte de pile concerné, pour examiner les variables automatiques par exemple.
(gdb) up #1 0x6fc72344 in main (argc=4, argv=0x56536b40) at test1.c:34 34 PrintArgs(argc, argv); (gdb) down #0 PrintArgs (count=4, argv=0x56536b40) at test1.c:7 7 for(idx = 1; idx < count; ++idx) (gdb)
L'attachement à un processus
L'attachement à un processus peut être très intéressant pour inspecter l'état d'un processus en cours d'exécution. Un exemple qui me vient à l'idée serait de s'attacher au processus d'un serveur Ftp pour le debugger en direct live.
Bon, je n'ai pas réussi à m'attacher à un processus, là aussi pas de surprise.
En conclusion
GDB est-il d'un grand secours en l'état actuel des choses ?
Bonne question.
On peut tenter un debuggage à l'aide de points d'arrêts temporaires , de commandes advance, d'affichage de variable et de modification de variable. Ca n'apporte pas beaucoup plus que des printf(), mais ça permet d'adapter ce qu'on veut voir et où on veut aller pendant l'exécution du programme, sans avoir besoin d'arrêter, de modifier le source et de recompiler.
Ca peut donc être un petit peu mieux que des printf() mais il y a des cas, comme les boucles, où GDB ne s'arrêtera que la première fois. Il faudra donc envisager un doux mélange de printf() et de debuggage.
Si maintenant on se demande si les développeurs disposent de bons outils pour mettre au point leurs développements, ma réponse est clairement: NON.
A tître de comparaison, lorsque je développe sous Delphi, le debugger intégré stoppe sur la ligne ayant déclenché une exception. Si la cause ne saute pas aux yeux, j'inspecte la pile d'appel, je mets un point d'arrêt en amont, je relance le bazarre, arrivé sur mon point d'arrêt j'inspecte les variables intéressantes en m'approchant du point d'exception jusqu'à localiser l'anomalie. Dans la pratique, je localise la cause immédiatement ou en quelques minutes, ce qui me permet d'utiliser mon temps à concevoir plutôt qu'à chercher la fichue ligne qui a déclenché un bête accès mémoire illégal.
Mon temps sur Amiga étant pris sur mon temps libre, j'ai encore plus besoin d'efficacité dans mes développements que pour mon boulot.
Alors s'il vous plait, donnez nous des outils qui fonctionnent.
