71
votes

Is there a way I can access (for printout) a list of sub + module to arbitrary depth of sub-calls preceding a current position in a Perl script?

I need to make changes to some Perl modules (.pm's). The workflow is initiated from a web-page thru a cgi-script, passing input through several modules/objects ending in the module where I need to use the data. Somewhere along the line the data got changed and I need to find out where.

8
While this does not answer your question it might help you solve your problem :-) Here is an interesting article describing one way how to figure out who changes your variables from Mark Dominus - Pat

8 Answers

67
votes

You can use Devel::StackTrace.

use Devel::StackTrace;
my $trace = Devel::StackTrace->new;
print $trace->as_string; # like carp

It behaves like Carp's trace, but you can get more control over the frames.

The one problem is that references are stringified and if a referenced value changes, you won't see it. However, you could whip up some stuff with PadWalker to print out the full data (it would be huge, though).

23
votes

This code works without any additional modules. Just include it where needed.

my $i = 1;
print STDERR "Stack Trace:\n";
while ( (my @call_details = (caller($i++))) ){
    print STDERR $call_details[1].":".$call_details[2]." in function ".$call_details[3]."\n";
}
20
votes

Carp::longmess will do what you want, and it's standard.

use Carp qw<longmess>;
use Data::Dumper;
sub A { &B; }
sub B { &C; }
sub C { &D; }
sub D { &E; }

sub E { 
    # Uncomment below if you want to see the place in E
    # local $Carp::CarpLevel = -1; 
    my $mess = longmess();
    print Dumper( $mess );
}

A();
__END__
$VAR1 = ' at - line 14
    main::D called at - line 12
    main::C called at - line 10
    main::B called at - line 8
    main::A() called at - line 23
';

I came up with this sub (Now with optional blessin' action!)

my $stack_frame_re = qr{
    ^                # Beginning of line
    \s*              # Any number of spaces
    ( [\w:]+ )       # Package + sub
    (?: [(] ( .*? ) [)] )? # Anything between two parens
    \s+              # At least one space
    called [ ] at    # "called" followed by a single space
    \s+ ( \S+ ) \s+  # Spaces surrounding at least one non-space character
    line [ ] (\d+)   # line designation
}x;

sub get_stack {
    my @lines = split /\s*\n\s*/, longmess;
    shift @lines;
    my @frames
        = map { 
              my ( $sub_name, $arg_str, $file, $line ) = /$stack_frame_re/;
              my $ref =  { sub_name => $sub_name
                         , args     => [ map { s/^'//; s/'$//; $_ } 
                                         split /\s*,\s*/, $arg_str 
                                       ]
                         , file     => $file
                         , line     => $line 
                         };
              bless $ref, $_[0] if @_;
              $ref
          } 
          @lines
       ;
    return wantarray ? @frames : \@frames;
}
18
votes

caller can do that, though you may want even more information than that.

16
votes

There's also Carp::confess and Carp::cluck.

3
votes

One that is more pretty: Devel::PrettyTrace

use Devel::PrettyTrace;
bt;
3
votes

In case you can't use (or would like to avoid) non-core modules, here's a simple subroutine I came up with:

#!/usr/bin/perl
use strict;
use warnings;

sub printstack {
    my ($package, $filename, $line, $subroutine, $hasargs, $wantarray, $evaltext, $is_require, $hints, $bitmask, $hinthash);
    my $i = 1;
    my @r;
    while (@r = caller($i)) {
        ($package, $filename, $line, $subroutine, $hasargs, $wantarray, $evaltext, $is_require, $hints, $bitmask, $hinthash) = @r;
        print "$filename:$line $subroutine\n";
        $i++;
    }
}

sub i {
    printstack();
}

sub h {
    i;
}

sub g {
    h;
}

g;

It produces output like as follows:

/root/_/1.pl:21 main::i
/root/_/1.pl:25 main::h
/root/_/1.pl:28 main::g

Or a oneliner:

for (my $i = 0; my @r = caller($i); $i++) { print "$r[1]:$r[2] $r[3]\n"; }

You can find documentation on caller here.

2
votes

Moving my comment to an answer:

  1. Install Devel::Confess the right way

    cpanm Devel::Confess
    
  2. Run with

    perl -d:Confess myscript.pl
    

On errors, this will show the whole call stack list.