Using The Perl Debugger (
Page 1 of 6 )
One of Perl's better-kept secrets is its built-in debugger, a powerful utility that allows developers to rapidly track down errors in their Perl scripts. This article introduces you to the Perl debugger, explaining how to use it to step through scripts, set breakpoints, inspect variables and objects, watch expressions and perform stack traces.Perl aficionados might find this hard to believe, but to a lot of people, Perl
is - to put it bluntly - one of the most terrifying languages on the planet.
There are a number of reasons for this, and only some of them are
unwarranted. Novice developers find the language's sometimes-complex syntax
intimidating, and its freewheeling "there's more than one way to do it" attitude
confusing. The special symbols and variables that experienced Perl developers
sprinkle liberally around their code (can you say @_?), coupled with the
intricacies of Perl variable scoping, regular expressions, object-oriented
programming and modules, can leave them bewildered and weak at the knees, and
the language's terse error messages only serve to increase their frustration
when things go wrong.
I can sympathize. Not so long ago, I too was one of these terrified souls,
lost in the darkness of the Perl universe. Until the day a friend introduced me
to the Perl debugger. And then there was light.
{mospagebreak title=Start
Me Up}
Most casual Perl users aren't even aware that Perl ships with a debugger, but
it does...and it's quite a good one too. In addition to allowing you to
arbitrarily jump around your code, it also allows you to execute statements on a
line-by-line basis, set breakpoints and watch-expressions, inspect variables and
trace program flow (among other things).
In order to demonstrate the features of the debugger, I'll be using some
sample Perl scripts. Here's the first one, a script which accepts an identifier
as input argument, connect to a database, retrieves a list of email addresses
and sends them email.
#!/usr/bin/perl
# load module
use DBI();
unless($ARGV[0])
{
print "ERROR: Please provide a valid input ID\n\n";
}
# get user input into variable
my $id = $ARGV[0];
# connect
my $dbh = DBI->connect("DBI:mysql:database=db198;host=localhost",
"root", "secret", {'RaiseError' => 1}) or die ("Cannot
connect to database");
# set up email message
my $sendmail = "/usr/sbin/sendmail -t";
my $reply_to = "Reply-to: foo\@bar.org";
my $subject = "Subject: FOOBAR";
my $content = "Hello and how are you doing? This is the message body";
# now retrieve email addresses and send mail to each
my $sth = $dbh->prepare("SELECT email FROM users WHERE users.id = '$id'");
$sth->execute(); while(my $ref = $sth->fetchrow_hashref()) {
my $to = $ref->{'email'};
open(SENDMAIL, "|$sendmail") or die "Cannot send mail: $!";
print SENDMAIL $reply_to ."\n";
print SENDMAIL $subject . "\n";
print SENDMAIL "To: " . $to . "\n";
print SENDMAIL "Content-type: text/plain\n\n";
print SENDMAIL $content;
close(SENDMAIL);
}
# clean up
$dbh->disconnect();
Perl's built-in debugger can be invoked simply by adding the -d option to
your Perl command line, as below:
$ perl -d mailer.pl 877
Loading DB routines from perl5db.pl
version 1.19
Editor support available.
Enter h or `h h' for help, or `man perldebug' for more help.
main::(mailer.pl:6): unless($ARGV[0])
main::(mailer.pl:7):
{
DB<1>
This will pop you into the debugger and place you at the first line of your
script.
The
DB<1>
marker is the debugger command prompt; this is where you will enter debugger
commands. The number in angle braces following the prompt keeps track of the
number of commands you've entered, so that you can easily reuse a previous
command.
Commands entered at the prompt which are not debugger commands are evaluated
as Perl statements, and executed - as illustrated below:
DB<2> print 8+10;
DB<3> 18
DB<3> $a=10/2
DB<4> print $a;
DB<5> 5
You can enter multi-line commands into the debugger by separating them with a
backslash, as in the example below:
DB<2> @flavours = ("vanilla", "chocolate", "strawberry");
DB<3> for $f (@flavours) { \
cont: print "$f\n"; \
cont:
};
vanilla
chocolate
strawberry
Help is available via the "h" command (don't be intimidated, most of the
following will be explained over this article),
DB<1> h
List/search source lines: Control script
execution:
l [ln|sub] List source code T Stack trace
- or . List
previous/current line s [expr] Single step [in expr]
v [line] View around
line n [expr] Next, steps over subs
f filename View source in file
<CR/Enter> Repeat last n or s
/pattern/ ?patt? Search forw/backw r
Return from subroutine
M Show module versions c [ln|sub] Continue until
position
Debugger controls: L List break/watch/actions
o [...] Set
debugger options t [expr] Toggle trace
[trace
expr]
<[<]|{[{]|>[>] [cmd] Do pre/post-prompt b
[ln|event|sub] [cnd] Set breakpoint
! [N|pat] Redo a previous command B ln|*
Delete a/all breakpoints
H [-num] Display last num commands a [ln] cmd Do cmd
before line
= [a val] Define/list an alias A ln|* Delete a/all actions
h
[db_cmd] Get help on command w expr Add a watch expression
h h Complete help
page W expr|* Delete a/all watch exprs
|[|]db_cmd Send output to pager ![!]
syscmd Run cmd in a subprocess
q or ^D Quit R Attempt a restart
Data
Examination: expr Execute perl code, also see: s,n,t expr
x|m expr Evals expr
in list context, dumps the result or lists
methods.
p expr Print
expression (uses script's current package).
S [[!]pat] List subroutine names
[not] matching pattern
V [Pk [Vars]] List Variables in Package. Vars can be
~pattern or !pattern.
X [Vars] Same as "V current_package [Vars]".
y [n
[Vars]] List lexicals in higher scope <n>. Vars same as V.
For more
help, type h cmd_letter, or run man perldebug for all docs.
and you can obtain detailed help with the "h h" command
DB<1> h h
Help is currently only available for the new 580 CommandSet,
if you really
want old behaviour, presumably you know what you're doing ?-)
T Stack trace.
s [expr] Single step [in expr].
n [expr] Next, steps
over subroutine calls [in expr].
<CR> Repeat last n or s command.
r
Return from current subroutine.
c [line|sub] Continue; optionally inserts a
one-time-only breakpoint
at the specified position.
l min+incr List incr+1
lines starting at min.
l min-max List lines min through max.
l line List
single line.
...
Once your script has completed exiting, you can restart it with the "R"
command, as below,
Debugged program terminated. Use q to quit or R to restart,
use O
inhibit_exit to avoid stopping after program termination,
h q, h R or h O to
get additional info.
DB<10> R
Warning: some settings and
command-line options may be lost!
Loading DB routines from perl5db.pl version 1.19
Editor support
available.
Enter h or `h h' for help, or `man perldebug' for more help.
main::(mailer.pl:6): unless($ARGV[0])
main::(mailer.pl:7):
{
DB<10>
or exit the debugger (this works even during script execution) by typing "q",
and you will be immediately returned to your command prompt.
DB<1> q
Don't do that just yet, though - after all, you're here to learn more about
this strange and wonderful new animal, and it would be rude to leave so quickly.
Next up, I'll show you how to step through a script and execute it on a
line-by-line basis.
* Moving Up
Now that you're in the debugger, let's start with something basic: moving
around your script. Type "l" at the command prompt, and watch as the debugger
goes busily to work listing the first ten lines of your script.
DB<1> l
6==> unless($ARGV[0])
7 {
8: print "ERROR:
Please provide a valid input ID\n\n";
9 }
10
11 # get user input into
variable
12: my $id = $ARGV[0];
13
14 # connect
15 # fix these as
per your local settings
Typing "l" again will take you to the next screen of code,
DB<1> l
16==> my $dbh =
DBI->connect("DBI:mysql:database=db198;host=localhost",
"secret", "",
{'RaiseError' => 1}) or die ("Cannot connect to database"); 17
18 # set up
email message
19: my $sendmail = "/usr/sbin/sendmail -t";
20: my $reply_to
= "Reply-to: foo\@bar.org";
21: my $subject = "Subject: FOOBAR";
22: my
$content = "Hello and how are you doing? This is the message body";
23
24
# now retrieve email addresses and send mail to each
25: my $sth =
$dbh->prepare("SELECT email FROM users WHERE users.id
=
'$id'");
and so on.
You can have the debugger display a specific line by specifying a line number
to the "l" command,
DB<3> l 19
19==> my $sendmail = "/usr/sbin/sendmail
-t";
or even display a block of lines by specifying a range of line numbers.
DB<4> l 20-25
20==> my $reply_to = "Reply-to:
foo\@bar.org";
21: my $subject = "Subject: FOOBAR";
22: my $content =
"Hello and how are you doing? This is the message body";
23
24 # now
retrieve email addresses and send mail to each
25: my $sth =
$dbh->prepare("SELECT email FROM users WHERE users.id
=
'$id'");
You can display the previous line with the "-" command,
DB<5> -
10==>
11 # get user input into variable
12:
my $id = $ARGV[0];
13
14 # connect
15 # fix these as per your local
settings
16: my $dbh =
DBI->connect("DBI:mysql:database=db198;host=localhost",
"root", "secret",
{'RaiseError' => 1}) or die ("Cannot connect to database"); 17
18 # set up
email message
19: my $sendmail = "/usr/sbin/sendmail -t";
or return to the last executed line with the "." command.
DB<5> .
main::(mailer.pl:16): my $dbh
=
DBI->connect("DBI:mysql:database=db198;host=localhost", "root",
DBI->"secret",
{'RaiseError' => 1}) or die ("Cannot connect to
database");
You can view the code above and below a specific line with the "v"
command,
DB<3> v 27
24: my $description =
$ref->{'description'};
25: $sth->finish();
26
27 # now use the
group ID to get the group text
28: my $sth = $dbh->prepare("SELECT
group_text FROM groups WHERE
group_id =
'$gid'");
29:
$sth->execute();
30: my $ref = $sth->fetchrow_hashref();
31: my
$gtext = $ref->{'group_text'};
32: $sth->finish();
33
or even perform basic search operations within the debugger.
DB<3> /FOOBAR
21: my $subject = "Subject:
FOOBAR";
This ability to move around your script comes in very handy when dealing with
large and complex scripts.