1

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 dies?
Community
  • 1
  • 1
sdaau
  • 36,975
  • 46
  • 198
  • 278

2 Answers2

0

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?

Community
  • 1
  • 1
sdaau
  • 36,975
  • 46
  • 198
  • 278
0

$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.)

mob
  • 117,087
  • 18
  • 149
  • 283