File Coverage

blib/lib/Enbugger.pm
Criterion Covered Total %
statement 18 209 8.6
branch 32 58 55.1
condition 6 12 50.0
subroutine 3 39 7.6
pod 10 15 66.6
total 69 333 20.7


line stmt bran cond sub pod time code
1             package Enbugger;
2              
3             # COPYRIGHT AND LICENCE
4             #
5             # Copyright (C) 2007,2008,2009 WhitePages.com, Inc. with primary
6             # development by Joshua ben Jore.
7             #
8             # This program is distributed WITHOUT ANY WARRANTY, including but not
9             # limited to the implied warranties of merchantability or fitness for
10             # a particular purpose.
11             #
12             # The program is free software. You may distribute it and/or modify
13             # it under the terms of the GNU General Public License as published by
14             # the Free Software Foundation (either version 2 or any later version)
15             # and the Perl Artistic License as published by O’Reilly Media, Inc.
16             # Please open the files named gpl-2.0.txt and Artistic for a copy of
17             # these licenses.
18              
19             BEGIN {
20 3     3   3913 $VERSION = '2.016';
21             }
22              
23 3     3   22 use XSLoader ();
  3         5  
  3         535  
24              
25             BEGIN {
26 3     3   1957 XSLoader::load( 'Enbugger', $VERSION );
27              
28              
29             # Provide minimal debugger hooks.
30             #
31             # When perl has debugging enabled, it always calls these functions
32             # at hook points. It dies if they're missing. These stub functions
33             # don't do anything except provide something that will keep perl
34             # from dying from lack of hooks.
35             {
36              
37             # Generate needed code for stubs.
38 3         17 my $src = "package DB;\n";
  3         6  
39 3         18 my $need_stubs;
40 3         8 for my $sub (qw( DB sub )) {
41 6         22 my $globref = $DB::{$sub};
42              
43             # Don't try replacing an existing function.
44 6 50 33     38 if ( $globref and defined &$globref ) {
45             }
46             else {
47             # Generate a stub method.
48 6         14 $src .= "sub $sub {};\n";
49 6         15 $need_stubs = 1;
50             }
51             }
52              
53             # Create stubs.
54 3 50       15 if ( $need_stubs ) {
55 3         8 $src .= "return 1;\n";
56 3     0 0 244 my $ok = eval $src;
  0     0 1    
  0            
57 3 50       14 die $@ unless $ok;
58             }
59             }
60              
61              
62             # Compile and load everything following w/ debugger hooks.
63             #
64             # That is, everything I'm asking to compile now could possibly be
65             # debugged if we do the loading. Most of everything else in the
66             # Enbugger namespace is explicitly removed from the debugger by
67             # making sure it's COP nodes are compiled with "nextstate" instead
68             # of "dbstate" hooks.
69 3         6963 Enbugger->_compile_with_dbstate();
70             }
71              
72              
73             # I don't know the real minimum version. I've gotten failure
74             # reports from 5.5 that show it's missing the COP opcodes I'm
75             # altering.
76 0     0     use 5.006_000;
  0            
  0            
77              
78 0     0     use strict;
  0            
  0            
79              
80 0     0     use B::Utils ();
  0            
  0            
81 0     0     use Carp ();
  0            
  0            
82 0     0     use Scalar::Util ();
  0            
  0            
83              
84             # Public class settings.
85 0     0     use vars qw( $DefaultDebugger %DBsub );
  0            
  0            
86              
87 0     0     use constant (); # just to load it.
  0            
  0            
