Cultured Perl: Debugging Perl with ease

Catch the bugs before they bite

Teodor Zlatanov walks you through both the built-in Perl debugger and CPAN's Devel::ptkdb. The Perl debugger is powerful but frustrating to navigate. CPAN's Devel::ptkdb, on the other hand, works wonders by simplifying code debugging and thereby saving hours of your precious time. In his discussion Zlatanov concentrates on explaining debugging methods and general concepts rather than looking at specific tools.

Share:

Teodor Zlatanov, Programmer, Gold Software Systems

Teodor ZlatanovTeodor Zlatanov graduated with an M.S. in computer engineering from Boston University in 1999. He has worked as a programmer since 1992, using Perl, Java, C, and C++. His interests are in open source work on text parsing, 3-tier client-server database architectures, UNIX system administration, CORBA, and project management. He can be contacted at tzz@bu.edu.



01 November 2000

Also available in Japanese

Bugs are as inevitable as death and taxes. Nevertheless, the following material should help you avoid the pitfalls of bugs. Some of the examples will require Perl 5.6.0 or at least 5.005. If you want to try the Emacs examples, you may also need to install the Emacs editor.

The trouble with bugs

Software developers commonly underestimate the importance of software testing. The fundamental reason for this is simple: dealing with bugs is hard! Occasionally bugs even require developers to re-write major portions of a project from scratch, because bugs are good at revealing fundamental deficiencies of the code.

I think debugging is so important that it should be allocated at least 30% of the project timeline. Extra debugging time will produce a better product. If, on the other hand, you cut debugging time to deliver the software more quickly, you will almost always double the amount of post-production time you spend fixing problems that come to light later.

Bugs come in three basic varieties: coding bugs, documentation bugs, and requirement bugs. Requirement bugs are generally the result of imprecise or missing requirements. Documentation bugs reside in the manuals or online help. Coding bugs result from programmer errors in implementing the requirements. Unfortunately requirement and documentation bugs are outside the scope of what we can do here, so we'll have to limit ourselves to "detecting," "solving," and "fixing" coding bugs.

Debugging terms

Breakpoint: a place in the program where execution will stop and control will return to the debugger.

Debugger: 1) the program in control of the debugging process, with facilities specifically written to support it; 2) the person performing the debugging.

Execution stack: the list of functions entered so far during program execution. If the main program calls function A, for instance, and function A calls function B, the execution stack is: main -> A -> B.

Step in: continuing execution step by stepping inside the currently selected line of code. If the current line of code contains a function, stepping in will go inside that function.

Step over: continuing execution over the currently selected line of code. Execution occurs unconditionally, regardless of the line's contents, and control returns to the debugger upon completion.

Watch: an automatically triggered action when a variable changes value.

See theResourcessection below for additional sources of information on debugging.


Basic concepts of debugging

We have defined coding bugs as errors by the programmer in implementing the requirements. Coding bugs cause improper program behavior (behavior deviating from the requirements). So the first thing a programmer should know before writing or debugging a program is the program's requirements.

Debugging is not unlike a hunt. The first step is detecting the bugs (by observing wrong behavior and identifying a pattern). At this stage, bugs are only symptoms.

The second step is solving the bugs. Someone who knows the program well should examine the bugs and understand their root causes, since bugs must be eliminated at their source. If the code becomes easier to understand and you have no more code than there was in the buggy version, you are probably on the right track.

The third and final step is fixing the bugs (note that "fixing" is distinct from "solving"). The debugger places the source code change into "live" production and checks it for correctness. If the code isn't correct, you've either failed to solve the bug or, even worse, you've introduced a new bug. Since solving bugs only to introduce new ones is not fun, make sure to fix every bug once you've solved it!

To make sure that you locate the bugs quickly and understand them well, you should have a clear mental image of the program's actions at every major branch, with every module and class, throughout the debugging process. This of course implies that you have a solid understanding of the language the code is written in (Perl, in our case). Because of all these requirements, good software testers are hard to find.


