How to move up call stack exactly at point of error?

后端 未结 3 1588
不知归路
不知归路 2020-12-12 03:22

I\'m aware of this idiom:

eval {
    ...
};

$DB::single = 1 if $@;

...but, as far as I can tell, if the debugger stops after the eva

相关标签:
3条回答
  • 2020-12-12 03:29

    Note   This was written for the original question, before it was changed. It retrieves all lexical variables for each frame in the call stack at the point where die is thrown, without the debugger.


    For debugging, Carp::Always is helpful.

    Also, for errors you seem to be after, you can override die to get Carp's backtrace

    eval { 
        local $SIG{__DIE__} = \&Carp::confess;
        # ... code ...
    };
    if ($@) { print $@ }
    

    This has limitations and complexities to be aware of, see eval and %SIG in perlvar, and die, but given the lack of detail on what triggers the error it should be worth trying.


    Since __DIE__ hook runs when die is triggered, in it we can examine the call stack 'live'.

    The code below uses caller to walk the stack and for basic info, and PadWalker to get lexical variables for each frame. I put some variables in subs so to more easily follow the output.

    use warnings;
    use strict;
    use PadWalker qw(peek_my);
    
    my $ondie = sub {
        my $sf = 0;
        while ( my @call = caller($sf) ) {        # go through stack frames
            say "At $sf frame, |@call[0..3]|";
            my $vars = peek_my($sf);              # lexicals for this frame
            for (keys %$vars) {
                if (ref($vars->{$_}) eq 'SCALAR') {
                    print "\t$_ => ${$vars->{$_}}\n";
                } elsif (not /^\$vars$/) {
                    print "\t$_ => $vars->{$_}\n";
                }
            }
            ++$sf;
        }
    };
    
    eval { 
        local $SIG{__DIE__} = $ondie;
        top_level(25); 
    }; 
    if ($@) { print "eval: $@" }
    
    sub top_level {
        my ($top_x,  $sub_name) = (12.1, (caller(0))[3]);
        next_level($_[0]);
    };    
    sub next_level { 
        my ($next_x, $sub_name) = (7, (caller(0))[3]);
        $_[0] / 0;
    };
    

    The output

    At 0 frame, |main debug_stack.pl 51 main::__ANON__|
            $sf => 0
            @call => ARRAY(0x15464c8)
    At 1 frame, |main debug_stack.pl 59 main::next_level|
            $next_x => 7
            $ondie => REF(0x1587938)
            $sub_name => main::next_level
    At 2 frame, |main debug_stack.pl 38 main::top_level|
            $top_x => 12.1
            $ondie => REF(0x1587938)
            $sub_name => main::top_level
    At 3 frame, |main debug_stack.pl 36 (eval)|
            $ondie => REF(0x1587938)
    eval: Illegal division by zero at debug_stack.pl line 51.
    

    The PadWalker's peek_my returns a hashref, where each value is a reference. I dereference only scalars, for demonstration, and also exclude $vars, where PadWalker's findings are stored, from prints. To leave out the handler itself start with my $sf = 1.

    0 讨论(0)
  • 2020-12-12 03:31

    if there was a way to stop exactly at the point of failure

    $SIG{__DIE__} is called where the exception is thrown, so you could add

    local $SIG{__DIE__} = sub { $DB::single = 1; die(@_); };
    

    $ cat a.pl
    sub g {
       die "!";
    }
    
    sub f {
       g();
    }
    
    local $SIG{__DIE__} = sub { $DB::single = 1; die(@_); };
    f();
    

    $ perl -d a.pl
    
    Loading DB routines from perl5db.pl version 1.49_04
    Editor support available.
    
    Enter h or 'h h' for help, or 'man perldebug' for more help.
    
    main::(a.pl:9): local $SIG{__DIE__} = sub { $DB::single = 1; die(@_); };
      DB<1> r
    main::CODE(0x1067280)(a.pl:9):  local $SIG{__DIE__} = sub { $DB::single = 1; die(@_); };
      DB<1> T
    @ = DB::DB called from file 'a.pl' line 9
    $ = main::__ANON__[a.pl:9]('! at a.pl line 2.^J') called from file 'a.pl' line 2
    . = main::g() called from file 'a.pl' line 6
    . = main::f() called from file 'a.pl' line 10
    
    0 讨论(0)
  • 2020-12-12 03:41

    You can view a stack backtrace when the program is paused at any breakpoint using the T command

    Alternatively, you may set the dieLevel option with o dieLevel=1 to cause an automatic backtrace on any exception

    0 讨论(0)
提交回复
热议问题