Ceci est une ancienne révision du document !
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 :
{| bgcolor=“#f7f8ff” cellpadding=“3” cellspacing=“0” border=“1” style=“font-size: 95%; border: gray solid 1px; border-collapse: collapse;”
'Commande ' | 'Nom ' |
||
's ' | Single step |
||
'n ' | Next |
||
'r ' | Return |
||
'c ' (line/sub) | Continue |
||
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 :
{| bgcolor=“#f7f8ff” cellpadding=“3” cellspacing=“0” border=“1” style=“font-size: 95%; border: gray solid 1px; border-collapse: collapse;”
'Commande ' | 'Nom ' |
||
'b ' (ligne/sub (cond)) | Breakpoint |
||
'B ' ligne | Delete b |
||
'L ' | List |
||
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 :
{| bgcolor=“#f7f8ff” cellpadding=“3” cellspacing=“0” border=“1” style=“font-size: 95%; border: gray solid 1px; border-collapse: collapse;”
'Commande ' | 'Nom ' |
||
'l ' (ligne) | line |
||
'v ' (ligne) | view |
||
'. ' | home |
||
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.
{| bgcolor=“#f7f8ff” cellpadding=“3” cellspacing=“0” border=“1” style=“font-size: 95%; border: gray solid 1px; border-collapse: collapse;”
'Commande ' | 'Nom ' |
||
'p ' … | print |
||
'x ' … | examine |
||
'S ' regex | subs |
||
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).
{| bgcolor=“#f7f8ff” cellpadding=“3” cellspacing=“0” border=“1” style=“font-size: 95%; border: gray solid 1px; border-collapse: collapse;”
'Commande ' | 'Nom ' | |||
'w ' variable | expression | watch |
||
'W ' … | delete watch | |||
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