The Perl debugger

The first recourse for a Perl programmer is the debugger that comes with Perl. As you'll see, it's easy enough to use from the start.

 perl -d program.pl

The Perl debugger comes with its own help ('h' or 'h h', for the long and short help screens, respectively). The perldoc perldebug page (type "perldoc perldebug" at your prompt) has a more complete description of the Perl debugger.

So let's start with a buggy program and take a look at how the Perl debugger works. First, it'll attempt to print the first 20 lines in a file.

 #!/usr/bin/perl -w

 use strict;

 foreach (0..20)
 {
  my $line = 
;
  print "$_ : $line";
 }

When run by itself, buggy.pl fails with the message: "Use of uninitialized value in concatenation (.) at ./buggy.pl line 8, line 9." More mysteriously, it prints "9:" on a line by itself and waits for user input.

Now what does that mean? You may already have spotted the bugbear that came along when we fired up the Perl debugger.

First let's simply make sure the bug is repeatable. We'll set an action on line 8 to print $line where the error occurred, and run the program.

 > perl -d ./buggy.pl buggy.pl

 Default die handler restored.

 Loading DB routines from perl5db.pl version 1.07
 Editor support available.

 Enter h or `h h' for help, or `man perldebug' for more help.

 main::(./buggy.pl:5):   foreach (0..20)
 main::(./buggy.pl:6):   {
   DB<1>  use Data::Dumper

   DB<1>  a 8 print 'The line variable is now ', Dumper $line

The Data::Dumper module loads so that the autoaction can use a nice output format. The autoaction is set to do a print statement every time line 8 is reached. Now let's watch the show.

  DB
 c
 The line variable is now $VAR1 = '#!/usr/bin/perl -w
 ';
 0 : #!/usr/bin/perl -w
 The line variable is now $VAR1 = '
 ';
 1 : 
 The line variable is now $VAR1 = 'use strict;
 ';
 2 : use strict;
 The line variable is now $VAR1 = '
 ';
 3 : 
 The line variable is now $VAR1 = 'foreach (0..20)
 ';
 4 : foreach (0..20)
 The line variable is now $VAR1 = '{
 ';
 5 : {
 The line variable is now $VAR1 = ' my $line = 
;
 ';
 6 :  my $line = 
;
 The line variable is now $VAR1 = ' print "$_ : $line";
 ';
 7 :  print "$_ : $line";
 The line variable is now $VAR1 = '}
 ';
 8 : }
 The line variable is now $VAR1 = undef;
 Use of uninitialized value in concatenation (.) at ./buggy.pl line 8, <> line 9.
 9 :

It's clear now that the problem occurred when the line variable was undefined. Furthermore, the program waited for more input. And pressing the Return key eleven more times created the following output:

The line variable is now $VAR1 = '
';
10 : 
The line variable is now $VAR1 = '
';
11 : 
The line variable is now $VAR1 = '
';
12 : 
The line variable is now $VAR1 = '
';
13 : 
The line variable is now $VAR1 = '
';
14 : 
The line variable is now $VAR1 = '
';
15 : 
The line variable is now $VAR1 = '
';
16 : 
The line variable is now $VAR1 = '
';
17 : 
The line variable is now $VAR1 = '
';
18 : 
The line variable is now $VAR1 = '
';
19 : 
The line variable is now $VAR1 = '
';
20 : 
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<3>

By now it's obvious that the program is buggy because it unconditionally waits for 20 lines of input, even though there are cases in which the lines will not be there. The fix is to test the $line after reading it from the filehandle:

#!/usr/bin/perl -w
use strict;
foreach (0..20)
{
 my $line = 
;
 last unless defined $line;		# exit loop if $line is not defined
 print "$_ : $line";
}

As you see, the fixed program works properly in all cases!


Concluding notes on the Perl debugger

The Emacs editor supports the Perl debugger and makes using it a somewhat better experience. You can read more about the GUD Emacs mode inside Emacs with Info (type M-x info). GUD is a universal debugging mode that works with the Perl debugger (type M-x perldb while editing a Perl program in Emacs).

With a little work, the vi family of editors will also support the Perl debuggers. See the perldoc perldebug page for more information. For other editors, consult each editor's documentation.

The Perl built-in debugger is a powerful tool and can do much more than the simple usage we just looked at. It does, however, require a fair amount of Perl expertise. Which is why we are now going to look at some simpler tools that will better suit beginning and intermediate Perl programmers.


Devel::ptkdb

To use the Devel::ptkdb debugger you first have to download it from CPAN (seeResources below) and install it on your system. (Some of you may also need to install the Tk module, also in CPAN.) On a personal note, Devel::ptkdb works best on UNIX systems like Linux. (Although it's not theoretically limited to UNIX-compatible systems, I have never heard of anyone successfully using Devel::ptkdb on Windows. As the old saying goes, anything is possible except skiing through a revolving door.)

If you can't get your system administrator to perform the installation for you (because, for instance, you are the system administrator), you can try doing the following at your prompt (you may need to run this as root):

perl -MCPAN -e'install Tk'
perl -MCPAN -e'install Devel::ptkdb'

After some initial questions, if this is your first time running the CPAN installation routines, you will download and install the appropriate modules automatically.

You can run a program with the ptkdb debugger as follows (using our old buggy.pl example):

perl -d:ptkdb buggy.pl buggy.pl

To read the documentation for the Devel::ptkdb modules, use the command "perldoc Devel::ptkdb". We are using version 1.1071 here. (Although updated versions may come out at any time, they should not look very different from the one we're using.)

A window will come up with the program's source code on the left and a list of watched expressions (initially empty) on the right. Enter the word "$line" in the "Enter Expr:" box. Then click on the "Step Over" button to watch the program execute.

The "Run" button will run the program until it finishes or hits a breakpoint. Clicking on the line number in the source-listing window sets or deletes breakpoints. If you select the "BrkPts" tab on the right, you can edit the list of breakpoints and make them conditional upon a variable or a function. (This is a very easy way to set up conditional breakpoints.)

Ptkdb also has File, Control, Data, Stack, and Bookmarks menus. These menus are all explained in the perldoc documentation. Because it's so easy to use, Ptkdb is an absolute must for beginner and intermediate Perl programmers. It can even be useful for Perl gurus (as long as they don't tell anyone that they're using those new-fangled graphical interfaces).


Writing your own Perl shell

Sometimes using a debugger is overkill. If, for example, you want to test something simple, in isolation from the rest of a large program, a debugger would be too complex for the task. This is where a Perl shell can come in handy.

While other valid approaches to a Perl shell certainly exist, we're going to look at a general solution that works well for most daily work (and I use it all the time). Once you understand the tool, you should feel free to tailor it to your own needs and preferences.

The following code requires the Term::ReadLine module. You can download it from CPAN and install it almost the same way as you did Devel::ptkdb.

#!/usr/bin/perl -w
use Term::ReadLine;
use Data::Dumper;
my $historyfile = $ENV{HOME} . '/.phistory';
my $term = new Term::ReadLine 'Perl Shell';
sub save_list 
{ 
 my $f = shift; 
 my $l = shift; 
 open F, $f; 
 print F "$_\n" foreach @$l 
}
if (open H, $historyfile)
{
 @h = 
;
 chomp @h;
 close H;
 $h{$_} = 1 foreach @h;
 $term->addhistory($_) foreach keys %h;
}
while ( defined ($_ = $term->readline("My Perl Shell> ")) ) 
{
  my $res = eval($_);
  warn $@ if $@;
  unless ($@)
  {
   open H, ">>$historyfile";
   print H "$_\n";
   close H;
   print "\n", Data::Dumper->Dump([$res], ['Result']);
  }
  $term->addhistory($_) if /\S/;
}

This Perl shell does several things well, and some things decently.

First of all, it keeps a unique history of commands already entered in your home directory in a file called ".phistory". If you enter a command twice, only one copy will remain (see the function that opens $historyfile and reads history lines from it).

With each entry of a new command, the command list is saved to the .phistory file. So if you enter a command that crashes the shell, the history of your last session is not lost.

The Term::ReadLine module makes it easy to enter commands for execution. Because commands are limited to only one line at a time, it's possible to write good old buggy.pl as:

Da Perl Shell> use strict
$Result = undef;
Perl Shell> print "$_: " . 
 foreach (0..20)
0: ...
1: ...

The problem of course is that the input operator ends up eating the shell's own input. So don't use or STDIN in the Perl shell, because they'll make things more difficult. Try this instead:

    Perl Shell> open F, "buggy.pl"
$Result = 1;
Perl Shell> foreach (0..20) { last if eof(F); print "$_: " . 
; }
0: #!/usr/bin/perl -w
1: 
2: use strict;
3: 
4: foreach (0..20)
5: {
6:  my $line = 
;
7:  last unless defined $line;   # exit loop if $line is not defined
8:  print "$_ : $line";
9: }
$Result = undef;

As you can see, the shell works for cases where you can easily condense statements into one line. It's also a surprisingly common solution to isolating bugs and provides a great learning environment. Do a little exercise and see if you can write a Perl shell for debugging on your own, and see how much you learn!


Building an arsenal of tools

We've only covered the very basics here of the built-in Perl debugger, Devel::ptkdb, and related tools. There are many more ways to debug Perl. What's important is that you gain an understanding of the debugging process: how a bug is observed, solved, and then fixed. Of course the single most important thing is to make sure you have a comprehensive understanding of your program's requirements.

The Perl built-in debugger is very powerful, but it's not good for beginner or intermediate Perl programmers. (With the exception of Emacs, where it can be a useful tool even for beginners as long as they understand debugging under Emacs.)

The Devel::ptkdb module and debugger are (because of power and usability) by far the best choice for beginning and intermediate programmers. Perl shells, on the other hand, are personalized debugging solutions for isolated problems with small pieces of code.

Every software tester builds his own arsenal of debugging tools, whether it's the Emacs editor with GUD, or a Perl shell, or print statements throughout the code. Hopefully the tools we've looked at here will make your debugging experience a little easier.

Resources

  • Read Ted's other Perl articles in the "Cultured Perl" series on developerWorks.
  • Visit CPAN for all the Perl modules you ever wanted.
  • Check out Perl.com for Perl information and related resources.
  • For the Perl 'perldebug' perldoc page, type "perldoc perldebug" at your command prompt to retrieve it (as long as the installation of Perl is complete and it is in your path).
  • Download the Devel::ptkdb module by Andrew E. Page.
  • Find the comp.software.testing FAQ at FAQS.org.
  • For software testing and QA resources from the comp.software.testing FAQ, go to :
  • Programming Perl Third Edition (Larry Wall, Tom Christiansen, and Jon Orwant; O'Reilly & Associates, 2000) is the best guide to Perl today, up-to-date with 5.005 and 5.6.0 now. Chapter 20 covers the standard Perl debugger.
  • Perl Cookbook (Tom Christiansen and Nathan Torkington; O'Reilly & Associates, 1998) is the definitive how-to for all Perl issues ; somewhat out of date with 5.6.0 out, but still worth every cent.
  • The Mythical Man-Month, 20th Anniversary Edition (Fred P. Brooks; Addison-Wesley, 1995) is a wonderful all-purpose read, especially for project managers and coders. Debugging is well covered.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Linux on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Linux
ArticleID=11057
ArticleTitle=Cultured Perl: Debugging Perl with ease
publish-date=11012000