简体   繁体   中英

Create/raise a SIGINT (Ctrl-C) from Perl script - and cause debugger entry in step mode from die handler?

I'm having a problem with a rather complex program in Perl (under Linux), which I'm trying to debug. I can simulate the problem with the simple snippet here ( test.pl ):

use warnings;
use strict;
use feature qw/say/;

my @testa = ();
my $numelems = 10000;

# populate array/list of arrays
for (my $ix = 0; $ix < $numelems; $ix++) {
  my @miniarr = ($ix, 1);
  push(@testa, \@miniarr);
}

say "Array is now " . scalar(@testa) . " elements long";

my $BADnumelems = $numelems + 2;
my $sum = 0;

# loop through array/list of arrays
for (my $ix = 0; $ix < $BADnumelems; $ix++) {
  my @minientry = @{$testa[$ix]};
  $sum += $minientry[0];
}

say "Sum of elements is $sum";

Running this program exits with:

$ perl test.pl 
Array is now 10000 elements long
Can't use an undefined value as an ARRAY reference at test.pl line 22.

So, now I'd like to debug it - but the error causes the program to die, and exit the debugger:

$ perl -d test.pl 

Loading DB routines from perl5db.pl version 1.32
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(test.pl:6):  my @testa = ();
  DB<1> c
Array is now 10000 elements long
Can't use an undefined value as an ARRAY reference at test.pl line 22.
 at test.pl line 22
Debugged program terminated.  Use q to quit or R to restart,
  use o inhibit_exit to avoid stopping after program termination,
  h q, h R or h o to get additional info.  
  DB<1> p $sum

  DB<2> exit
$ 

Then, I found break on warning in debugger (PerlMonks) ; so I tried adding this:

...
use feature qw/say/;

$SIG{__DIE__} = sub { my($signal) = @_; say "DIEhandler: $signal"; $DB::single = 1; };
$SIG{__WARN__} = sub { my($signal) = @_; say "WARNhandler: $signal"; $DB::single = 1; };

my @testa = ();
...

... but this also kills the debugger:

$ perl -d test.pl 

Loading DB routines from perl5db.pl version 1.32
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(test.pl:6):  $SIG{__DIE__} = sub { my($signal) = @_; say "DIEhandler: $signal"; $DB::single = 1; };
  DB<1> c
Array is now 10000 elements long
DIEhandler: Can't use an undefined value as an ARRAY reference at test.pl line 25.

Can't use an undefined value as an ARRAY reference at test.pl line 25.
Debugged program terminated.  Use q to quit or R to restart,
  use o inhibit_exit to avoid stopping after program termination,
  h q, h R or h o to get additional info.  

Now, the thing is, I know that if I interrupt the program with Ctrl-C - that usually causes the debugger to enter into step mode; for instance, you can set my $numelems = 100000000; , and while it's looping, press Ctrl-C - and you can debug:

$ perl -d test.pl 

Loading DB routines from perl5db.pl version 1.32
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(test.pl:6):  $SIG{__DIE__} = sub { my($signal) = @_; say "DIEhandler: $signal"; $DB::single = 1; };
  DB<1> c
^Cmain::(test.pl:14):     my @miniarr = ($ix, 1);
  DB<1> p $ix
148607
  DB<2> q

Now, I can insert break point into source perl program :

...
for (my $ix = 0; $ix < $BADnumelems; $ix++) {
  $DB::single = 1;               ### <=== BREAK HERE
  my @minientry = @{$testa[$ix]};
...

but it enters the loop when $ix = 0 :

$ perl -d test.pl 

Loading DB routines from perl5db.pl version 1.32
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(test.pl:6):  $SIG{__DIE__} = sub { my($signal) = @_; say "DIEhandler: $signal"; $DB::single = 1; };
  DB<1> c
Array is now 10000 elements long
main::(test.pl:26):   my @minientry = @{$testa[$ix]};
  DB<1> p $ix
0
  DB<2> q

... and I don't want to step through 10000 elements to get to the problematic part :)

So, I thought the following - if there is a way to raise/generate/create SIGINT (Ctrl-C) from the Perl script itself , then I'd be able to raise it from the die handler, and hopefully cause a step in the debugger before the process dies. So, my questions are:

  • Is it possible to raise SIGINT from a Perl script, which will break itself - if so, how?
  • If the previous is possible, would it be able to cause entry into debugger step mode using Ctrl-C from a die handler, before the process dies?
  • If the previous is not possible - what possibility do I have to cause an entry into debugger step mode, right at the line where program die s?

Got it going (although not sure if I understand everything properly :) ), thanks to these:

Simply modify the code with an eval wrap and warn as such:

...
# loop through array/list of arrays
for (my $ix = 0; $ix < $BADnumelems; $ix++) {
  eval {
    my @minientry;
    @minientry = @{$testa[$ix]};
    $sum += $minientry[0];
  }; # just this causes step into debugger at $ix = 10001
  warn $@ if $@; # this causes step into debugger at $ix=10000 (OK)
}
...

... then debugger works like this:

$ perl -d test.pl 

Loading DB routines from perl5db.pl version 1.32
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(test.pl:6):  $SIG{__DIE__} = sub { my($signal) = @_; say "DIEhandler: $signal"; @_ = (); $DB::single = 1; };
  DB<1> c
Array is now 10000 elements long
DIEhandler: Can't use an undefined value as an ARRAY reference at test.pl line 27.

main::(test.pl:30):   warn $@ if $@; # this causes step into debugger at $ix=10000 (OK)
  DB<1> p $ix
10000
  DB<2> p @{$testa[$ix]}

  DB<3> p @{$testa[$ix-1]}
99991
  DB<4> p join("--", @{$testa[$ix]})

  DB<5> p join("--", @{$testa[$ix-1]})
9999--1
  DB<6> q

... that is, steps right into the problematic line, which is what I wanted.

Well, hope this helps someone,
Cheers!

EDIT: also possibly related: Why do I need to localize $@ before using eval?

$DB::single can be set to an expression, so that it will only break under your specified condition.

...
for (my $ix = 0; $ix < $BADnumelems; $ix++) {

  $DB::single ||= ref($testa[$ix]) ne 'ARRAY';

  my @minientry = @{$testa[$ix]};
  $sum += $minientry[0];
}
...

...
for (my $ix = 0; $ix < $BADnumelems; $ix++) {
  eval {
      my @minientry = @{$testa[$ix]};
      $sum += $minientry[0];
  };
  $DB::single ||= $@; # break if eval block fails
  # give debugger somewhere to break before proceeding to next iteration
  1;
}
...

(I prefer $DB::single ||= ... to $DB::single = ... because if you really are stepping through each iteration in that block (or any code after that block), Perl will want to set $DB::single to true for you, and you don't want your code to unset that value inadvertently and prevent you from stepping through other lines of your code.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM