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«.
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.
|
|
|
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
|