Debugging Perl

Roll you own debugging code

Peter Makholm

perldebguts(1) says...

This is not the perldebug(1) manpage, which tells you how to use the debugger. This manpage describes low-level details concerning the debugger's internals, which range from difficult to impossible to understand for anyone who isn't incredibly intimate with Perl's guts. Caveat lector.

The story...

A Perl demon stopped responding at random times

Two ways to call you debugger

There are two ways to call you own debugger:

A minimal debugger

The function DB::DB is called for each statement.

{ 
  package DB;
  sub DB { $stmt++; print STDERR "." if $stmt % 25 }
}

(but only if either $DB::single, $DB::trace, or $DB::signal is true)

Debugging function calls

DB::sub is called instead of each function call

{
  package DB;
  sub DB {}
  sub sub { print STDERR "$sub\n"; &$sub; }
}

(not depending on any variables)

Variables maintained by Perl

For each file ($filename) Perl maintains a few variables:

${"_<$filename"}
- The filename (useless eh?, see 'Tracing compilation')
@{"_<$filename"}
- The lines in the file
%{"_<$filenames"}
- Breakpoints in the file (used internally???)

Also %DB::subs is maintained with all suroutine names as keys and the filename, startline, and endline of the subroutine definition used as values.

A simple tracer

By using caller() we can use this to make a simple tracer:

{
  package DB;
  sub DB { 
    my ($package, $filename, $line) = caller;
    print $main::{"_<$filename"}[$line]
  }
}

Tracing subroutine calls

Tracing subroutine call is just as easy

{
  package DB;
  sub sub {
    my ($package, $filename, $line) = caller(-1);
    print "Called $sub at $filename:$line\n";
    &$sub;
  }
}

Tracing compilation

If DB::postponed() exists it will be called when either a subroutine or a file is compiled.

Intermezzo: GLOB's (simplified)

A GLOB is a special type containing all the other types of the same name. I.e. *foo{SCALAR} is the same as $foo and *foo{ARRAY} is the same as @foo.

How can we differ between being called with an ordinary scalar or a glob? This seems to work:

sub postponed {
  my $arg = shift;
  if (ref \$arg eq 'GLOB') { ... }
  ...
}

Tracing compilation (cont.)

Then tracing compilation is easy:

sub postponed {
    my $arg = shift;
    if (ref \$arg eq 'GLOB') {
        print "Loaded file ${ *$arg{SCALAR} }\n";
    } else {
        print "Compiled function $arg\n";
    }
}

But remember to fill %DB::postponed with fully qualified subroutine names.

Using $^P

$^P contains flags used by the debugger. Some are used at compile time and some at run time. These are the most usefull:

0x01  Debug subroutine enter/exit.
0x02  Line-by-line debugging.
0x04  Switch off optimizations.
0x08  Preserve more data for future interactive inspections.
0x10  Keep info about source lines on which a subroutine is defined.
0x20  Start with single-step on.
0x100 Provide informative "file" names for evals
0x200 Provide informative names to anonymous subroutines

Putting it all together

See Devel::RemoteTrace for everything put together

Tips for the standard debugger