====== Le debugger ====== ====== 1. Présentation ====== Le debugger fait partie intégrante de Perl et est toujours distribué avec l'interpréteur. Il est implémenté majoritairement sous la forme de modules Perl (appelés //DB::...//, sans rapport avec les bases de données!). Le debugger est également le socle du profileur (pour étudier les performances d'un programme et les optimiser). ===== Sans le debugger ===== Nous avons vu qu'il était facile d'utiliser la console et quelques //print// bien placés pour obtenir des traces simples et facile à lire, par ex : print "Liste 'bla': ".join(' - ', @bla)."\n"; print "$_ => $tab{$_]\n" foreach keys %tab; Mais ces affichages sont limités, rébarbatifs pour les tableaux, et surtout ne peuvent pas rendre compte facilement de structures imbriquées basées sur les références. On peut alors appeler à la rescousse un module **standard** très pratique : use Data::Dumper; print Dumper(\@list, \%tab); Ceci affichant par exemple : $VAR1 = [ 'lundi', 'mardi', 'mercredi' ]; $VAR2 = { 'aout' => 'chaud', 'mai' => 'bon', 'janvier' => 'froid' }; Notez l'utilisation des références sur des listes ou des tableaux lors de l'appel à //Dumper// : sans passage par référence, les structures sont //mises à plat// sous forme de liste et //Dumper// ne peut que les considérer comme des variables distinctes à afficher. Ainsi avec //print Dumper(@list)// on obtiendrai : $VAR1 = 'lundi'; $VAR2 = 'mardi'; $VAR3 = 'mercredi'; Il reste que ces méthodes d'introspection sont limitées et demandent des modifications fréquentes du code à analyser. ====== 2. Utilisation ====== Il suffit d'invoquer son programme avec l'option //-d// de l'interpréteur : $ perl -d ./server.pl Loading DB routines from perl5db.pl version 1.28 Editor support available. Enter h or `h h' for help, or `man perldebug' for more help. main::(./server.pl:14): my $port = 2500; DB<1> Afin de préserver votre contexte de debug (points d'arrêts, sondes, etc), vous pouvez "recharger" le debugger avec la commande **R** quand vous avez modifié votre programme et que vous voulez continuer à le debugger. Il est donc fortement conseillé d'utiliser en parallèle son éditeur de texte et sa session de debug. **Note**: le debugger utilise la //complétion// partout où cela est possible (en particulier sur les noms de variables et de fonctions), n'oubliez pas d'en abuser ! ===== Flux du programme ===== Le programme est normalement compilé mais son exécution reste en attente. Pour piloter l'exécution on a accès aux commandes suivantes : ^ **Commande** ^ **Nom** ^ **Action**^ | **s** | //Single step//| instruction suivante, avec entrée dans les fonctions| | **n** | //Next// | instruction suivante, sans entrée dans les fonctions| | **r** | //Return// | continue jusqu'à la fin de la fonction courante | | **c** (line/sub) | //Continue// | reprend l'exécution, optionnellement jusqu'à une ligne ou l'entrée d'une fonction| ===== Points d'arrêts ===== Le debugger devient nettement plus intéressant quand il s'agit de programmer des interruptions à des endroits choisis de son programme : ^ **Commande** ^ **Nom** ^ **Action** ^ | **b** (ligne/sub (cond)) | //Breakpoint// | Point d'arrêt, ligne courante ou spécifiée ou fonction, avec condition optionnelle| | **B** ligne | //Delete b// | Effacer le point d'arrêt d'une ligne donnée| | **L** | //List// | Liste les points d'arrêts (et sondes+actions)| L'utilisation des points d'arrêt mérite quelques exemples. On remarque notamment que les conditions sont des expressions Perl normales (sans le //if// qui est sous entendu) : DB<2> b receive_from_client DB<3> L ./server.pl: 73: my ($cli) = @_; break if (1) DB<3> b send_to_client @_[0]->{'name'} eq 'mickey' DB<4> L ./server.pl: 65: my ($cli, $msg) = @_; break if (@_[0]->{'name'} eq 'mickey') **Note**: ces exemples sont basés sur le programme source:/insia/perl/bomberman/server.pl ===== Contexte d'exécution ===== Suivant les instructions exécutées, le debugger met à jour son **contexte** en se déplaçant dans le programme (dernier point d'arrêt positionné, etc). On peut obtenir des informations sur le contexte du code en train d'être analysé ou qui va être exécuté de plusieurs manières : ^ **Commande** ^ **Nom** ^ **Action**^ | **l** (ligne) | //line// | affiche la ligne du contexte courant et quelques suivantes| | **v** (ligne) | //view// | affiche les lignes "autour" du contexte courant| | **.** | //home// | positionne le contexte courant sur la prochaine ligne à exécuter| On peut également à tout moment obtenir la suite d'appel de fonctions (//backtrace//) qui nous a amené dans notre contexte courant : DB<2> T . = main::send_to_client(ref(HASH), 'JOIN-STATUS OK Welcome...') called from file `./server.pl' line 142 . = main::op_join(ref(HASH), 'noname') called from file `./server.pl' line 126 . = main::op_client(ref(HASH)) called from file `./server.pl' line 281 ===== Introspection ===== Ou le nom savant pour décrire la possibilité de recenser les paquets, les fonctions et les variables déclarées. ^ **Commande** ^ **Nom** ^ **Action**^ | **p** ... | //print// | l'instruction //print// de Perl, avec les mêmes limitations| | **x** ... | //examine// | affichage automatique comparable à //Data::Dumper//| | **S** regex | //subs// | affiche les fonctions, recherche par expression régulière| La fonction la plus utilise sera sans conteste **x** (voir les //watches// plus loin toutefois), et suggère les mêmes techniques que //Data::Dumper// concernant les références : DB<5> x %linfo empty array DB<6> x \%linfo 0 HASH(0x8250214) empty hash Les noms des fonctions sont toujours préfixées par leur paquet d'origine, ceci permettant d'analyser n'importe quel emplacement d'un programme modulaire. Le programme principal est représenté par le paquet //main// en Perl : DB<7> S main:: main::BEGIN main::bomb_explode main::check_bomb_timeout main::del_client main::dumpValue main::dumpvar main::is_bomb_at main::is_other_player_at [...] ===== Surveillance ===== Pour faire l'analyse d'un programme selon une variable et ses changements d'état, positionner correctement les points d'arrêts et effectuer les affichagent qui conviennent est peu efficace. Perl peut surveiller pour nous tout changement d'état, et en particulier des conditions quelconques sur une variable quel que soit l'endroit où celle-ci est modifiée : c'est ce qu'on appelle les sondes (//watches// en anglais). ^ **Commande** ^ **Nom** ^ **Action** ^ | **w** | //watch// | pose une sonde: arrête le programme si la variable est modifiée ou l'expression est vérifiée | | **W** ... | //delete watch// | supprime une sonde | La commande **L** permet d'afficher les sondes posées (cf. flux du programme plus haut). Exemple d'utilisation : DB<30> w @clients DB<31> w @clients > 1 Watchpoint 0: @clients changed: old value: // new value: 'HASH(0x85eaf6c)' ====== 3. Le profileur ====== L'outil de "profilage" de Perl est basé sur les mécanisme de debug (principes d'interception de code similaire) et permet d'obtenir un aperçu précis de la répartion du temps dans les différents appels de fonction. Il suffit de lancer son programme avec l'option ad hoc, et on obtient en fin d'exécution un fichier de mesures nommé //tmon.out//. On peut analyser ce fichier avec le programme standard //dprofpp// : $ perl -d:DProf ./client.pl [...] $ dprofpp tmon.out Total Elapsed Time = 19.32377 Seconds User+System Time = 6.613772 Seconds Exclusive Times %Time ExclSec CumulS #Calls sec/call Csec/c Name 97.2 6.429 6.429 870 0.0074 0.0074 SDL::BlitSurface 0.76 0.050 0.050 1 0.0500 0.0500 SDL::Init 0.74 0.049 0.049 435 0.0001 0.0001 SDL::UpdateRects 0.44 0.029 0.029 435 0.0001 0.0001 SDL::Delay 0.29 0.019 0.068 435 0.0000 0.0002 SDL::Surface::update 0.15 0.010 0.010 2 0.0050 0.0050 SDL::IMGLoad 0.15 0.010 0.010 3 0.0033 0.0033 DynaLoader::dl_load_file 0.15 0.010 0.010 5 0.0020 0.0020 SDL::Event::BEGIN 0.15 0.010 0.010 13 0.0008 0.0008 Exporter::export 0.15 0.010 0.010 7 0.0014 0.0014 IO::Handle::BEGIN 0.15 0.010 0.020 6 0.0017 0.0033 IO::Socket::BEGIN 0.14 0.009 0.009 435 0.0000 0.0000 SDL::PollEvent 0.14 0.009 0.038 435 0.0000 0.0001 SDL::App::delay 0.11 0.007 6.436 870 0.0000 0.0074 SDL::Surface::blit 0.08 0.005 6.544 435 0.0000 0.0150 main::win_update