File Coverage

utils/auto-debug-module.pl
Criterion Covered Total %
statement 9 19 47.3
branch 0 4 0.0
condition 0 12 0.0
subroutine 3 4 75.0
pod n/a
total 12 39 30.7


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2 1     1   755 use strict;
  1         2  
  1         41  
3 1     1   6 use File::Path 'mkpath';
  1         3  
  1         67  
4 1     1   960 use File::Copy 'copy';
  1         2975  
  1         1552  
5              
6             my $VERSION = '0.02'; # Changelog at end
7             die "Debugging cycle detected" # set to -1 to allow extra iteration
8             if ++$ENV{PERL_DEBUG_MCODE_CYCLE} > 1;
9              
10             my %opt;
11             $opt{$1} = shift while ($ARGV[0] || 0) =~ /^-([dq1])$/;
12             if ($opt{1}) {
13             open STDERR, '>&STDOUT' or warn "can't redirect STDERR to STDOUT";
14             } else {
15             open STDOUT, '>&STDERR' or warn "can't redirect STDOUT to STDERR";
16             }
17              
18             my $bd = 'dbg-bld';
19             @ARGV >= 1 or die <
20              
21             Usage: $0 [-d] [-q] [-1] check-module [failing-script1 failing-script2 ...]
22              
23             A tool to simplify remote debugging of build problems for XSUB modules.
24             By default, output goes to STDERR (to pass through the test suite wrappers).
25              
26             If CHECK-MODULE is non-empty (and not 0) checks whether it may be
27             loaded (with -Mblib). If FAILING-SCRIPTS are present, rebuilds the
28             current distribution with debugging (in subdirectory $bd), and
29             machine-code-debugs Perl crash when running each FAILING-SCRIPT.
30             Outputs as much info about the crash as it can massage from gdb or
31             dbx.
32              
33             If any problem is detected, outputs the MakeMaker arguments (extracted
34             from the generated Makefiles). Some minimal intelligence to avoid a
35             flood of useless information is applied: if CHECK-MODULE cannot be
36             loaded (but there is no crash during loading), no debugging for
37             FAILING-SCRIPTs is done.
38              
39             Options: With -d, prefers dbx to gdb (DEFAULT: prefer gdb).
40             With -q and no FAILING-SCRIPTs, won't print anything unless a
41             failure of loading is detected.
42             With -1, all output goes to STDOUT.
43              
44             Assumptions:
45             Should be run in the root of a distribution, or its immediate subdir.
46             Running Makefile.PL with OPTIMIZE=-g builds debugging version.
47             If FAILING-SCRIPTs are relative paths, they should be local w.r.t. the
48             root of the distribution.
49             gdb (or dbx) is fresh enough to understand the options we throw in.
50             Building in a subdirectory does not break a module (e.g., there is
51             no dependence on its position in its parent distribution, if any).
52              
53             Creates a subdirectory ./$bd. Add it to `clean' in Makefile.PL.
54              
55             Version: $VERSION
56             EOP
57             my ($chk_module) = (shift);
58              
59             sub report_Makefile ($) {
60 0     0     my $f = shift;
61 0           print STDERR "# reporting $f header:\n# ==========================\n";
62 0 0         my ($base_d, $in) = (-f "t/sinl.t" ? '.' : '..', '');
63 0 0         open M, "< $f" or die "Can't open $f";
64 0   0       $in = while defined $in and $in !~ /MakeMaker \s+ Parameters/xi;
65 0           $in = ;
66 0   0       $in = while defined $in and $in !~ /\S/;
67 0   0       print STDERR $in and $in = while defined $in and $in =~ /^#/;
      0        
68 0           close M;
69 0           print STDERR "# ==========================\n";
70             }
71              
72             # We assume that MANIFEST contains no filenames with spaces
73             chdir '..' or die "chdir ..: $!"
74             if not -f 'MANIFEST' and -f '../MANIFEST'; # we may be in ./t
75              
76             # Try to avoid debugging a code failing by some other reason than crashing.
77             # In principle, it is easier to do in the "trigger" code with proper BEGIN/END;
78             # just be extra careful, and recheck. (And we can be used standalone as well!)
79              
80             # There are 4 cases detected below, with !@ARGV thrown in, one covers 8 types.
81             my($skip_makefiles, $mod_load_out);
82             if ($chk_module) {
83             # Using blib may give a false positive (blib fails) unless distribution
84             # is already built; but the cost is small: just a useless rebuild+test
85             if (system $^X, q(-wle), q(use blib)) {
86             warn <
87              
88             Given that -Mblib fails, `perl Makefile.PL; make' was not run here yet...
89             I can't do any intelligent pre-flight testing now;
90              
91             EOW
92             die "Having no FAILING-SCRIPT makes no sense when -Mblib fails"
93             unless @ARGV;
94             warn <
95             ... so I just presume YOU know that machine-code debugging IS needed...
96              
97             EOW
98             $skip_makefiles = 1;
99             } else { #`
100             # The most common "perpendicular" problem is that a loader would not load DLL ==> no crash.
101             # Then there is no point in running machine code debugging; try to detect this:
102             my $mod_load = `$^X -wle "use blib; print(eval q(use $chk_module; 1) ? 123456789 : 987654321)" 2>&1`;
103             # Crashes ==> no "digits" output; DO debug. Do not debug if no crash, and no load
104             if ($mod_load =~ /987654321/) { # DLL does not load, no crash
105             $mod_load_out = `$^X -wle "use blib; use $chk_module" 2>&1`;
106             warn "Module $chk_module won't load: $mod_load_out";
107             @ARGV = (); # machine-code debugging won't help
108             } elsif ($mod_load =~ /123456789/) { # Loads OK
109             # a (suspected) failure has a chance to be helped by machine-code debug
110             ($opt{'q'} or warn(<
111              
112             Module loads without a problem. (No FAILING-SCRIPT, so I skip debugging step.)
113              
114             EOW
115             } # else: Crash during DLL load. Do debug
116             }
117             }
118             unless ($skip_makefiles) {
119             report_Makefile($_) for grep -f "$_.PL" && -f, map "$_/Makefile", '.', <*>;
120             }
121             exit 0 unless @ARGV;
122              
123             my $gdb = `gdb --version` unless $opt{d};
124             my $dbx = `dbx -V -c quit` unless $gdb;
125             $gdb = `gdb --version` unless $gdb or $dbx;
126             die "Can't find gdb or dbx" unless defined $gdb or defined $dbx;
127             die "Can't parse output of gdb --version"
128             unless $dbx or $gdb =~ /\b GDB \b | \b Copyright \b .* \b Free Software \b/x;
129             die "Can't parse output of `dbx -V -c quit'"
130             unless $gdb or $dbx =~ /\b dbx \s+ debugger \b/xi;
131              
132             die "Directory $bd exist; won't overwrite" if -d $bd;
133             mkdir $bd or die "mkdir $bd: $!";
134             chdir $bd or die "chdir $bd: $!";
135              
136             open MF, '../MANIFEST' or die "Can't read MANIFEST: $!";
137             while () {
138             next unless /^\S/;
139             s/\s.*//;
140             my ($f, $d) = m[^((.*/)?.*)];
141             -d $d or mkpath $d if defined $d; # croak()s itself
142             copy "../$f", $f or die "copy `../$f' to `$f' (inside $bd): $!";
143             }
144             close MF or die "Can't close MANIFEST: $!";
145              
146             system $^X, qw(Makefile.PL OPTIMIZE=-g) and die "system(Makefile.PL OPTIMIZE=-g): rc=$?";
147             system 'make' and die "system(make): rc=$?";
148              
149             my $p = ($^X =~ m([\\/]) ? $^X : `which perl`) || $^X;
150             chomp $p unless $p eq $^X;
151             my(@cmd, $ver);
152              
153             for my $script (@ARGV) {
154             if ($gdb) {
155             $ver = $gdb;
156             my $gdb_in = 'gdb-in';
157             open TT, ">$gdb_in" or die "Can't open $gdb_in for write: $!";
158             # bt full: include local vars (not in 5.0; is in 6.5; is in 6.1, but crashes on 6.3:
159             # http://www.cpantesters.org/cpan/report/2fffc390-afd2-11df-834b-ae20f5ac70d3)
160             # disas /m : with source lines (FULL function?!) (not in 6.5; is in 7.0.1)
161             # XXX all-registers may take 6K on amd64; maybe put at end?
162             my $proc = (-d "/proc/$$" ? <
163             info proc mapping
164             echo \\n=====================================\\n\\n
165             EOP
166             print TT <
167             run -Mblib $script
168             echo \\n=====================================\\n\\n
169             bt
170             echo \\n=====================================\\n\\n
171             info all-registers
172             echo \\n=====================================\\n\\n
173             disassemble
174             echo \\n=====================================\\n\\n
175             bt 5 full
176             echo \\n=====================================\\n\\n
177             $proc disassemble /m
178             quit
179             EOP
180             close TT or die "Can't close $gdb_in for write: $!";
181              
182             #open STDIN, $gdb_in or die "cannot open STDIN from $gdb_in: $!";
183             @cmd = (qw(gdb -batch), "--command=$gdb_in", $p);
184             } else { # Assume $script has no spaces or metachars
185             # Linux: /proc/$proc/maps has the text map
186             # Solaris: /proc/$proc/map & /proc/$proc/rmap: binary used/reserved
187             # /usr/proc/bin/pmap $proc (>= 2.5) needs -F (force) inside dbx
188             $ver = $dbx;
189             # where -v # Verbose traceback (include function args and line info)
190             # dump # Print all variables local to the current procedure
191             # regs [-f] [-F] # Print value of registers (-f/-F: SPARC only)
192             # list - # List previous lines (next with +)
193             # -i or -instr # Intermix source lines and assembly code
194             @cmd = (qw(dbx -c), # We do not do non-integer registers...
195             qq(run -Mblib $script; echo; echo =================================; echo; where -v; echo; echo =================================; echo; dump; echo; echo =================================; echo; regs; echo; echo =================================; echo; list -i +1; echo; echo =================================; echo; list -i -10; echo; echo =================================; echo; echo ============== up 1:; up; dump; echo; echo ============== up 2:; up; dump; echo; echo ============== up 3:; up; dump; echo; echo ============== up 4:; up; dump; echo ==============; /usr/proc/bin/pmap -F \$proc; quit),
196             $p);
197             }
198             system @cmd and die "Running @cmd: rc=$?";
199             print $ver;
200             }
201             1;
202              
203             __END__