File Coverage

auto-dbg/auto-debug-module.pl
Criterion Covered Total %
statement 12 58 20.6
branch 0 48 0.0
condition 0 65 0.0
subroutine 4 8 50.0
pod n/a
total 16 179 8.9


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2 1     1   205825 use strict;
  1         3  
  1         50  
3 1     1   7 use File::Path 'mkpath';
  1         2  
  1         92  
4 1     1   729 use File::Copy 'copy';
  1         7111  
  1         81  
5 1     1   8 use Config;
  1         1  
  1         4493  
6              
7             my $VERSION = '1.10a'; # Changelog at end
8             die "Debugging cycle detected" # set to -1 to allow extra iteration
9             if ++$ENV{PERL_DEBUG_MCODE_CYCLE} > 1;
10              
11             my(@ARGVi, %opt) = @ARGV;
12             $opt{$1} = shift while ($ARGV[0] || 0) =~ /^-([dq1OBU])$/;
13             if ($opt{1}) {
14             open STDERR, '>&STDOUT' or warn "can't redirect STDERR to STDOUT";
15             } else {
16             open STDOUT, '>&STDERR' or warn "can't redirect STDOUT to STDERR";
17             }
18              
19             my $bd = (my $bd0 = 'dbg-bld') . ($opt{O} || '');
20             @ARGV >= 1 or die <
21              
22             Usage:
23             $0 [-B] [-U] [-d] [-q] [-1] [-O] check-module [failing-script1 failing-script2 ...]
24              
25             A tool to simplify remote debugging of build problems for XSUB modules.
26             By default, output goes to STDERR (to pass through the test suite wrappers).
27              
28             If CHECK-MODULE is non-empty (and not 0) checks whether it may be
29             loaded (with -Mblib). If any problem is detected, outputs the MakeMaker
30             arguments (extracted from the generated Makefiles).
31              
32             If CHECK-MODULE is empty (or 0), or if FAILING-SCRIPTS are present,
33             rebuilds the current distribution with debugging (in subdirectory $bd),
34             and machine-code-debugs Perl crash when running each FAILING-SCRIPT.
35             Outputs as much info about the crash as it can massage from gdb, or
36             dbx, or lldb.
37              
38             Some minimal intelligence to avoid a flood of useless information is applied:
39             if CHECK-MODULE cannot be loaded (but there is no crash during loading), no
40             debugging for FAILING-SCRIPTs is done.
41              
42             Options: With -d, prefers dbx to gdb (DEFAULT: prefer gdb).
43             With -q and no FAILING-SCRIPTs, won't print anything unless a
44             failure of loading is detected.
45             With -1, all our output goes to STDOUT.
46             With -O, makes a non-debugging build.
47             With -B, builds in a subdirectory even if no debugger was found.
48             With -U will reuse the build directory if present.
49              
50             Assumptions:
51             Should be run in the root of a distribution, or its immediate subdir.
52             Running Makefile.PL with OPTIMIZE=-g builds debugging version.
53             (Actually, v1.00 starts to massage CFLAGS, LDFLAGS and DLLDFLAGS too.)
54             If FAILING-SCRIPTs are relative paths, they should be local w.r.t. the
55             root of the distribution.
56             gdb (or dbx, lldb) is fresh enough to understand the options we throw in.
57             Building in a subdirectory does not break a module (e.g., there is
58             no dependence on its position in its parent distribution, if any).
59              
60             Creates a subdirectory ./$bd0 or ./${bd0}O. Add them to `clean' in Makefile.PL
61             (add also the temporary files triggering running this script, if applicable), like
62             ./had-tst-run-.
63              
64             Version: $VERSION
65             EOP
66             $bd .= ($opt{O} || '');
67              
68             my ($chk_module) = (shift);
69              
70             sub report_Makefile ($) {
71 0     0     my($f, $in) = (shift, '');
72 0           print STDERR "# reporting $f header:\n# ==========================\n";
73 0 0         open M, "< $f" or die "Can't open $f";
74 0   0       ($in =~ /ARGV/ and print STDERR $in), $in = while defined $in and $in !~ /MakeMaker \s+ Parameters/xi;
      0        
75 0           $in = ;
76 0   0       $in = while defined $in and $in !~ /\S/;
77 0   0       print STDERR $in and $in = while defined $in and $in =~ /^#/;
      0        
78 0           close M;
79 0           print STDERR "# ==========================\n";
80             }
81              
82             # We assume that MANIFEST contains no filenames with spaces
83             chdir '..' or die "chdir ..: $!"
84             if not -f 'MANIFEST' and -f '../MANIFEST'; # we may be in ./t
85              
86             # Try to avoid debugging a code failing by some other reason than crashing.
87             # In principle, it is easier to do in the "trigger" code with proper BEGIN/END;
88             # just be extra careful, and recheck. (And we can be used standalone as well!)
89              
90             warn <
91              
92             >>> Checking whether I need to auto-debug (v=$VERSION): $0 @ARGVi
93              
94             EOW
95             # There are 4 cases detected below, with !@ARGV thrown in, one covers 8 types.
96             my($skip_makefiles, $mod_load_out);
97             if ($chk_module) {
98             # Using blib may give a false positive (blib fails) unless distribution
99             # is already built; but the cost is small: just a useless rebuild+test
100             if (system $^X, q(-wle), q(use blib)) {
101             warn <
102              
103             Given that -Mblib fails, `perl Makefile.PL; make' was not run here yet...
104             I can't do any intelligent pre-flight testing now;
105              
106             EOW
107             die "Having no FAILING-SCRIPT makes no sense when -Mblib fails"
108             unless @ARGV;
109             warn <
110             ... so I just presume YOU know that machine-code debugging IS needed...
111              
112             EOW
113             $skip_makefiles = 1;
114             } else { #`
115             # The most common "perpendicular" problem is that a loader would not load DLL ==> no crash.
116             # Then there is no point in running machine code debugging; try to detect this:
117             my $mod_load = `$^X -wle "use blib; print(eval q(use $chk_module; 1) ? 123456789 : 987654321)" 2>&1`;
118             # Crashes ==> no "digits" output; DO debug. Do not debug if no crash, and no load
119             if ($mod_load =~ /987654321/) { # DLL does not load, no crash
120             $mod_load_out = `$^X -wle "use blib; use $chk_module" 2>&1`;
121             warn "Module $chk_module won't load: $mod_load_out";
122             @ARGV = (); # machine-code debugging won't help
123             } elsif ($mod_load =~ /123456789/) { # Loads OK
124             # a (suspected) failure has a chance to be helped by machine-code debug
125             ($opt{'q'} or warn(<
126              
127             Module loads without a problem. (No FAILING-SCRIPT, so I skip debugging step.)
128              
129             EOW
130             } # else: Crash during DLL load. Do debug
131             }
132             }
133             unless ($skip_makefiles) {
134             report_Makefile($_) for grep -f "$_.PL" && -f, map "$_/Makefile", '.', <*>;
135             }
136             exit 0 unless @ARGV or not $chk_module;
137              
138             my $dbxname = 'dbx';
139             my $gdb = `gdb --version` unless $opt{d};
140             my $dbx = `dbx -V -c quit` unless $gdb;
141             my $lldb = `lldb --version` unless $gdb or $dbx; # untested
142             $dbx = `dbxtool -V` and $dbxname = 'dbxtool' unless $gdb or $dbx or $lldb;
143              
144             sub find_candidates () {
145 0     0     my($sep, @cand) = quotemeta $Config{path_sep};
146 0   0       for my $dir (split m($sep), ($ENV{PATH} || '')) {
147 0           for my $f (<$dir/*>) {
148 0 0 0       push @cand, $f if $f =~ m{dbx|gdb|lldb}i and -x $f;
149             }
150             }
151 0 0         warn 'Possible candidates for debuggers: {{{'. join('}}} {{{', @cand), '}}}' if @cand;
152             }
153              
154             unless ($gdb or $dbx or $lldb) {
155             find_candidates() unless $gdb = `gdb --version`;
156             }
157              
158             sub report_no_debugger () {
159 0 0 0 0     die "Can't find gdb or dbx or lldb" unless defined $gdb or defined $dbx or defined $lldb;
      0        
160 0 0 0       die "Can't parse output of gdb --version: {{{$gdb}}}"
      0        
161             unless $dbx or $lldb or $gdb =~ /\b GDB \b | \b Copyright \b .* \b Free \s+ Software \b/x;
162 0 0 0       die "Can't parse output of `dbx -V -c quit': {{{$dbx}}}"
      0        
      0        
163             unless $gdb or $lldb or $dbxname eq 'dbxtool' or $dbx =~ /\b dbx \s+ debugger \b/xi;
164 0 0 0       warn "Can't parse output of `dbxtool -V': {{{$dbx}}}"
      0        
      0        
165             unless $gdb or $lldb or $dbxname eq 'dbx' or $dbx =~ /\b dbx \s+ debugger \b/xi;
166 0 0 0       die "Can't parse output of lldb --version: {{{$lldb}}}"
      0        
167             unless $dbx or $gdb or $lldb =~ /\b lldb-\S*\d/x;
168             }
169              
170             $@ = '';
171             my $postpone = ( eval {report_no_debugger(); 1 } ? '' : "$@" );
172             if ($opt{B}) {
173             warn "No debugger found. Nevertheless, I build a new version per -B switch." if $postpone;
174             } else {
175             die $postpone if $postpone;
176             }
177              
178             my $build_was_OK = -f "$bd/autodebug-make-ok";
179             if (-d $bd) {
180             if ($build_was_OK) {
181             die "Directory $bd exists; won't overwrite (give option -U to override)" unless $opt{U};
182             warn <
183             Directory $bd exists with a good build; reusing per option -U.
184              
185             (If you edited something in the "top" directory "in between",
186             you need to copy changes to the directory $bd manually!!!)
187              
188             EOW
189             } else {
190             warn "Directory $bd exists, but the build was not successful; retrying...";
191             }
192             }
193             mkdir $bd or die "mkdir $bd: $!" unless -d $bd;
194             chdir $bd or die "chdir $bd: $!";
195              
196             sub do_subdir_build () {
197 0 0   0     open MF, '../MANIFEST' or die "Can't read MANIFEST: $!";
198 0           while () {
199 0 0         next unless /^\S/;
200 0           s/\s.*//;
201 0           my ($f, $d) = m[^((.*/)?.*)];
202 0 0 0       -d $d or mkpath $d if defined $d; # croak()s itself
203 0 0         copy "../$f", $f or die "copy `../$f' to `$f' (inside $bd): $!";
204             }
205 0 0         close MF or die "Can't close MANIFEST: $!";
206              
207 0           my(@extraflags, $more, $subst) = 'OPTIMIZE=-g';
208             # Work around bugs in Config: 'ccflags' may contain (parts???) of 'optimize'.
209 0 0 0       if ($opt{O}) { # Do not change debugging
    0          
    0          
210 0           @extraflags = ();
211             } elsif ($Config{ccflags} =~ s/(?
212             # e.g., Strawberry Perl
213 0           $subst++;
214             } elsif ($Config{gccversion} or $Config{cc} =~ /\b\w?cc\b/i) { # assume cc-flavor
215             # http://www.cpantesters.org/cpan/report/ef2ee424-1c8e-11e6-b928-8293027c4940
216             # http://www.cpantesters.org/cpan/report/4837b230-1d9d-11e6-91cb-6b7bc172c7fc
217             # Extra check:
218 0 0         $more++ if $Config{optimize} =~ /(?
219             }
220 0 0 0       if ($more or $subst) {
221 0           my $FL;
222 0 0         $subst++ if ($FL = $Config{ccflags}) =~ s/(?
223 0 0         push @extraflags, qq(CCFLAGS=$FL) if $subst;
224 0           for my $f (qw(ldflags lddlflags)) {
225             push @extraflags, qq(\U$f\E=$FL)
226 0 0         if ($FL = $Config{$f}) =~ s/(?
227             }
228             }
229              
230 0 0         system $^X, 'Makefile.PL', @extraflags and die "system(Makefile.PL @extraflags): rc=$?";
231 0           my $make = $Config{make};
232 0 0         $make = 'make' unless defined $make;
233 0 0         system $make and die "system($make): rc=$?";
234 0           { open my $f, '>', 'autodebug-make-ok'; } # Leave a footprint of a successful build
  0            
235 0           warn "Renaming Makefile.PL to orig-Makefile.PL\n\t(to avoid recursive calls from Makefile.PL in the parent directory)";
236 0           rename 'Makefile.PL', 'orig-Makefile.PL'; # ignore error
237             }
238              
239             do_subdir_build() unless -f 'autodebug-make-ok';
240              
241             die $postpone if $postpone; # Reached without a debugger only with -B
242              
243             my $p = ($^X =~ m([\\/]) ? $^X : `which perl`) || $^X;
244             chomp $p unless $p eq $^X;
245             my(@cmd, $ver, $ver_done, $cand_done, $dscript, $dbbatchname);
246             my $leak_ok = 1 eq ($ENV{AUTO_DBG_LOADMAPS} || 0);
247              
248             for my $script (@ARGV) {
249             $script = "../$script" if not -f $script and -f "../$script";
250             if ($gdb) {
251             $ver = $gdb;
252             my $gdb_in = $dbbatchname = 'gdb-in';
253             # bt full: include local vars (not in 5.0; is in 6.5; is in 6.1, but crashes on 6.3:
254             # http://www.cpantesters.org/cpan/report/2fffc390-afd2-11df-834b-ae20f5ac70d3)
255             # disas /m : with source lines (FULL function?!) (not in 6.5; is in 7.0.1)
256             # XXX all-registers may take 6K on amd64; maybe put at end?
257             # sharedlibrary: present on 7.3.1 (2011)
258             my $graceful_degradation;
259             my $tst_python = q[gdb -nx -batch -ex "python print(' py' + 'thon-ok ')"];
260             my $out = `$tst_python 2>&1`;
261             if ($out =~ /\bpython-ok\s*/) {
262             $graceful_degradation = <<'EOP';
263             python
264             import os
265              
266             def safe(*cmds):
267             for cmd in cmds:
268             try:
269             gdb.execute(cmd)
270             return
271             except gdb.error as e:
272             print(f"[SKIPPED] {cmd}: {e}")
273             print("[FAILED] No fallback command succeeded.")
274              
275             safe("set confirm off")
276              
277             safe("bt full 5", "bt 5 full", "bt full", "bt")
278             print("\n=====================================\n\n")
279              
280             safe("disassemble /m")
281             print("\n=====================================\n\n")
282              
283             safe("info proc status")
284             print("\n=====================================\n\n")
285              
286             if os.getenv("AUTO_DBG_LOADMAPS") != "1":
287             print("[INFO] Memory maps disabled to reduce attack surface; enable by AUTO_DBG_LOADMAPS=1.")
288             print("\n=====================================\n\n")
289             else:
290             safe("info sharedlibrary", "info shared")
291             print("\n=====================================\n\n")
292              
293             safe("info proc mapping")
294             print("\n=====================================\n\n")
295              
296             safe("info files")
297             print("\n=====================================\n\n")
298              
299              
300             safe("info w32 thread-information-block")
301             print("\n=====================================\n\n")
302              
303             safe("info w32 exception")
304             print("\n=====================================\n\n")
305              
306             safe("info w32 ldt")
307             print("\n=====================================\n\n")
308              
309             gdb.execute("quit")
310             end
311             EOP
312             } else {
313             warn <
314             Seems gdb is not supporting embedded-Python. Tested by <<<$tst_python 2>&1>>> ==>
315             $out
316              
317             I'll try using GDB commands directly.
318             Since GDB interpreter isn't backward-compatible, some commands may
319             fail - and then this would ruin the rest of my script!
320              
321             EOW
322             my $proc = (-d "/proc/$$" ? <
323             info proc mapping
324             echo \\n=====================================\\n\\n
325             EOP
326             my $extra = '';
327             $extra .= <
328             info w32 thread-information-block
329             echo \\n=====================================\\n\\n
330             EOE
331             $graceful_degradation = <
332             disassemble
333             echo \\n=====================================\\n\\n
334             bt 5 full
335             echo \\n=====================================\\n\\n
336             disassemble /m
337             echo \\n=====================================\\n\\n
338             ${extra}info sharedlibrary
339             echo \\n=====================================\\n\\n
340             ${proc}quit
341             EOP
342             }
343             $dscript = <
344             run -Mblib $script
345             echo \\n=====================================\\n\\n
346             bt
347             echo \\n=====================================\\n\\n
348             info all-registers
349             echo \\n=====================================\\n\\n
350             $graceful_degradation
351             EOP
352              
353             # One could also consider
354             # info frame; info threads; thread apply all bt full 5; info target
355              
356             #open STDIN, $gdb_in or die "cannot open STDIN from $gdb_in: $!";
357             @cmd = (qw(gdb -batch), "--command=$gdb_in", $p);
358             } elsif ($lldb) {
359             $ver = $lldb;
360             my $Python = `lldb -b -o 'script print(" pyth" + "on-ok ")' -o 'quit' 2>&1`;
361             warn <
362              
363             !!!! I seem to have found LLDB, but extra work may be needed. !!!
364             !!!! If you see something like this: !!!
365              
366             (lldb) run -Mblib t/000_load-problem.t
367             error: process exited with status -1 (developer mode is not enabled on this machine and this is a non-interactive debug session.)
368              
369             !!!! Inspect the following recipe !!!
370             !!!! from https://developer.apple.com/forums/thread/678032 !!!
371              
372             sudo DevToolsSecurity -enable
373             Developer mode is now enabled.
374              
375             !!!! This was Step 1; it should lead to the following error: !!!
376              
377             error: process exited with status -1 (this is a non-interactive debug session, cannot get permission to debug processes.)
378              
379             !!!! You also need Step 2 (security implications???): !!!
380              
381             sudo dseditgroup -o edit -a UUU -t user _developer
382             ### replace UUU with your user name.
383              
384             !!!! I'm crossing my virtual fingers and proceed. !!!
385              
386             EOW
387             my $lldb_in = $dbbatchname = 'lldb-in';
388             # bt full: include local vars (not in 5.0; is in 6.5; is in 6.1, but crashes on 6.3:
389             # http://www.cpantesters.org/cpan/report/2fffc390-afd2-11df-834b-ae20f5ac70d3)
390             # disas /m : with source lines (FULL function?!) (not in 6.5; is in 7.0.1)
391             # XXX all-registers may take 6K on amd64; maybe put at end?
392             # sharedlibrary: present on 7.3.1 (2011)
393             my $proc = $leak_ok && <<'EOP';
394             image list
395             script print "\\n=====================================\\n"
396              
397             image dump sections
398             script print "\n=====================================\n"
399              
400             ## script print "??? info proc mapping"
401             script
402             import lldb
403             process = lldb.debugger.GetSelectedTarget().process
404             count = process.GetNumMemoryRegions()
405             for i in range(count):
406             region = lldb.SBMemoryRegionInfo()
407             process.GetMemoryRegionAtIndex(i, region)
408             print(region)
409             end
410              
411             script print "\n=====================================\n"
412             EOP
413             my $extra = '';
414             $extra .= <
415             script print "??? info w32 thread-information-block"
416             script print "\\n=====================================\\n"
417             EOE
418             $dscript = <
419             run -Mblib $script
420             script print "\\n=====================================\\n"
421             bt
422             script print "\\n=====================================\\n"
423             frame variable
424             script print "\\n=====================================\\n"
425             register read
426             script print "\\n=====================================\\n"
427             disassemble --frame
428             script print "\\n=====================================\\n"
429             bt --count 5 --verbose # bt 5 full
430             script print "\\n=====================================\\n"
431             disassemble --frame --mixed
432             script print "\\n=====================================\\n"
433             register read --all
434             script print "\\n=====================================\\n"
435             ${extra}${proc}quit
436             EOP
437              
438             #open STDIN, $gdb_in or die "cannot open STDIN from $gdb_in: $!";
439             @cmd = (qw(lldb -batch -s), $lldb_in, $p);
440             } else { # Assume $script has no spaces or metachars
441             # Linux: /proc/$proc/maps has the text map
442             # Solaris: /proc/$proc/map & /proc/$proc/rmap: binary used/reserved
443             # /usr/proc/bin/pmap $proc (>= 2.5) needs -F (force) inside dbx
444             $ver = $dbx;
445             # where -v # Verbose traceback (include function args and line info)
446             # dump # Print all variables local to the current procedure
447             # regs [-f] [-F] # Print value of registers (-f/-F: SPARC only)
448             # list - # List previous lines (next with +)
449             # -i or -instr # Intermix source lines and assembly code
450             my $pmap = $leak_ok && '/usr/proc/bin/pmap -F \$proc;';
451             @cmd = ($dbxname, qw(-c), # We do not do non-integer registers...
452             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 ==============; $pmap quit),
453             $p);
454             }
455             if (defined $dbbatchname) {
456             open TT, ">$dbbatchname" or die "Can't open $dbbatchname for write: $!";
457             print TT $dscript;
458             close TT or die "Can't close $dbbatchname for write: $!";
459             warn "\n========= $dbbatchname script begin\n\n$dscript\n\n========= script end\n\n";
460             }
461             warn "\nDebugger's version: $ver\n" unless $ver_done++;
462             warn 'Running {{{', join('}}} {{{', @cmd), "}}}\n\n";
463             if (system @cmd) {
464             warn "Running @cmd: rc=$?", ($dscript ? "\n========= script begin\n$dscript\n========= script end\n\t" : '');
465             find_candidates() if $leak_ok;
466             die "I stop here,"
467             }
468             }
469             1;
470              
471             __END__