Viele verdammen Debugger als Teufelszeug. Oft erweisen sie sich aber als letzte Rettung. Perl hat einen sogar eingebaut. Mit ihm beschäftigt sich dieser Snapshot – passend zum Schwerpunkt des Hefts.
Linus Torvalds mag keine Debugger und auch keine Programmierer, die sie verwenden. Zu leichtfertig lässt sich nach seiner Ansicht ein Stück Code zusammenschustern und mit einem Debugger geradebiegen. Allerdings macht sich kaum ein Chaosprogrammierer Design-Gedanken. Das rächt sich später oft, weil die Software schwer wart- oder erweiterbar ist. Gewissenhaft eingebettetes Logging macht aber Debugger in vielen Fällen überflüssig. Wie[3] erläutert, hilft Log::Log4perl dabei, einen maßgeschneiderten Debugger in die Applikation einzubauen und per Fernsteuerung zu aktivieren.
Probelauf
Manchmal nützt aber alles nichts. Was tun, wenn ein Programmstück unerwartet reagiert, die Dokumentation darüber nichts enthält und der (natürlich von anderen geschriebene) Code zu kompliziert ist, um seinen Ablauf durch Studieren der Listings zu verstehen? Perl enthält einen Debugger, der Fehler recht schnell mit Breakpoints, Actions und Watchpoints einkreist. Listing 1 zeigt den praktischen Fünfzeiler »wsrv«, der anzeigt, was für ein Webserver hinter einer URL steckt. Der Aufruf »wsrv http://sun.com« etwa gibt preis, dass Sun auf eigene Technik setzt »Sun Java System Web Server 6.1«.
| Listing 1: »wsrv« |
|---|
01 #!/usr/bin/perl -w 02 ########################################### 03 # wsrv - Display a URL's web server 04 ########################################### 05 use LWP::Simple; 06 my $url = shift or die "usage $0 url"; 07 my (@fields) = head($url) or 08 die "Fetch failed"; 09 print "Server: $fields[4]n"; |
Soll das Skript stattdessen im Debugger laufen, setzt man einfach ein »perl -d« vor den vollständigen Skriptpfad und alle Kommandozeilenargumente, also »perl -d wsrv http://microsoft.com«. Das führt zu folgender Ausgabe:
Loading DB routines from perl5db.pl version 1.27 Editor support available. Enter h or `h h' for help or `man perldebug' for more help. main::(wsrv:7): my $url = shift or die "usage $0 url";
Das Debugger-Kommando »n« (Next) führt die erste Zeile des Skripts aus – sie ist auch am Ende der obigen Ausgabe zu sehen. Diese Zeile extrahiert die URL aus dem Argumenten-Array »@ARGV«:
DB<1> n main::(wsrv:8): my (@fields) = head($url)or main::(wsrv:9): die "Fetch failed";
Perl hat die erste Zeile des Programms ausgeführt, die auf der Kommandozeile angegebene URL extrahiert und sie in der Variablen »$url« abgelegt. Die nächste ausführbare Anweisung besteht aus den Zeilen 8 und 9 von »wsrv«. Statt sie mit »n« vollständig auszuführen, geht das Kommando »s« (Step) in Einzelschritten vor. Prompt steigt der Debugger in die in Zeile 9 aufgerufene Funktion »head« hinab:<c>@15 Li:
LWP::Simple::head(…/LWP/Simple.pm:70):
70: my($url) = @_;
Das Kommando »l« (List) verschafft einen Überblick über die nächsten Zeilen:
70==> my($url) = @_; 71: _init_ua() unless $ua; 72 73: my $request = HTTP::Request->new(HEAD => $url); 74: my $response = $ua->request($request); [...]
Um im Code weiter nach unten zu fahren, ohne ihn auszuführen, genügt ein weiteres »l«-Kommando. Alternativ auch »l 70+20« (20 Zeilen ab Zeile 70) oder »l 70-100« (Zeilen 70 bis 100). Die nächste ausführbare Zeile zeigt »==>« an.
Zurück zum Anfang
Durch die Eingabe eines Punkts kehrt die Listinganzeige wieder zum Ausgangspunkt zurück. Die Eingabe von »r« (Return) weist den Debugger dazu an, die aktuelle Funktion bis zum Ende auszuführen und anschließend sofort im Hauptprogramm anzuhalten.
list context return from LWP::Simple::head: 0 'text/html' 1 16144 2 1107018115 3 1107028115 4 'Microsoft-IIS/6.0' main::(wsrv:9): print "Server: $fields[4]n";
Freundlicherweise zeigt der Debugger sogar die Rückgabewerte der Funktion »head()« an, unmittelbar vor der nächsten ausführbaren Zeile, der »print()«-Funktion im Hauptprogramm. Interessiert der Wert des Array-Elements »$fields[4]«, fördert ihn der Befehl »p« (Print) des Debuggers zutage, noch bevor die »print()«-Zeile des Hauptprogramms ihn preisgibt. Auf »p $fields[4]« folgt »Microsoft-IIS/6.0«
Um den Inhalt des Arrays »@fields« auszugeben, eignet sich »p @fields«, allerdings wäre die Ausgabe nicht gerade augenfreundlich. Für kompliziertere Datenstrukturen bietet der Debugger daher die Funktion »x«:
DB<2> x @fields 0 'text/html' 1 16144 2 1107021419 3 1107031419 4 'Microsoft-IIS/6.0'
Ähnliches gilt für Hashes, die man sogar direkt im Debugger definieren kann:
DB<3> %h = (donald => 'duck',
klaas => 'klever')
DB<4> x %h
0 'donald'
1 'duck'
2 'klaas'
3 'klever'
Wer statt der Array-artigen Anzeige lieber Key-Value-Paare möchte, übergibt stattdessen eine Hash-Referenz an »x«:
DB<5> x %h 0 HASH(0x837a5f8) 'donald' => 'duck' 'klass' => 'klever'
Inzwischen hat sich die Nummer im Prompt erhöht. Sie taucht auch in der History-Liste auf, die »H« ausgibt:
DB<6> H 5: x %h 4: x %h 3: %h=(donald=>'duck',klaas=>'klever') 2: x @fields 1: p $fields[4]
Um zum Beispiel das »$field[4]«-Element nochmals auszugeben, genügt ein Ausrufezeichen gefolgt von der Nummer des History-Eintrags »!1«. Mit diesem kleinen Rüstsatz an Befehlen lassen sich nun schon kompliziertere Aufgaben anpacken.
| Tabelle 1: Dynamische Navigation |
||
|---|---|---|
| Kommando | Bedeutung | |
| Programmausführung steuern | ||
| n | Nächste Zeile ausführen, danach anhalten | |
| s | Nächste Zeile starten, in Unterfunktion anhalten | |
| r | Aktuelle Funktion fertig durchlaufen, dann stopp | |
| R | Zurück zum Start und noch einmal ausführen | |
| Variablen anzeigen | ||
| p | Wert ausgeben | |
| x | Dump (x %hash) | |
| Source-Navigation | ||
| l | Vorwärts blättern | |
| – | Rückwärts blättern | |
| v | Code um aktuelle Zeile herum zeigen | |
| . | Zurück zur aktuellen Zeile | |
| f | In eine andere Source-Datei wechseln | |
| Erweiterte dynamische Navigation | ||
| c Zeile | Code bis zu dieser Zeile ausführen, dann stoppen | |
| c Funktion | Code bis zur Funktion ausführen, in ihr anhalten | |
| b Zeile | Breakpoint in Zeile setzen | |
| b Funktion | Breakpoint in Funktion setzen | |
| b Zei/Fu | Breakpoint mit Bedingung Bedingung | |
| a Zei/Fu Aktion | Actionpoint in Zeile/Funktion | |
| w Zei/Fu | Watchpoint in Zeile/Funktion Variable | |
| < Command | Pre-Prompt setzen | |
| L | Breakpoints, Watchpoints, Actions anzeigen | |
| B/A/W | Breakpoints, Watchpoints, Actions löschen | |
Echtes Problem
Ein fiktiver Programmierer hat ein neues Modul »Irgendwie::Sowieso« fertig und möchte es für eine CPAN-Release vorbereiten. Dazu gehören eine Datei »Makefile.PL« nach Listing 3 und eine Datei mit dem Modul »lib/Irgendwie/Sowieso .pm«, die vielleicht etwas Dokumentation enthält (Listing 2). Ein Aufruf von »perl Makefile.PL« führt zu der etwas kryptischen Fehlermeldung:
WARNING: Setting ABSTRACT via file 'lib/Irgendwie/Sowieso.pm' failed at ExtUtils/MakeMaker.pm line 606
Auf den ersten Blick wird nicht klar, was hier passiert ist. Hinter »Makefile.PL« steckt »ExtUtils::MakeMaker«, ein Urgestein der Perl-Programmierung, dessen Innereien nicht ganz einfach zu verstehen sind. Auch hier gewährt der Debugger Einblick mit »perl -d Makefile.PL«. Da die Warnung oben die Zeile 606 aus der Datei »ExtUtils/MakeMaker.pm« als Fehlerquelle nennt, lohnt es sich, diese Stelle näher zu inspizieren. Das File-Kommando »f ExtUtils/MakeMaker.pm« führt dorthin. Danach braucht die Zeile 606 noch einen Breakpoint (»b 606«). Der Befehl »c« (Continue) weist den Debugger an, bis dorthin fortzufahren:
DB<2> c
606: push @{$self->{RESULT}},
$self->nicetext($self->$method( %a ));
Statt erst den Breakpoint zu setzen und mit »c« dorthin zu fahren, hätte »c 606« gleich losgelegt und in Zeile 606 gestoppt. Dann wäre dort aber kein permanenter Breakpoint gesetzt worden, der sich später wieder verwenden lässt. Der obige »push«-Befehl scheint das Ergebnis eines Methodenaufrufs an ein Array anzuhängen. Es wäre interessant, welche Methode »$method« aufruft. Das bringt »p $method« in Erfahrung. Das Ergebnis ist »post_initialize«.
Der Perl-Debugger geht mit »n« eine Zeile weiter – keine Reaktion. »n« führt die Zeile zwar aus, aber eine Warnung erscheint nicht. Offenbar führt »MakeMaker« die Zeile mehrfach aus und erst beim n-ten Mal tritt der Fehler auf. Kein Problem: Bevor es mit »c« (continue) in die nächste Iteration geht (gestoppt vom Breakpoint in 606), definiert »a« eine Aktion in dieser Zeile – nämlich den Inhalt von $method auszugeben »a 606 print(“$methodn”);«.
Diese Aktion wird der Debugger nun immer ausführen, wenn er an Zeile 606 vorbeirauscht, auch bei voller Geschwindigkeit. Weiter geht es mit einem Continue-Kommando:
DB<4> c
606: push @{$self->{RESULT}},
$self->nicetext($self->$method( %a ));
platform_constants
Da der Breakpoint in Zeile 606 noch gesetzt ist, hält der Debugger in der nächsten Runde wieder an.
| Listing 2: Beispielmodul |
|---|
01 =head1 NAME 02 Irgendwie::Sowieso - blah blah blah 03 =head1 SYNOPSIS 04 use Irgendwie::Sowieso; |
| Listing 3: »Makefile.PL« |
|---|
01 ####################################
02 # Makefile.PL for Irgendwie::Sowieso
03 ####################################
04 use ExtUtils::MakeMaker;
05 WriteMakefile(
06 'NAME' => 'Irgendwie::Sowieso',
07 'VERSION_FROM' =>
08 'lib/Irgendwie/Sowieso.pm',
09 'PREREQ_PM' => {},
10 (
11 $] >= 5.005
12 ? (
13 ABSTRACT_FROM =>
14 'lib/Irgendwie/Sowieso.pm',
15 AUTHOR =>
16 'Mike Schilli <m@perlmeister.com>'
17 ) : ()
18 ),
19 );
|
Warnung gesucht
Noch immer taucht die gesuchte Warnung nicht auf. Also den Breakpoint in Zeile 606 mit »B 606« löschen und das Programm mit »c« weiterlaufen lassen:
DB<4> B 606 DB<5> c [...] staticmake test ppd WARNING: Setting ABSTRACT via file 'lib/Irgendwie/Sowieso.pm' failed
Ergebnis: Die »ppd«-Methode löst die Warnung aus. Leider ist die letzte Aktion übers Ziel hinausgeschossen, aber keine Bange, mit »R« fängt das Programm wieder von vorne an. Ein neuer Breakpoint in »ExtUtils/MakeMaker.pm«, Zeile 606, wird diesmal mit einer Bedingung verknüpft:
DB<5> f ExtUtils/MakeMaker.pm DB<6> b 606 $method eq "ppd" DB<7> c
An diesem Breakpoint hält der Debugger nur, wenn die Variable »$method« den String »ppd« enthält. Das Programm startet, stoppt und der Debugger zeigt wieder den Inhalt der Zeile 606. Der Befehl »p $method« bestätigt, dass die eben konfigurierte Bedingung auch tatsächlich eingetreten ist.
Welche Methoden die »$self«-Referenz in Zeile 606 ausführt, lässt sich mit einem »m«-Befehl im Einzelnen ermitteln:
DB<8> m $self [...] via MM -> ExtUtils::MM -> ExtUtils::UMM_Unix: post_initialize via MM-> ExtUtils::MM ->ExtUtils::MM_Unix: postamble via MM -> ExtUtils::MM->ExtUtils::MM_Unix: ppd
Die Methode »ppd« ist offenbar im Modul »ExtUtils::MM_Unix« definiert. Zur Fehlersuche soll das Programm mit »c« weiterlaufen, aber bremsen, sobald »ExtUtils::MM_Unix::ppd« aktiv ist:
DB<9> c ExtUtils::MM_Unix::ppd ExtUtils::MM_Unix::ppd(ExtUtils/MM_Unix.pm:3322): 3322: my($self) = @_;
Der Debugger steht jetzt in der ersten Zeile der Methode »ppd« im Modul »ExtUtils::MM_Unix«. Eine kurze Orientierung mit »l« zeigt, dass »ppd« die Methode »parse_abstract()« aufruft:
DB<10> l
3322==> my($self) = @_;
3323: if ($self->{ABSTRACT_FROM}){
3324: $self->{ABSTRACT} = $self->parse_abstract($self->{ABSTRACT_FROM}) or
Mit »c Funktion« soll der Debugger weiterlaufen und in der ersten Zeile von »parse_abstract« anhalten:
DB<11> c parse_abstract ExtUtils::MM_Unix::parse_abstract( ExtUtils/MM_Unix.pm:3045): 3045: my($self,$parsefile) = @_;
Die nächsten 20 Zeilen zeigt »l +20« und verrät folgenden regulären Ausdruck, mit dem »Makefile.PL« den Abstract aus dem Modul holt »057:next unless /^($packages-s)(.*)/;«. Das Kommando »w« setzt zur Kontrolle einen Watchpoint auf die »$package«-Variable, damit das Programm nach »c« wieder stoppt, sobald »$package« seinen Wert ändert:
DB<2> w $package DB<3> c Watchpoint 0: $package changed: old value: '' new value: 'Irgendwie-Sowieso'
Jetzt ist alles klar: Für das Modul »Irgendwie::Sowieso« sucht die Methode »parse_abstract()« nach dem regulären Ausdruck »/^Irgendwie-Sowiesos-=s)(.*)/«. Der Modulname muss dabei am Zeilenanfang stehen, dann wird der Abstract gefunden.
Grafische Oberflächen
Wer lieber eine grafische Oberfläche mit Mausbedienung möchte, kann den Data Display Debugger (DDD, siehe Artikel im Heftschwerpunkt) mit dem Perl-Backend verbinden. Hier ein Aufruf mit »wsrv« auf die Microsoft-Homepage: »ddd -perl wsrv http://microsoft.com«.
Abbildung 1 zeigt die Oberfläche in Aktion. Sie hilft beim Setzen von Breakpoints oder beim Überwachen von Ausdrücken (etwa des Skalars »$url« in Abbildung 1). Ähnliches ließe sich im Perl-Debugger mit dem Pre-Prompt-Kommando »<« erreichen. Weitere GUIs sind die kostenpflichtige IDE Komodo und Ptkdb, das im CPAN zu finden ist:
perl -MCPAN -e 'install(Tk,Devel::ptkdb)' perl -d:ptkdb wsrv http://microsoft.com
Abbildung 2 zeigt Ptkdb beim Durchschreiten der »request«-Methode des »LWP::UserAgent«-Pakets. In der rechten Spalte sind die Elemente des »LWP:: UserAgent«-Objekts zu sehen, das der Methode übergeben wurde.