88              
89             BEGIN {
90             # Compile all of Enbugger:: w/o debugger hooks.
91 0     0     Enbugger->_compile_with_nextstate();
92             }
93              
94             our( $DEBUGGER, $DEBUGGER_CLASS, %REGISTERED_DEBUGGERS );
95              
96              
97              
98              
99             ######################################################################
100             # Public API
101              
102             BEGIN {
103 0     0     my $src = "no warnings 'redefine';\n";
104 0           for my $sub (qw( stop write )) {
105 0           $src .= <<"SRC";
106 0           #line @{[__LINE__+1]} "@{[__FILE__]}"
  0            
107             sub $sub {
108 0     0 1   my ( \$class ) = \@_;
  0     0 1    
109              
110             # Fetch and install the real implementation.
111 0           my \$debuggerSubClass = \$class->DEBUGGER_CLASS;
  0            
112              
113 0           *Enbugger::$sub = \$debuggerSubClass->can('_${sub}');
  0            
114              
115             # Redispatch to the implementation.
116 0           goto &Enbugger::$sub;
  0            
117             };
118             SRC
119             }
120              
121 0           $src .= "return 1;\n";
122 0     0     my $ok = eval $src;
  0            
  0            
  0            
123 0 50         die $@ unless $ok;
124             }
125              
126              
127              
128              
129              
130 0     0     BEGIN { $DefaultDebugger = 'perl5db' }
131              
132             sub DEBUGGER_CLASS () {
133 0 0   0 1   unless ( defined $DEBUGGER_CLASS ) {
134 0           Enbugger->load_debugger;
135             }
136              
137             # Install a replacement method that doesn't know how to load
138             # debuggers.
139             #
140             # There's no need to always have a 100% capable function around
141             # once there's no possibility for change.
142 0           my $ok = eval <<"DEBUGGER_CLASS";
143 0     0     #line @{[__LINE__]} "@{[__FILE__]}"
  0            
  0            
  0            
  0            
144             no warnings 'redefine';
145             sub DEBUGGER_CLASS () {
146             "\Q$DEBUGGER_CLASS\E"
147             }
148             return 1;
149             DEBUGGER_CLASS
150              
151 0 0         die $@ unless $ok;
152              
153 0           goto &Enbugger::DEBUGGER_CLASS;
154             }
155              
156              
157              
158              
159              
160              
161              
162              
163              
164             sub _stop;
165             sub _write;
166             sub _load_debugger;
167              
168              
169              
170              
171              
172              
173             BEGIN {
174             # There is an automatically registered "null" debugger which is
175             # really just a known empty thing that exists only so I can match
176             # against it and thereby know it can be replaced.
177 0     0     $REGISTERED_DEBUGGERS{''} = {
178             null => 1,
179             symbols => [qw[ sub DB ]],
180             };
181             }
182              
183             sub load_debugger {
184 0     0 1   my ( $class, $requested_debugger ) = @_;
185              
186             # Choose a debugger to load if none was specified.
187 0 50         if ( not defined $requested_debugger ) {
188              
189             # Don't bother if we've already loaded a debugger.
190 0 50         return if $DEBUGGER;
191              
192             # Choose the default.
193 0           $requested_debugger = $DefaultDebugger;
194             }
195              
196             # Don't load a debugger if there is one loaded already.
197             #
198             # Enbugger already populates %DB:: with &DB and &sub so I'll check
199             # for something that I didn't create.
200             my %debugger_symbols =
201 0           map {; $_ => 0b01 }
  0            
202             keys %DB::;
203              
204              
205             # Compare all registered debuggers to our process.
206 0           my %debugger_matches;
207 0           for my $debugger ( keys %REGISTERED_DEBUGGERS ) {
208            
209             # Find the intersection vs the difference.
210 0           my $intersection = 0;
211 0           my %match = %debugger_symbols;
212 0           for my $symbol ( @{$REGISTERED_DEBUGGERS{$debugger}{symbols}} ) {
  0            
213 0 50         if ( ( $match{$symbol} |= 0b10 ) == 0b11 ) {
214 0           ++ $intersection;
215             }
216             }
217            
218             # Score.
219 0           my $difference =
220             keys(%match) - $intersection;
221 0           my $score = $difference / $intersection;
222            
223 0           $debugger_matches{$debugger} = $score;
224             }
225              
226             # Select the best matching debugger.
227 0           my ( $best_debugger ) =
228 0           sort { $debugger_matches{$a} <=> $debugger_matches{$b} }
229             keys %debugger_matches;
230            
231            
232             # It is ok to replace the null debugger but an error to replace
233             # anything else. Also, there's nothing to do if we've already
234             # loaded the requested debugger.
235 0 50         if ( $REGISTERED_DEBUGGERS{$best_debugger}{null} ) {
    0          
236             }
237             elsif ( $best_debugger eq $requested_debugger ) {
238 0           return;
239             }
240             else {
241 0           Carp::confess("Can't replace the existing $best_debugger debugger with $requested_debugger");
242             }
243              
244              
245             # Debugger's name -> Debugger's class.
246 0           $DEBUGGER = $requested_debugger;
247 0           $DEBUGGER_CLASS = "${class}::$DEBUGGER";
248              
249             # Debugger's class -> Debugger's .pm file.
250 0           my $debugger_class_file = $DEBUGGER_CLASS;
251 0           $debugger_class_file =~ s#::#/#g;
252 0           $debugger_class_file .= '.pm';
253              
254             # Load the file.
255             #
256             # Be darn sure we're compiling COP nodes with pp_nextstate
257             # instead of pp_dbstate. It sucks to start debugging your
258             # debugger by accident. Incidentally... this is a great place
259             # to hack if you /do/ want to make debugging a debugger a
260             # possibility.
261             #
262             # Further, note that some debugger supports have already been loaded
263             # by __PACKAGE__->register_debugger(...) below. In general, this
264             # is for things I've needed to use myself.
265 0           Enbugger->_compile_with_nextstate();
266 0           require $debugger_class_file;
267 0           $DEBUGGER_CLASS->_load_debugger;
268 0           $DEBUGGER_CLASS->instrument_runtime;
269              
270              
271             # Subsequent compilation will use pp_dbstate like expected.
272 0           $DEBUGGER_CLASS->_instrumented_ppaddr();
273              
274 0           return;
275             }
276              
277              
278              
279 0     0     sub _uninstrumented_ppaddr { $_[0]->_compile_with_nextstate() }
280 0     0     sub _instrumented_ppaddr { $_[0]->_compile_with_dbstate() }
281              
282              
283              
284              
285              
286              
287             sub _load_debugger;
288              
289              
290              
291              
292              
293             sub register_debugger {
294 0     0 1   my ( $class, $debugger ) = @_;
295            
296             # name -> class
297 0           my $enbugger_subclass = "Enbugger::$debugger";
298              
299             # class -> module file
300 0           my $enbugger_subclass_file = $enbugger_subclass;
301 0           $enbugger_subclass_file =~ s<::>g;
302 0           $enbugger_subclass_file .= '.pm';
303              
304             # Load it. *Assume* PL_ppaddr[OP_NEXTSTATE] is something
305             # useful like Perl_pp_nextstate still.
306             #
307             # TODO: localize PL_ppaddr[OP_NEXTSTATE] during this compilation to
308             # be Perl_pp_nextstate.
309 0           require $enbugger_subclass_file;
310              
311              
312 0           my $src = <<"REGISTER_DEBUGGER";
313 0           #line @{[__LINE__]} "@{[__FILE__]}"
  0            
314 0     0 0   sub load_$debugger {
  0     0 0    
  0     0 0    
315 0           my ( \$class ) = \@_;
  0            
  0            
316 0           \$class->load_debugger( '$debugger' );
  0            
  0            
317             return;
318             };
319             REGISTER_DEBUGGER
320              
321 0           $src .= "return 1;\n";
322 0           my $ok = eval $src;
323 0 50         die $@ unless $ok;
324             }
325              
326              
327              
328              
329              
330             sub load_source {
331 0     0 1   my ( $class ) = @_;
332              
333             # Load the original program.
334 0           $class->load_file($0);
335              
336             # Load all modules.
337 0 50         for ( grep { defined and -e } values %INC ) {
  0            
338 0           $class->load_file($_);
339             }
340              
341 0           $class->initialize_dbline;
342              
343 0           return;
344             }
345              
346              
347             sub initialize_dbline {
348 0     0 0   my $file;
349 0           for ( my $cx = 1; my ( $package, $c_file ) = caller $cx; ++ $cx ) {
350 0 100         if ( $package !~ /^Enbugger/ ) {
351 0           $file = $c_file;
352 0           last;
353             }
354             }
355              
356 0 50         if ( not defined $file ) {
357 0           *DB::dbline = [];
358 0           *DB::dbline = {};
359 0           Enbugger::set_magic_dbfile( \%DB::dbline );
360             }
361             else {
362 0     0     no strict 'refs';
  0            
  0            
363 0           *DB::dbline = \*{"main::_<$file"};
  0            
364             }
365             }
366              
367              
368              
369              
370             sub load_file {
371 0     0 1   my ($class, $file) = @_;
372            
373             # The symbols by which we'll know ye.
374 0           my $base_symname = "_<$file";
375 0           my $symname = "main::$base_symname";
376            
377 0     0     no strict 'refs';
  0            
  0            
378              
379 0           my $glob = \*$symname;
380              
381 0 50 33       if ( ! *$symname{ARRAY} && -f $file ) {
382             # Read the source.
383             # Open the file.
384 0           my $fh;
385 0 50         if ( not open $fh, '<', $file ) {
386 0           Carp::croak( "Can't open $file for reading: $!" );
387             }
388            
389             # Load our source code. All source must be installed as at least PVIV or
390             # some asserts in op.c may fail. Later, I'll assign better pointers to each
391             # line in instrument_op.
392 0           local $/ = "\n";
393 0           *$glob = [
394 0           map { Scalar::Util::dualvar( 0, $_ ) }
395             ( "BEGIN { require 'perl5db.pl' } # Generated by " . __FILE__,
396             readline $fh )
397             ];
398             }
399              
400 0 50         if ( ! *$glob{HASH} ) {
401 0           my %breakpoints;
402 0           Enbugger::set_magic_dbfile(\%breakpoints);
403 0           *$glob = \%breakpoints;
404             }
405            
406 0   33       $$symname ||= $file;
407            
408 0           return;
409             }
410              
411              
412              
413              
414              
415              
416              
417             sub instrument_runtime {
418             # Now do the *real* work.
419 0     0 1   my ( $class ) = @_;
420              
421             # PL_DBgv = gv_fetchpvs("DB::DB", GV_ADDMULTI, SVt_PVGV);
422 0 50         eval 'sub DB::DB {}' if ! defined &DB::DB;
423              
424             # Load the source code for all loaded files. Too bad about (eval 1)
425             # though. This doesn't work. Why not!?!
426 0           $class->load_source;
427              
428             # PL_DBsingle = GvSV((gv_fetchpvs("DB::single", GV_ADDMULTI, SVt_PV)));
429             # if (!SvIOK(PL_DBsingle))
430             # sv_setiv(PL_DBsingle, 0);
431 0 50         $DB::single = 0 if ! defined $DB::single;
432              
433             # PL_DBtrace = GvSV((gv_fetchpvs("DB::trace", GV_ADDMULTI, SVt_PV)));
434             # if (!SvIOK(PL_DBtrace))
435             # sv_setiv(PL_DBtrace, 0);
436 0 50         $DB::trace = 0 if ! defined $DB::trace;
437              
438             # PL_DBsignal = GvSV((gv_fetchpvs("DB::signal", GV_ADDMULTI, SVt_PV)));
439             # if (!SvIOK(PL_DBsignal))
440             # sv_setiv(PL_DBsignal, 0);
441 0 50         $DB::signal = 0 if ! defined $DB::signal;
442              
443             # Walk over all optrees.
444             # * Transform nextstate COP* nodes to dbstate COP* nodes as appropriate
445             # * Set ${"main::_<$file"}[X] array elements with COP* pointers
446             # * Capture function name start/end line numbers
447 0           B::Utils::walkallops_simple( \ &Enbugger::instrument_op );
448              
449             # Provide %DB::sub.
450 0           %DB::sub =
451 0           map { $_ => sprintf '%s:%d-%d', @{$DBsub{$_}} }
  0            
452             keys %DBsub;
453 0           undef %DBsub;
454             }
455              
456              
457              
458              
459              
460             sub instrument_op {
461 0     0 1   my ( $op ) = @_;
462              
463             # Must be a B::COP node.
464 0 100 100       if ( $$op and B::class( $op ) eq 'COP' ) {
465              
466             # @{"_<$file"} entries where there are COP entries are
467             # dualvars of pointers to the COP nodes that will get
468             # OPf_SPECIAL toggled to indicate breakpoints.
469 0           my $ptr = $$op;
470 0           my $source = do {
471 0     0     no strict 'refs';
  0            
  0            
472 0           \ @{"main::_<$B::Utils::file"};
  0            
473             };
474 0 50         if ( $ptr ) {
475 0           $source->[$B::Utils::line] = Scalar::Util::dualvar( $ptr, $source->[$B::Utils::line] );
476             }
477              
478 0 100         if ($DBsub{$B::Utils::sub}) {
479 0 100         $DBsub{$B::Utils::sub}[1] = $B::Utils::line if $B::Utils::line < $DBsub{$B::Utils::sub}[1];
480 0 100         $DBsub{$B::Utils::sub}[2] = $B::Utils::line if $B::Utils::line > $DBsub{$B::Utils::sub}[2];
481             }
482             else {
483 0           $DBsub{$B::Utils::sub} = [ $B::Utils::file, ($B::Utils::line) x 2 ];
484             }
485              
486             #print $op->file ."\t".$op->line."\t".$o->stash->NAME."\t";
487             # Disable or enable debugging for this opcode.
488 0 100         if ( $op->stash->NAME =~ /^(?=[DE])(?:DB|Enbugger)(?:::|\z)/ ) {
489             #print 'next';
490 0           Enbugger::_nextstate_cop( $op );
491             }
492             else {
493 0           Enbugger::_dbstate_cop( $op );
494             }
495             }
496             }
497              
498              
499              
500              
501              
502             sub import {
503 0     0     my $class = shift @_;
504              
505 0 50         if ( @_ ) {
506 0           my $selected_debugger = shift @_;
507 0           $DefaultDebugger = $selected_debugger;
508             }
509             }
510              
511              
512             BEGIN {
513 0     0     __PACKAGE__->register_debugger( 'perl5db' );
514 0           __PACKAGE__->register_debugger( 'trepan' );
515 0           __PACKAGE__->register_debugger( 'NYTProf' );
516             }
517             # TODO: __PACKAGE__->register_debugger( 'ebug' );
518             # TODO: __PACKAGE__->register_debugger( 'sdb' );
519             # TODO: __PACKAGE__->register_debugger( 'ptkdb' );
520              
521              
522             # Anything compiled after this statement runs will be debuggable.
523             Enbugger->_compile_with_dbstate();
524              
525             ## Local Variables:
526             ## mode: cperl
527             ## mode: auto-fill
528             ## cperl-indent-level: 4
529             ## tab-width: 8
530             ## End:
531              
532 0     0     no warnings 'void'; ## no critic
  0            
  0            
533             'But this is the internet, dear, stupid is one of our prime exports.';