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

时间:2017-02-12 01:18:07

标签: perl debugging

我知道这个成语:

/((?:[a-z]+\.)+[a-z]+)/gi

...但是,据我所知,如果调试器在eval { ... }; $DB::single = 1 if $@; 之后停止,那么检查堆栈中的帧已经太晚了,因为它们恰好发生错误。

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

3 个答案:

答案 0 :(得分:6)

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

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

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

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

这需要注意的局限性和复杂性,请参阅eval%SIG in perlvar以及die,但由于缺乏触发错误的详细信息,因此值得尝试。

由于__DIE__挂钩在die被触发时运行,因此我们可以检查调用堆栈'live'。

下面的代码使用caller来遍历堆栈和基本信息,并使用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;
};

输出

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开始。

答案 1 :(得分:4)

  

如果有办法在故障点完全停止

在抛出异常的地方调用

$SIG{__DIE__},因此您可以添加

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

答案 2 :(得分:2)

使用T命令在任何断点暂停程序时,可以查看堆栈回溯

或者,您可以将dieLevel选项设置为o dieLevel=1,以便对任何异常进行自动回溯