如何准确地在错误点向上移动调用堆栈?

kjo*_*kjo 2 debugging perl

我知道这个成语:

eval {
    ...
};

$DB::single = 1 if $@;
Run Code Online (Sandbox Code Playgroud)

...但是,据我所知,如果调试器在eval检测到堆栈中的帧已经太晚之后就停止了,因为它们恰好是错误发生的瞬间.

有没有办法在错误发生时准确地停止调试器,并检查调用堆栈中的帧?

zdi*_*dim 6

注意   这是在原始问题发生变化之前编写的.它在die没有调试器的情况下,在抛出的位置检索调用堆栈中每个帧的所有词法变量.


对于调试,Carp :: Always很有帮助.

此外,对于您似乎遇到的错误,您可以覆盖die以获取Carp回溯

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

这有一些局限性和复杂性需要注意,请参阅perlvar中的eval%SIG,并且死亡,但由于缺乏触发错误的详细信息,它应该值得尝试.


由于__DIE__钩子在die触发时运行,因此我们可以检查调用堆栈"实时".

下面的代码使用调用者来遍历堆栈和基本信息,PadWalker使用调用来获取每个帧的词法变量.subs中的变量可以更容易地跟随输出.

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;
};
Run Code Online (Sandbox Code Playgroud)

输出

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.

PadWalker peek_my返回一个hashref,其中每个值都是一个引用.提领我只标量,示范,同时也排除$vars,其中PadWalker的调查结果存储,打印从.要省略处理程序本身my $sf = 1.


ike*_*ami 5

是否有一种方法可以完全在故障点停止

$SIG{__DIE__} 在引发异常的地方调用,因此您可以添加

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

sub f {
   g();
}

local $SIG{__DIE__} = sub { $DB::single = 1; die(@_); };
f();
Run Code Online (Sandbox Code Playgroud)

$ 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
Run Code Online (Sandbox Code Playgroud)