1*0Sstevel@tonic-gate# 2*0Sstevel@tonic-gate# Documentation is at the __END__ 3*0Sstevel@tonic-gate# 4*0Sstevel@tonic-gate 5*0Sstevel@tonic-gatepackage DB; 6*0Sstevel@tonic-gate 7*0Sstevel@tonic-gate# "private" globals 8*0Sstevel@tonic-gate 9*0Sstevel@tonic-gatemy ($running, $ready, $deep, $usrctxt, $evalarg, 10*0Sstevel@tonic-gate @stack, @saved, @skippkg, @clients); 11*0Sstevel@tonic-gatemy $preeval = {}; 12*0Sstevel@tonic-gatemy $posteval = {}; 13*0Sstevel@tonic-gatemy $ineval = {}; 14*0Sstevel@tonic-gate 15*0Sstevel@tonic-gate#### 16*0Sstevel@tonic-gate# 17*0Sstevel@tonic-gate# Globals - must be defined at startup so that clients can refer to 18*0Sstevel@tonic-gate# them right after a C<require DB;> 19*0Sstevel@tonic-gate# 20*0Sstevel@tonic-gate#### 21*0Sstevel@tonic-gate 22*0Sstevel@tonic-gateBEGIN { 23*0Sstevel@tonic-gate 24*0Sstevel@tonic-gate # these are hardcoded in perl source (some are magical) 25*0Sstevel@tonic-gate 26*0Sstevel@tonic-gate $DB::sub = ''; # name of current subroutine 27*0Sstevel@tonic-gate %DB::sub = (); # "filename:fromline-toline" for every known sub 28*0Sstevel@tonic-gate $DB::single = 0; # single-step flag (set it to 1 to enable stops in BEGIN/use) 29*0Sstevel@tonic-gate $DB::signal = 0; # signal flag (will cause a stop at the next line) 30*0Sstevel@tonic-gate $DB::trace = 0; # are we tracing through subroutine calls? 31*0Sstevel@tonic-gate @DB::args = (); # arguments of current subroutine or @ARGV array 32*0Sstevel@tonic-gate @DB::dbline = (); # list of lines in currently loaded file 33*0Sstevel@tonic-gate %DB::dbline = (); # actions in current file (keyed by line number) 34*0Sstevel@tonic-gate @DB::ret = (); # return value of last sub executed in list context 35*0Sstevel@tonic-gate $DB::ret = ''; # return value of last sub executed in scalar context 36*0Sstevel@tonic-gate 37*0Sstevel@tonic-gate # other "public" globals 38*0Sstevel@tonic-gate 39*0Sstevel@tonic-gate $DB::package = ''; # current package space 40*0Sstevel@tonic-gate $DB::filename = ''; # current filename 41*0Sstevel@tonic-gate $DB::subname = ''; # currently executing sub (fullly qualified name) 42*0Sstevel@tonic-gate $DB::lineno = ''; # current line number 43*0Sstevel@tonic-gate 44*0Sstevel@tonic-gate $DB::VERSION = $DB::VERSION = '1.0'; 45*0Sstevel@tonic-gate 46*0Sstevel@tonic-gate # initialize private globals to avoid warnings 47*0Sstevel@tonic-gate 48*0Sstevel@tonic-gate $running = 1; # are we running, or are we stopped? 49*0Sstevel@tonic-gate @stack = (0); 50*0Sstevel@tonic-gate @clients = (); 51*0Sstevel@tonic-gate $deep = 100; 52*0Sstevel@tonic-gate $ready = 0; 53*0Sstevel@tonic-gate @saved = (); 54*0Sstevel@tonic-gate @skippkg = (); 55*0Sstevel@tonic-gate $usrctxt = ''; 56*0Sstevel@tonic-gate $evalarg = ''; 57*0Sstevel@tonic-gate} 58*0Sstevel@tonic-gate 59*0Sstevel@tonic-gate#### 60*0Sstevel@tonic-gate# entry point for all subroutine calls 61*0Sstevel@tonic-gate# 62*0Sstevel@tonic-gatesub sub { 63*0Sstevel@tonic-gate push(@stack, $DB::single); 64*0Sstevel@tonic-gate $DB::single &= 1; 65*0Sstevel@tonic-gate $DB::single |= 4 if $#stack == $deep; 66*0Sstevel@tonic-gate# print $DB::sub, "\n"; 67*0Sstevel@tonic-gate if ($DB::sub =~ /(?:^|::)DESTROY$/ or not defined wantarray) { 68*0Sstevel@tonic-gate &$DB::sub; 69*0Sstevel@tonic-gate $DB::single |= pop(@stack); 70*0Sstevel@tonic-gate $DB::ret = undef; 71*0Sstevel@tonic-gate } 72*0Sstevel@tonic-gate elsif (wantarray) { 73*0Sstevel@tonic-gate @DB::ret = &$DB::sub; 74*0Sstevel@tonic-gate $DB::single |= pop(@stack); 75*0Sstevel@tonic-gate @DB::ret; 76*0Sstevel@tonic-gate } 77*0Sstevel@tonic-gate else { 78*0Sstevel@tonic-gate $DB::ret = &$DB::sub; 79*0Sstevel@tonic-gate $DB::single |= pop(@stack); 80*0Sstevel@tonic-gate $DB::ret; 81*0Sstevel@tonic-gate } 82*0Sstevel@tonic-gate} 83*0Sstevel@tonic-gate 84*0Sstevel@tonic-gate#### 85*0Sstevel@tonic-gate# this is called by perl for every statement 86*0Sstevel@tonic-gate# 87*0Sstevel@tonic-gatesub DB { 88*0Sstevel@tonic-gate return unless $ready; 89*0Sstevel@tonic-gate &save; 90*0Sstevel@tonic-gate ($DB::package, $DB::filename, $DB::lineno) = caller; 91*0Sstevel@tonic-gate 92*0Sstevel@tonic-gate return if @skippkg and grep { $_ eq $DB::package } @skippkg; 93*0Sstevel@tonic-gate 94*0Sstevel@tonic-gate $usrctxt = "package $DB::package;"; # this won't let them modify, alas 95*0Sstevel@tonic-gate local(*DB::dbline) = "::_<$DB::filename"; 96*0Sstevel@tonic-gate 97*0Sstevel@tonic-gate # we need to check for pseudofiles on Mac OS (these are files 98*0Sstevel@tonic-gate # not attached to a filename, but instead stored in Dev:Pseudo) 99*0Sstevel@tonic-gate # since this is done late, $DB::filename will be "wrong" after 100*0Sstevel@tonic-gate # skippkg 101*0Sstevel@tonic-gate if ($^O eq 'MacOS' && $#DB::dbline < 0) { 102*0Sstevel@tonic-gate $DB::filename = 'Dev:Pseudo'; 103*0Sstevel@tonic-gate *DB::dbline = "::_<$DB::filename"; 104*0Sstevel@tonic-gate } 105*0Sstevel@tonic-gate 106*0Sstevel@tonic-gate my ($stop, $action); 107*0Sstevel@tonic-gate if (($stop,$action) = split(/\0/,$DB::dbline{$DB::lineno})) { 108*0Sstevel@tonic-gate if ($stop eq '1') { 109*0Sstevel@tonic-gate $DB::signal |= 1; 110*0Sstevel@tonic-gate } 111*0Sstevel@tonic-gate else { 112*0Sstevel@tonic-gate $stop = 0 unless $stop; # avoid un_init warning 113*0Sstevel@tonic-gate $evalarg = "\$DB::signal |= do { $stop; }"; &eval; 114*0Sstevel@tonic-gate $DB::dbline{$DB::lineno} =~ s/;9($|\0)/$1/; # clear any temp breakpt 115*0Sstevel@tonic-gate } 116*0Sstevel@tonic-gate } 117*0Sstevel@tonic-gate if ($DB::single || $DB::trace || $DB::signal) { 118*0Sstevel@tonic-gate $DB::subname = ($DB::sub =~ /\'|::/) ? $DB::sub : "${DB::package}::$DB::sub"; #'; 119*0Sstevel@tonic-gate DB->loadfile($DB::filename, $DB::lineno); 120*0Sstevel@tonic-gate } 121*0Sstevel@tonic-gate $evalarg = $action, &eval if $action; 122*0Sstevel@tonic-gate if ($DB::single || $DB::signal) { 123*0Sstevel@tonic-gate _outputall($#stack . " levels deep in subroutine calls.\n") if $DB::single & 4; 124*0Sstevel@tonic-gate $DB::single = 0; 125*0Sstevel@tonic-gate $DB::signal = 0; 126*0Sstevel@tonic-gate $running = 0; 127*0Sstevel@tonic-gate 128*0Sstevel@tonic-gate &eval if ($evalarg = DB->prestop); 129*0Sstevel@tonic-gate my $c; 130*0Sstevel@tonic-gate for $c (@clients) { 131*0Sstevel@tonic-gate # perform any client-specific prestop actions 132*0Sstevel@tonic-gate &eval if ($evalarg = $c->cprestop); 133*0Sstevel@tonic-gate 134*0Sstevel@tonic-gate # Now sit in an event loop until something sets $running 135*0Sstevel@tonic-gate do { 136*0Sstevel@tonic-gate $c->idle; # call client event loop; must not block 137*0Sstevel@tonic-gate if ($running == 2) { # client wants something eval-ed 138*0Sstevel@tonic-gate &eval if ($evalarg = $c->evalcode); 139*0Sstevel@tonic-gate $running = 0; 140*0Sstevel@tonic-gate } 141*0Sstevel@tonic-gate } until $running; 142*0Sstevel@tonic-gate 143*0Sstevel@tonic-gate # perform any client-specific poststop actions 144*0Sstevel@tonic-gate &eval if ($evalarg = $c->cpoststop); 145*0Sstevel@tonic-gate } 146*0Sstevel@tonic-gate &eval if ($evalarg = DB->poststop); 147*0Sstevel@tonic-gate } 148*0Sstevel@tonic-gate ($@, $!, $,, $/, $\, $^W) = @saved; 149*0Sstevel@tonic-gate (); 150*0Sstevel@tonic-gate} 151*0Sstevel@tonic-gate 152*0Sstevel@tonic-gate#### 153*0Sstevel@tonic-gate# this takes its argument via $evalarg to preserve current @_ 154*0Sstevel@tonic-gate# 155*0Sstevel@tonic-gatesub eval { 156*0Sstevel@tonic-gate ($@, $!, $,, $/, $\, $^W) = @saved; 157*0Sstevel@tonic-gate eval "$usrctxt $evalarg; &DB::save"; 158*0Sstevel@tonic-gate _outputall($@) if $@; 159*0Sstevel@tonic-gate} 160*0Sstevel@tonic-gate 161*0Sstevel@tonic-gate############################################################################### 162*0Sstevel@tonic-gate# no compile-time subroutine call allowed before this point # 163*0Sstevel@tonic-gate############################################################################### 164*0Sstevel@tonic-gate 165*0Sstevel@tonic-gateuse strict; # this can run only after DB() and sub() are defined 166*0Sstevel@tonic-gate 167*0Sstevel@tonic-gatesub save { 168*0Sstevel@tonic-gate @saved = ($@, $!, $,, $/, $\, $^W); 169*0Sstevel@tonic-gate $, = ""; $/ = "\n"; $\ = ""; $^W = 0; 170*0Sstevel@tonic-gate} 171*0Sstevel@tonic-gate 172*0Sstevel@tonic-gatesub catch { 173*0Sstevel@tonic-gate for (@clients) { $_->awaken; } 174*0Sstevel@tonic-gate $DB::signal = 1; 175*0Sstevel@tonic-gate $ready = 1; 176*0Sstevel@tonic-gate} 177*0Sstevel@tonic-gate 178*0Sstevel@tonic-gate#### 179*0Sstevel@tonic-gate# 180*0Sstevel@tonic-gate# Client callable (read inheritable) methods defined after this point 181*0Sstevel@tonic-gate# 182*0Sstevel@tonic-gate#### 183*0Sstevel@tonic-gate 184*0Sstevel@tonic-gatesub register { 185*0Sstevel@tonic-gate my $s = shift; 186*0Sstevel@tonic-gate $s = _clientname($s) if ref($s); 187*0Sstevel@tonic-gate push @clients, $s; 188*0Sstevel@tonic-gate} 189*0Sstevel@tonic-gate 190*0Sstevel@tonic-gatesub done { 191*0Sstevel@tonic-gate my $s = shift; 192*0Sstevel@tonic-gate $s = _clientname($s) if ref($s); 193*0Sstevel@tonic-gate @clients = grep {$_ ne $s} @clients; 194*0Sstevel@tonic-gate $s->cleanup; 195*0Sstevel@tonic-gate# $running = 3 unless @clients; 196*0Sstevel@tonic-gate exit(0) unless @clients; 197*0Sstevel@tonic-gate} 198*0Sstevel@tonic-gate 199*0Sstevel@tonic-gatesub _clientname { 200*0Sstevel@tonic-gate my $name = shift; 201*0Sstevel@tonic-gate "$name" =~ /^(.+)=[A-Z]+\(.+\)$/; 202*0Sstevel@tonic-gate return $1; 203*0Sstevel@tonic-gate} 204*0Sstevel@tonic-gate 205*0Sstevel@tonic-gatesub next { 206*0Sstevel@tonic-gate my $s = shift; 207*0Sstevel@tonic-gate $DB::single = 2; 208*0Sstevel@tonic-gate $running = 1; 209*0Sstevel@tonic-gate} 210*0Sstevel@tonic-gate 211*0Sstevel@tonic-gatesub step { 212*0Sstevel@tonic-gate my $s = shift; 213*0Sstevel@tonic-gate $DB::single = 1; 214*0Sstevel@tonic-gate $running = 1; 215*0Sstevel@tonic-gate} 216*0Sstevel@tonic-gate 217*0Sstevel@tonic-gatesub cont { 218*0Sstevel@tonic-gate my $s = shift; 219*0Sstevel@tonic-gate my $i = shift; 220*0Sstevel@tonic-gate $s->set_tbreak($i) if $i; 221*0Sstevel@tonic-gate for ($i = 0; $i <= $#stack;) { 222*0Sstevel@tonic-gate $stack[$i++] &= ~1; 223*0Sstevel@tonic-gate } 224*0Sstevel@tonic-gate $DB::single = 0; 225*0Sstevel@tonic-gate $running = 1; 226*0Sstevel@tonic-gate} 227*0Sstevel@tonic-gate 228*0Sstevel@tonic-gate#### 229*0Sstevel@tonic-gate# XXX caller must experimentally determine $i (since it depends 230*0Sstevel@tonic-gate# on how many client call frames are between this call and the DB call). 231*0Sstevel@tonic-gate# Such is life. 232*0Sstevel@tonic-gate# 233*0Sstevel@tonic-gatesub ret { 234*0Sstevel@tonic-gate my $s = shift; 235*0Sstevel@tonic-gate my $i = shift; # how many levels to get to DB sub 236*0Sstevel@tonic-gate $i = 0 unless defined $i; 237*0Sstevel@tonic-gate $stack[$#stack-$i] |= 1; 238*0Sstevel@tonic-gate $DB::single = 0; 239*0Sstevel@tonic-gate $running = 1; 240*0Sstevel@tonic-gate} 241*0Sstevel@tonic-gate 242*0Sstevel@tonic-gate#### 243*0Sstevel@tonic-gate# XXX caller must experimentally determine $start (since it depends 244*0Sstevel@tonic-gate# on how many client call frames are between this call and the DB call). 245*0Sstevel@tonic-gate# Such is life. 246*0Sstevel@tonic-gate# 247*0Sstevel@tonic-gatesub backtrace { 248*0Sstevel@tonic-gate my $self = shift; 249*0Sstevel@tonic-gate my $start = shift; 250*0Sstevel@tonic-gate my($p,$f,$l,$s,$h,$w,$e,$r,$a, @a, @ret,$i); 251*0Sstevel@tonic-gate $start = 1 unless $start; 252*0Sstevel@tonic-gate for ($i = $start; ($p,$f,$l,$s,$h,$w,$e,$r) = caller($i); $i++) { 253*0Sstevel@tonic-gate @a = @DB::args; 254*0Sstevel@tonic-gate for (@a) { 255*0Sstevel@tonic-gate s/'/\\'/g; 256*0Sstevel@tonic-gate s/([^\0]*)/'$1'/ unless /^-?[\d.]+$/; 257*0Sstevel@tonic-gate s/([\200-\377])/sprintf("M-%c",ord($1)&0177)/eg; 258*0Sstevel@tonic-gate s/([\0-\37\177])/sprintf("^%c",ord($1)^64)/eg; 259*0Sstevel@tonic-gate } 260*0Sstevel@tonic-gate $w = $w ? '@ = ' : '$ = '; 261*0Sstevel@tonic-gate $a = $h ? '(' . join(', ', @a) . ')' : ''; 262*0Sstevel@tonic-gate $e =~ s/\n\s*\;\s*\Z// if $e; 263*0Sstevel@tonic-gate $e =~ s/[\\\']/\\$1/g if $e; 264*0Sstevel@tonic-gate if ($r) { 265*0Sstevel@tonic-gate $s = "require '$e'"; 266*0Sstevel@tonic-gate } elsif (defined $r) { 267*0Sstevel@tonic-gate $s = "eval '$e'"; 268*0Sstevel@tonic-gate } elsif ($s eq '(eval)') { 269*0Sstevel@tonic-gate $s = "eval {...}"; 270*0Sstevel@tonic-gate } 271*0Sstevel@tonic-gate $f = "file `$f'" unless $f eq '-e'; 272*0Sstevel@tonic-gate push @ret, "$w&$s$a from $f line $l"; 273*0Sstevel@tonic-gate last if $DB::signal; 274*0Sstevel@tonic-gate } 275*0Sstevel@tonic-gate return @ret; 276*0Sstevel@tonic-gate} 277*0Sstevel@tonic-gate 278*0Sstevel@tonic-gatesub _outputall { 279*0Sstevel@tonic-gate my $c; 280*0Sstevel@tonic-gate for $c (@clients) { 281*0Sstevel@tonic-gate $c->output(@_); 282*0Sstevel@tonic-gate } 283*0Sstevel@tonic-gate} 284*0Sstevel@tonic-gate 285*0Sstevel@tonic-gatesub trace_toggle { 286*0Sstevel@tonic-gate my $s = shift; 287*0Sstevel@tonic-gate $DB::trace = !$DB::trace; 288*0Sstevel@tonic-gate} 289*0Sstevel@tonic-gate 290*0Sstevel@tonic-gate 291*0Sstevel@tonic-gate#### 292*0Sstevel@tonic-gate# without args: returns all defined subroutine names 293*0Sstevel@tonic-gate# with subname args: returns a listref [file, start, end] 294*0Sstevel@tonic-gate# 295*0Sstevel@tonic-gatesub subs { 296*0Sstevel@tonic-gate my $s = shift; 297*0Sstevel@tonic-gate if (@_) { 298*0Sstevel@tonic-gate my(@ret) = (); 299*0Sstevel@tonic-gate while (@_) { 300*0Sstevel@tonic-gate my $name = shift; 301*0Sstevel@tonic-gate push @ret, [$DB::sub{$name} =~ /^(.*)\:(\d+)-(\d+)$/] 302*0Sstevel@tonic-gate if exists $DB::sub{$name}; 303*0Sstevel@tonic-gate } 304*0Sstevel@tonic-gate return @ret; 305*0Sstevel@tonic-gate } 306*0Sstevel@tonic-gate return keys %DB::sub; 307*0Sstevel@tonic-gate} 308*0Sstevel@tonic-gate 309*0Sstevel@tonic-gate#### 310*0Sstevel@tonic-gate# first argument is a filename whose subs will be returned 311*0Sstevel@tonic-gate# if a filename is not supplied, all subs in the current 312*0Sstevel@tonic-gate# filename are returned. 313*0Sstevel@tonic-gate# 314*0Sstevel@tonic-gatesub filesubs { 315*0Sstevel@tonic-gate my $s = shift; 316*0Sstevel@tonic-gate my $fname = shift; 317*0Sstevel@tonic-gate $fname = $DB::filename unless $fname; 318*0Sstevel@tonic-gate return grep { $DB::sub{$_} =~ /^$fname/ } keys %DB::sub; 319*0Sstevel@tonic-gate} 320*0Sstevel@tonic-gate 321*0Sstevel@tonic-gate#### 322*0Sstevel@tonic-gate# returns a list of all filenames that DB knows about 323*0Sstevel@tonic-gate# 324*0Sstevel@tonic-gatesub files { 325*0Sstevel@tonic-gate my $s = shift; 326*0Sstevel@tonic-gate my(@f) = grep(m|^_<|, keys %main::); 327*0Sstevel@tonic-gate return map { substr($_,2) } @f; 328*0Sstevel@tonic-gate} 329*0Sstevel@tonic-gate 330*0Sstevel@tonic-gate#### 331*0Sstevel@tonic-gate# returns reference to an array holding the lines in currently 332*0Sstevel@tonic-gate# loaded file 333*0Sstevel@tonic-gate# 334*0Sstevel@tonic-gatesub lines { 335*0Sstevel@tonic-gate my $s = shift; 336*0Sstevel@tonic-gate return \@DB::dbline; 337*0Sstevel@tonic-gate} 338*0Sstevel@tonic-gate 339*0Sstevel@tonic-gate#### 340*0Sstevel@tonic-gate# loadfile($file, $line) 341*0Sstevel@tonic-gate# 342*0Sstevel@tonic-gatesub loadfile { 343*0Sstevel@tonic-gate my $s = shift; 344*0Sstevel@tonic-gate my($file, $line) = @_; 345*0Sstevel@tonic-gate if (!defined $main::{'_<' . $file}) { 346*0Sstevel@tonic-gate my $try; 347*0Sstevel@tonic-gate if (($try) = grep(m|^_<.*$file|, keys %main::)) { 348*0Sstevel@tonic-gate $file = substr($try,2); 349*0Sstevel@tonic-gate } 350*0Sstevel@tonic-gate } 351*0Sstevel@tonic-gate if (defined($main::{'_<' . $file})) { 352*0Sstevel@tonic-gate my $c; 353*0Sstevel@tonic-gate# _outputall("Loading file $file.."); 354*0Sstevel@tonic-gate *DB::dbline = "::_<$file"; 355*0Sstevel@tonic-gate $DB::filename = $file; 356*0Sstevel@tonic-gate for $c (@clients) { 357*0Sstevel@tonic-gate# print "2 ", $file, '|', $line, "\n"; 358*0Sstevel@tonic-gate $c->showfile($file, $line); 359*0Sstevel@tonic-gate } 360*0Sstevel@tonic-gate return $file; 361*0Sstevel@tonic-gate } 362*0Sstevel@tonic-gate return undef; 363*0Sstevel@tonic-gate} 364*0Sstevel@tonic-gate 365*0Sstevel@tonic-gatesub lineevents { 366*0Sstevel@tonic-gate my $s = shift; 367*0Sstevel@tonic-gate my $fname = shift; 368*0Sstevel@tonic-gate my(%ret) = (); 369*0Sstevel@tonic-gate my $i; 370*0Sstevel@tonic-gate $fname = $DB::filename unless $fname; 371*0Sstevel@tonic-gate local(*DB::dbline) = "::_<$fname"; 372*0Sstevel@tonic-gate for ($i = 1; $i <= $#DB::dbline; $i++) { 373*0Sstevel@tonic-gate $ret{$i} = [$DB::dbline[$i], split(/\0/, $DB::dbline{$i})] 374*0Sstevel@tonic-gate if defined $DB::dbline{$i}; 375*0Sstevel@tonic-gate } 376*0Sstevel@tonic-gate return %ret; 377*0Sstevel@tonic-gate} 378*0Sstevel@tonic-gate 379*0Sstevel@tonic-gatesub set_break { 380*0Sstevel@tonic-gate my $s = shift; 381*0Sstevel@tonic-gate my $i = shift; 382*0Sstevel@tonic-gate my $cond = shift; 383*0Sstevel@tonic-gate $i ||= $DB::lineno; 384*0Sstevel@tonic-gate $cond ||= '1'; 385*0Sstevel@tonic-gate $i = _find_subline($i) if ($i =~ /\D/); 386*0Sstevel@tonic-gate $s->output("Subroutine not found.\n") unless $i; 387*0Sstevel@tonic-gate if ($i) { 388*0Sstevel@tonic-gate if ($DB::dbline[$i] == 0) { 389*0Sstevel@tonic-gate $s->output("Line $i not breakable.\n"); 390*0Sstevel@tonic-gate } 391*0Sstevel@tonic-gate else { 392*0Sstevel@tonic-gate $DB::dbline{$i} =~ s/^[^\0]*/$cond/; 393*0Sstevel@tonic-gate } 394*0Sstevel@tonic-gate } 395*0Sstevel@tonic-gate} 396*0Sstevel@tonic-gate 397*0Sstevel@tonic-gatesub set_tbreak { 398*0Sstevel@tonic-gate my $s = shift; 399*0Sstevel@tonic-gate my $i = shift; 400*0Sstevel@tonic-gate $i = _find_subline($i) if ($i =~ /\D/); 401*0Sstevel@tonic-gate $s->output("Subroutine not found.\n") unless $i; 402*0Sstevel@tonic-gate if ($i) { 403*0Sstevel@tonic-gate if ($DB::dbline[$i] == 0) { 404*0Sstevel@tonic-gate $s->output("Line $i not breakable.\n"); 405*0Sstevel@tonic-gate } 406*0Sstevel@tonic-gate else { 407*0Sstevel@tonic-gate $DB::dbline{$i} =~ s/($|\0)/;9$1/; # add one-time-only b.p. 408*0Sstevel@tonic-gate } 409*0Sstevel@tonic-gate } 410*0Sstevel@tonic-gate} 411*0Sstevel@tonic-gate 412*0Sstevel@tonic-gatesub _find_subline { 413*0Sstevel@tonic-gate my $name = shift; 414*0Sstevel@tonic-gate $name =~ s/\'/::/; 415*0Sstevel@tonic-gate $name = "${DB::package}\:\:" . $name if $name !~ /::/; 416*0Sstevel@tonic-gate $name = "main" . $name if substr($name,0,2) eq "::"; 417*0Sstevel@tonic-gate my($fname, $from, $to) = ($DB::sub{$name} =~ /^(.*):(\d+)-(\d+)$/); 418*0Sstevel@tonic-gate if ($from) { 419*0Sstevel@tonic-gate local *DB::dbline = "::_<$fname"; 420*0Sstevel@tonic-gate ++$from while $DB::dbline[$from] == 0 && $from < $to; 421*0Sstevel@tonic-gate return $from; 422*0Sstevel@tonic-gate } 423*0Sstevel@tonic-gate return undef; 424*0Sstevel@tonic-gate} 425*0Sstevel@tonic-gate 426*0Sstevel@tonic-gatesub clr_breaks { 427*0Sstevel@tonic-gate my $s = shift; 428*0Sstevel@tonic-gate my $i; 429*0Sstevel@tonic-gate if (@_) { 430*0Sstevel@tonic-gate while (@_) { 431*0Sstevel@tonic-gate $i = shift; 432*0Sstevel@tonic-gate $i = _find_subline($i) if ($i =~ /\D/); 433*0Sstevel@tonic-gate $s->output("Subroutine not found.\n") unless $i; 434*0Sstevel@tonic-gate if (defined $DB::dbline{$i}) { 435*0Sstevel@tonic-gate $DB::dbline{$i} =~ s/^[^\0]+//; 436*0Sstevel@tonic-gate if ($DB::dbline{$i} =~ s/^\0?$//) { 437*0Sstevel@tonic-gate delete $DB::dbline{$i}; 438*0Sstevel@tonic-gate } 439*0Sstevel@tonic-gate } 440*0Sstevel@tonic-gate } 441*0Sstevel@tonic-gate } 442*0Sstevel@tonic-gate else { 443*0Sstevel@tonic-gate for ($i = 1; $i <= $#DB::dbline ; $i++) { 444*0Sstevel@tonic-gate if (defined $DB::dbline{$i}) { 445*0Sstevel@tonic-gate $DB::dbline{$i} =~ s/^[^\0]+//; 446*0Sstevel@tonic-gate if ($DB::dbline{$i} =~ s/^\0?$//) { 447*0Sstevel@tonic-gate delete $DB::dbline{$i}; 448*0Sstevel@tonic-gate } 449*0Sstevel@tonic-gate } 450*0Sstevel@tonic-gate } 451*0Sstevel@tonic-gate } 452*0Sstevel@tonic-gate} 453*0Sstevel@tonic-gate 454*0Sstevel@tonic-gatesub set_action { 455*0Sstevel@tonic-gate my $s = shift; 456*0Sstevel@tonic-gate my $i = shift; 457*0Sstevel@tonic-gate my $act = shift; 458*0Sstevel@tonic-gate $i = _find_subline($i) if ($i =~ /\D/); 459*0Sstevel@tonic-gate $s->output("Subroutine not found.\n") unless $i; 460*0Sstevel@tonic-gate if ($i) { 461*0Sstevel@tonic-gate if ($DB::dbline[$i] == 0) { 462*0Sstevel@tonic-gate $s->output("Line $i not actionable.\n"); 463*0Sstevel@tonic-gate } 464*0Sstevel@tonic-gate else { 465*0Sstevel@tonic-gate $DB::dbline{$i} =~ s/\0[^\0]*//; 466*0Sstevel@tonic-gate $DB::dbline{$i} .= "\0" . $act; 467*0Sstevel@tonic-gate } 468*0Sstevel@tonic-gate } 469*0Sstevel@tonic-gate} 470*0Sstevel@tonic-gate 471*0Sstevel@tonic-gatesub clr_actions { 472*0Sstevel@tonic-gate my $s = shift; 473*0Sstevel@tonic-gate my $i; 474*0Sstevel@tonic-gate if (@_) { 475*0Sstevel@tonic-gate while (@_) { 476*0Sstevel@tonic-gate my $i = shift; 477*0Sstevel@tonic-gate $i = _find_subline($i) if ($i =~ /\D/); 478*0Sstevel@tonic-gate $s->output("Subroutine not found.\n") unless $i; 479*0Sstevel@tonic-gate if ($i && $DB::dbline[$i] != 0) { 480*0Sstevel@tonic-gate $DB::dbline{$i} =~ s/\0[^\0]*//; 481*0Sstevel@tonic-gate delete $DB::dbline{$i} if $DB::dbline{$i} =~ s/^\0?$//; 482*0Sstevel@tonic-gate } 483*0Sstevel@tonic-gate } 484*0Sstevel@tonic-gate } 485*0Sstevel@tonic-gate else { 486*0Sstevel@tonic-gate for ($i = 1; $i <= $#DB::dbline ; $i++) { 487*0Sstevel@tonic-gate if (defined $DB::dbline{$i}) { 488*0Sstevel@tonic-gate $DB::dbline{$i} =~ s/\0[^\0]*//; 489*0Sstevel@tonic-gate delete $DB::dbline{$i} if $DB::dbline{$i} =~ s/^\0?$//; 490*0Sstevel@tonic-gate } 491*0Sstevel@tonic-gate } 492*0Sstevel@tonic-gate } 493*0Sstevel@tonic-gate} 494*0Sstevel@tonic-gate 495*0Sstevel@tonic-gatesub prestop { 496*0Sstevel@tonic-gate my ($client, $val) = @_; 497*0Sstevel@tonic-gate return defined($val) ? $preeval->{$client} = $val : $preeval->{$client}; 498*0Sstevel@tonic-gate} 499*0Sstevel@tonic-gate 500*0Sstevel@tonic-gatesub poststop { 501*0Sstevel@tonic-gate my ($client, $val) = @_; 502*0Sstevel@tonic-gate return defined($val) ? $posteval->{$client} = $val : $posteval->{$client}; 503*0Sstevel@tonic-gate} 504*0Sstevel@tonic-gate 505*0Sstevel@tonic-gate# 506*0Sstevel@tonic-gate# "pure virtual" methods 507*0Sstevel@tonic-gate# 508*0Sstevel@tonic-gate 509*0Sstevel@tonic-gate# client-specific pre/post-stop actions. 510*0Sstevel@tonic-gatesub cprestop {} 511*0Sstevel@tonic-gatesub cpoststop {} 512*0Sstevel@tonic-gate 513*0Sstevel@tonic-gate# client complete startup 514*0Sstevel@tonic-gatesub awaken {} 515*0Sstevel@tonic-gate 516*0Sstevel@tonic-gatesub skippkg { 517*0Sstevel@tonic-gate my $s = shift; 518*0Sstevel@tonic-gate push @skippkg, @_ if @_; 519*0Sstevel@tonic-gate} 520*0Sstevel@tonic-gate 521*0Sstevel@tonic-gatesub evalcode { 522*0Sstevel@tonic-gate my ($client, $val) = @_; 523*0Sstevel@tonic-gate if (defined $val) { 524*0Sstevel@tonic-gate $running = 2; # hand over to DB() to evaluate in its context 525*0Sstevel@tonic-gate $ineval->{$client} = $val; 526*0Sstevel@tonic-gate } 527*0Sstevel@tonic-gate return $ineval->{$client}; 528*0Sstevel@tonic-gate} 529*0Sstevel@tonic-gate 530*0Sstevel@tonic-gatesub ready { 531*0Sstevel@tonic-gate my $s = shift; 532*0Sstevel@tonic-gate return $ready = 1; 533*0Sstevel@tonic-gate} 534*0Sstevel@tonic-gate 535*0Sstevel@tonic-gate# stubs 536*0Sstevel@tonic-gate 537*0Sstevel@tonic-gatesub init {} 538*0Sstevel@tonic-gatesub stop {} 539*0Sstevel@tonic-gatesub idle {} 540*0Sstevel@tonic-gatesub cleanup {} 541*0Sstevel@tonic-gatesub output {} 542*0Sstevel@tonic-gate 543*0Sstevel@tonic-gate# 544*0Sstevel@tonic-gate# client init 545*0Sstevel@tonic-gate# 546*0Sstevel@tonic-gatefor (@clients) { $_->init } 547*0Sstevel@tonic-gate 548*0Sstevel@tonic-gate$SIG{'INT'} = \&DB::catch; 549*0Sstevel@tonic-gate 550*0Sstevel@tonic-gate# disable this if stepping through END blocks is desired 551*0Sstevel@tonic-gate# (looks scary and deconstructivist with Swat) 552*0Sstevel@tonic-gateEND { $ready = 0 } 553*0Sstevel@tonic-gate 554*0Sstevel@tonic-gate1; 555*0Sstevel@tonic-gate__END__ 556*0Sstevel@tonic-gate 557*0Sstevel@tonic-gate=head1 NAME 558*0Sstevel@tonic-gate 559*0Sstevel@tonic-gateDB - programmatic interface to the Perl debugging API (draft, subject to 560*0Sstevel@tonic-gatechange) 561*0Sstevel@tonic-gate 562*0Sstevel@tonic-gate=head1 SYNOPSIS 563*0Sstevel@tonic-gate 564*0Sstevel@tonic-gate package CLIENT; 565*0Sstevel@tonic-gate use DB; 566*0Sstevel@tonic-gate @ISA = qw(DB); 567*0Sstevel@tonic-gate 568*0Sstevel@tonic-gate # these (inherited) methods can be called by the client 569*0Sstevel@tonic-gate 570*0Sstevel@tonic-gate CLIENT->register() # register a client package name 571*0Sstevel@tonic-gate CLIENT->done() # de-register from the debugging API 572*0Sstevel@tonic-gate CLIENT->skippkg('hide::hide') # ask DB not to stop in this package 573*0Sstevel@tonic-gate CLIENT->cont([WHERE]) # run some more (until BREAK or another breakpt) 574*0Sstevel@tonic-gate CLIENT->step() # single step 575*0Sstevel@tonic-gate CLIENT->next() # step over 576*0Sstevel@tonic-gate CLIENT->ret() # return from current subroutine 577*0Sstevel@tonic-gate CLIENT->backtrace() # return the call stack description 578*0Sstevel@tonic-gate CLIENT->ready() # call when client setup is done 579*0Sstevel@tonic-gate CLIENT->trace_toggle() # toggle subroutine call trace mode 580*0Sstevel@tonic-gate CLIENT->subs([SUBS]) # return subroutine information 581*0Sstevel@tonic-gate CLIENT->files() # return list of all files known to DB 582*0Sstevel@tonic-gate CLIENT->lines() # return lines in currently loaded file 583*0Sstevel@tonic-gate CLIENT->loadfile(FILE,LINE) # load a file and let other clients know 584*0Sstevel@tonic-gate CLIENT->lineevents() # return info on lines with actions 585*0Sstevel@tonic-gate CLIENT->set_break([WHERE],[COND]) 586*0Sstevel@tonic-gate CLIENT->set_tbreak([WHERE]) 587*0Sstevel@tonic-gate CLIENT->clr_breaks([LIST]) 588*0Sstevel@tonic-gate CLIENT->set_action(WHERE,ACTION) 589*0Sstevel@tonic-gate CLIENT->clr_actions([LIST]) 590*0Sstevel@tonic-gate CLIENT->evalcode(STRING) # eval STRING in executing code's context 591*0Sstevel@tonic-gate CLIENT->prestop([STRING]) # execute in code context before stopping 592*0Sstevel@tonic-gate CLIENT->poststop([STRING])# execute in code context before resuming 593*0Sstevel@tonic-gate 594*0Sstevel@tonic-gate # These methods will be called at the appropriate times. 595*0Sstevel@tonic-gate # Stub versions provided do nothing. 596*0Sstevel@tonic-gate # None of these can block. 597*0Sstevel@tonic-gate 598*0Sstevel@tonic-gate CLIENT->init() # called when debug API inits itself 599*0Sstevel@tonic-gate CLIENT->stop(FILE,LINE) # when execution stops 600*0Sstevel@tonic-gate CLIENT->idle() # while stopped (can be a client event loop) 601*0Sstevel@tonic-gate CLIENT->cleanup() # just before exit 602*0Sstevel@tonic-gate CLIENT->output(LIST) # called to print any output that API must show 603*0Sstevel@tonic-gate 604*0Sstevel@tonic-gate=head1 DESCRIPTION 605*0Sstevel@tonic-gate 606*0Sstevel@tonic-gatePerl debug information is frequently required not just by debuggers, 607*0Sstevel@tonic-gatebut also by modules that need some "special" information to do their 608*0Sstevel@tonic-gatejob properly, like profilers. 609*0Sstevel@tonic-gate 610*0Sstevel@tonic-gateThis module abstracts and provides all of the hooks into Perl internal 611*0Sstevel@tonic-gatedebugging functionality, so that various implementations of Perl debuggers 612*0Sstevel@tonic-gate(or packages that want to simply get at the "privileged" debugging data) 613*0Sstevel@tonic-gatecan all benefit from the development of this common code. Currently used 614*0Sstevel@tonic-gateby Swat, the perl/Tk GUI debugger. 615*0Sstevel@tonic-gate 616*0Sstevel@tonic-gateNote that multiple "front-ends" can latch into this debugging API 617*0Sstevel@tonic-gatesimultaneously. This is intended to facilitate things like 618*0Sstevel@tonic-gatedebugging with a command line and GUI at the same time, debugging 619*0Sstevel@tonic-gatedebuggers etc. [Sounds nice, but this needs some serious support -- GSAR] 620*0Sstevel@tonic-gate 621*0Sstevel@tonic-gateIn particular, this API does B<not> provide the following functions: 622*0Sstevel@tonic-gate 623*0Sstevel@tonic-gate=over 4 624*0Sstevel@tonic-gate 625*0Sstevel@tonic-gate=item * 626*0Sstevel@tonic-gate 627*0Sstevel@tonic-gatedata display 628*0Sstevel@tonic-gate 629*0Sstevel@tonic-gate=item * 630*0Sstevel@tonic-gate 631*0Sstevel@tonic-gatecommand processing 632*0Sstevel@tonic-gate 633*0Sstevel@tonic-gate=item * 634*0Sstevel@tonic-gate 635*0Sstevel@tonic-gatecommand alias management 636*0Sstevel@tonic-gate 637*0Sstevel@tonic-gate=item * 638*0Sstevel@tonic-gate 639*0Sstevel@tonic-gateuser interface (tty or graphical) 640*0Sstevel@tonic-gate 641*0Sstevel@tonic-gate=back 642*0Sstevel@tonic-gate 643*0Sstevel@tonic-gateThese are intended to be services performed by the clients of this API. 644*0Sstevel@tonic-gate 645*0Sstevel@tonic-gateThis module attempts to be squeaky clean w.r.t C<use strict;> and when 646*0Sstevel@tonic-gatewarnings are enabled. 647*0Sstevel@tonic-gate 648*0Sstevel@tonic-gate 649*0Sstevel@tonic-gate=head2 Global Variables 650*0Sstevel@tonic-gate 651*0Sstevel@tonic-gateThe following "public" global names can be read by clients of this API. 652*0Sstevel@tonic-gateBeware that these should be considered "readonly". 653*0Sstevel@tonic-gate 654*0Sstevel@tonic-gate=over 8 655*0Sstevel@tonic-gate 656*0Sstevel@tonic-gate=item $DB::sub 657*0Sstevel@tonic-gate 658*0Sstevel@tonic-gateName of current executing subroutine. 659*0Sstevel@tonic-gate 660*0Sstevel@tonic-gate=item %DB::sub 661*0Sstevel@tonic-gate 662*0Sstevel@tonic-gateThe keys of this hash are the names of all the known subroutines. Each value 663*0Sstevel@tonic-gateis an encoded string that has the sprintf(3) format 664*0Sstevel@tonic-gateC<("%s:%d-%d", filename, fromline, toline)>. 665*0Sstevel@tonic-gate 666*0Sstevel@tonic-gate=item $DB::single 667*0Sstevel@tonic-gate 668*0Sstevel@tonic-gateSingle-step flag. Will be true if the API will stop at the next statement. 669*0Sstevel@tonic-gate 670*0Sstevel@tonic-gate=item $DB::signal 671*0Sstevel@tonic-gate 672*0Sstevel@tonic-gateSignal flag. Will be set to a true value if a signal was caught. Clients may 673*0Sstevel@tonic-gatecheck for this flag to abort time-consuming operations. 674*0Sstevel@tonic-gate 675*0Sstevel@tonic-gate=item $DB::trace 676*0Sstevel@tonic-gate 677*0Sstevel@tonic-gateThis flag is set to true if the API is tracing through subroutine calls. 678*0Sstevel@tonic-gate 679*0Sstevel@tonic-gate=item @DB::args 680*0Sstevel@tonic-gate 681*0Sstevel@tonic-gateContains the arguments of current subroutine, or the C<@ARGV> array if in the 682*0Sstevel@tonic-gatetoplevel context. 683*0Sstevel@tonic-gate 684*0Sstevel@tonic-gate=item @DB::dbline 685*0Sstevel@tonic-gate 686*0Sstevel@tonic-gateList of lines in currently loaded file. 687*0Sstevel@tonic-gate 688*0Sstevel@tonic-gate=item %DB::dbline 689*0Sstevel@tonic-gate 690*0Sstevel@tonic-gateActions in current file (keys are line numbers). The values are strings that 691*0Sstevel@tonic-gatehave the sprintf(3) format C<("%s\000%s", breakcondition, actioncode)>. 692*0Sstevel@tonic-gate 693*0Sstevel@tonic-gate=item $DB::package 694*0Sstevel@tonic-gate 695*0Sstevel@tonic-gatePackage namespace of currently executing code. 696*0Sstevel@tonic-gate 697*0Sstevel@tonic-gate=item $DB::filename 698*0Sstevel@tonic-gate 699*0Sstevel@tonic-gateCurrently loaded filename. 700*0Sstevel@tonic-gate 701*0Sstevel@tonic-gate=item $DB::subname 702*0Sstevel@tonic-gate 703*0Sstevel@tonic-gateFully qualified name of currently executing subroutine. 704*0Sstevel@tonic-gate 705*0Sstevel@tonic-gate=item $DB::lineno 706*0Sstevel@tonic-gate 707*0Sstevel@tonic-gateLine number that will be executed next. 708*0Sstevel@tonic-gate 709*0Sstevel@tonic-gate=back 710*0Sstevel@tonic-gate 711*0Sstevel@tonic-gate=head2 API Methods 712*0Sstevel@tonic-gate 713*0Sstevel@tonic-gateThe following are methods in the DB base class. A client must 714*0Sstevel@tonic-gateaccess these methods by inheritance (*not* by calling them directly), 715*0Sstevel@tonic-gatesince the API keeps track of clients through the inheritance 716*0Sstevel@tonic-gatemechanism. 717*0Sstevel@tonic-gate 718*0Sstevel@tonic-gate=over 8 719*0Sstevel@tonic-gate 720*0Sstevel@tonic-gate=item CLIENT->register() 721*0Sstevel@tonic-gate 722*0Sstevel@tonic-gateregister a client object/package 723*0Sstevel@tonic-gate 724*0Sstevel@tonic-gate=item CLIENT->evalcode(STRING) 725*0Sstevel@tonic-gate 726*0Sstevel@tonic-gateeval STRING in executing code context 727*0Sstevel@tonic-gate 728*0Sstevel@tonic-gate=item CLIENT->skippkg('D::hide') 729*0Sstevel@tonic-gate 730*0Sstevel@tonic-gateask DB not to stop in these packages 731*0Sstevel@tonic-gate 732*0Sstevel@tonic-gate=item CLIENT->run() 733*0Sstevel@tonic-gate 734*0Sstevel@tonic-gaterun some more (until a breakpt is reached) 735*0Sstevel@tonic-gate 736*0Sstevel@tonic-gate=item CLIENT->step() 737*0Sstevel@tonic-gate 738*0Sstevel@tonic-gatesingle step 739*0Sstevel@tonic-gate 740*0Sstevel@tonic-gate=item CLIENT->next() 741*0Sstevel@tonic-gate 742*0Sstevel@tonic-gatestep over 743*0Sstevel@tonic-gate 744*0Sstevel@tonic-gate=item CLIENT->done() 745*0Sstevel@tonic-gate 746*0Sstevel@tonic-gatede-register from the debugging API 747*0Sstevel@tonic-gate 748*0Sstevel@tonic-gate=back 749*0Sstevel@tonic-gate 750*0Sstevel@tonic-gate=head2 Client Callback Methods 751*0Sstevel@tonic-gate 752*0Sstevel@tonic-gateThe following "virtual" methods can be defined by the client. They will 753*0Sstevel@tonic-gatebe called by the API at appropriate points. Note that unless specified 754*0Sstevel@tonic-gateotherwise, the debug API only defines empty, non-functional default versions 755*0Sstevel@tonic-gateof these methods. 756*0Sstevel@tonic-gate 757*0Sstevel@tonic-gate=over 8 758*0Sstevel@tonic-gate 759*0Sstevel@tonic-gate=item CLIENT->init() 760*0Sstevel@tonic-gate 761*0Sstevel@tonic-gateCalled after debug API inits itself. 762*0Sstevel@tonic-gate 763*0Sstevel@tonic-gate=item CLIENT->prestop([STRING]) 764*0Sstevel@tonic-gate 765*0Sstevel@tonic-gateUsually inherited from DB package. If no arguments are passed, 766*0Sstevel@tonic-gatereturns the prestop action string. 767*0Sstevel@tonic-gate 768*0Sstevel@tonic-gate=item CLIENT->stop() 769*0Sstevel@tonic-gate 770*0Sstevel@tonic-gateCalled when execution stops (w/ args file, line). 771*0Sstevel@tonic-gate 772*0Sstevel@tonic-gate=item CLIENT->idle() 773*0Sstevel@tonic-gate 774*0Sstevel@tonic-gateCalled while stopped (can be a client event loop). 775*0Sstevel@tonic-gate 776*0Sstevel@tonic-gate=item CLIENT->poststop([STRING]) 777*0Sstevel@tonic-gate 778*0Sstevel@tonic-gateUsually inherited from DB package. If no arguments are passed, 779*0Sstevel@tonic-gatereturns the poststop action string. 780*0Sstevel@tonic-gate 781*0Sstevel@tonic-gate=item CLIENT->evalcode(STRING) 782*0Sstevel@tonic-gate 783*0Sstevel@tonic-gateUsually inherited from DB package. Ask for a STRING to be C<eval>-ed 784*0Sstevel@tonic-gatein executing code context. 785*0Sstevel@tonic-gate 786*0Sstevel@tonic-gate=item CLIENT->cleanup() 787*0Sstevel@tonic-gate 788*0Sstevel@tonic-gateCalled just before exit. 789*0Sstevel@tonic-gate 790*0Sstevel@tonic-gate=item CLIENT->output(LIST) 791*0Sstevel@tonic-gate 792*0Sstevel@tonic-gateCalled when API must show a message (warnings, errors etc.). 793*0Sstevel@tonic-gate 794*0Sstevel@tonic-gate 795*0Sstevel@tonic-gate=back 796*0Sstevel@tonic-gate 797*0Sstevel@tonic-gate 798*0Sstevel@tonic-gate=head1 BUGS 799*0Sstevel@tonic-gate 800*0Sstevel@tonic-gateThe interface defined by this module is missing some of the later additions 801*0Sstevel@tonic-gateto perl's debugging functionality. As such, this interface should be considered 802*0Sstevel@tonic-gatehighly experimental and subject to change. 803*0Sstevel@tonic-gate 804*0Sstevel@tonic-gate=head1 AUTHOR 805*0Sstevel@tonic-gate 806*0Sstevel@tonic-gateGurusamy Sarathy gsar@activestate.com 807*0Sstevel@tonic-gate 808*0Sstevel@tonic-gateThis code heavily adapted from an early version of perl5db.pl attributable 809*0Sstevel@tonic-gateto Larry Wall and the Perl Porters. 810*0Sstevel@tonic-gate 811*0Sstevel@tonic-gate=cut 812