| 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__ |