Abbildung 2: Der auf Perl/Tk basierende grafische Debugger Ptkdb lässt sich leicht vom CPAN installieren.
Tracing
Zum Abschluss noch ein kleiner Trick, um ein Programm jede ausgeführte Zeile anzeigen zu lassen. Dieses Tracing lässt sich über die Environment-Variable »PERLDB_OPTS« einstellen, bevor der Aufruf des Debuggers erfolgt:
PERLDB_OPTS="NonStop=1 AutoTrace=1 frame=2" perl -dS Programm
Die »AutoTrace«-Option setzt den Debugger in den Tracing-Modus, indem er jede Sourcezeile erst ausgibt, bevor er sie ausführt. Mit der »NonStop«-Option hält der Debugger weder am Anfang noch am Ende an. Mit »frame=2« kommen beim Eintritt und beim Verlassen von Unterfunktionen »entering …«- und »exiting …«-Meldungen dazu. Wer zusätzlich übergebene Parameter und Rückgabewerte von Unterfunktionen braucht, setzt »frame=4«. Und schließlich sucht Perls Option »-S« ein ausführbares Perl-Skript » Programm« nicht nur im aktuellen Verzeichnis, sondern auch in allen in »$PATH« definierten Pfaden.
Eine kleine Einführung in den Gebrauch des Debuggers findet sich in jeder neuen Perl-Distribution unter »perldoc perldebtut«. Die ausführliche Dokumentation steht in »perldebug« und wer ins Innere des Debuggers vordringen möchte, sollte sich »perldebguts« zu Gemüte führen. Besonders zu empfehlen ist[2], die ultimative Referenz zum Perl-Debugger im handlichen Taschenformat. (jcb)
| Infos |
|---|
| [1] Listings: [ftp://www.linux-magazin.de/pub/listings/magazin/2005/04/Perl]
[2] Richard Foley, “Perl Debugger Pocket Reference”: O\’Reilly 2004 [3] Michael Schilli, “Retire your Debugger, log smartly with Log::Log4perl”: [http://www.perl.com/pub/a/2002/09/11/log4perl.html] [4] Peter Scott und Ed Wright: “Perl Debugged”, Addison-Wesley 2001 |
| Der Autor |
|---|
![]() Michael Schilli arbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat “Goto Perl 5” (deutsch) und “Perl Power” (englisch) für Addison-Wesley geschrieben und ist unter [mschilli@perlmeister.com] zu erreichen. Seine Homepage: [http://perlmeister.com] |







