package DB;
use strict;
use warnings;
use IO::Socket::INET;
use String::Koremutake;
use YAML::Syck;
use Module::Pluggable
search_path => 'Devel::ebug::Backend::Plugin',
require => 1;
our $VERSION = "0.48";
use vars qw(@dbline %dbline);
# Let's catch INT signals and set a flag when they occur
$SIG{INT} = sub {
$DB::signal = 1;
return;
};
my $context = {
finished => 0,
initialise => 1,
mode => "step",
stack => [],
watch_points => [],
};
# Commands that the back end can respond to
# Set record if the command changes start and should thus be recorded
# in order for undo to work properly
my %commands = ();
sub DB {
my ($package, $filename, $line) = caller;
($context->{package}, $context->{filename}, $context->{line}) =
($package, $filename, $line);
initialise() if $context->{initialise};
# we're here because of a signal, reset the flag
if ($DB::signal) {
$DB::signal = 0;
}
# single step
my $old_single = $DB::single;
$DB::single = 1;
if (@{ $context->{watch_points} }) {
my %delete;
foreach my $watch_point (@{ $context->{watch_points} }) {
local $SIG{__WARN__} = sub { };
my $v = eval "package $package; $watch_point";
if ($v) {
$context->{watch_single} = 1;
$delete{$watch_point} = 1;
}
}
if ($context->{watch_single} == 0) {
return;
} else {
@{ $context->{watch_points} } =
grep { !$delete{$_} } @{ $context->{watch_points} };
}
}
# we're here because of a break point, test the condition
if ($old_single == 0) {
my $condition = break_point_condition($filename, $line);
if ($condition) {
local $SIG{__WARN__} = sub { };
my $v = eval "package $package; $condition";
unless ($v) {
# condition not true, go back to running
$DB::single = 0;
return;
}
}
}
$context->{watch_single} = 1;
$context->{codeline} = (fetch_codelines($filename, $line - 1))[0];
chomp $context->{codeline};
while (1) {
my $req = get();
my $command = $req->{command};
my $sub = $commands{$command}->{sub};
if (defined $sub) {
put($sub->($req, $context));
if ($context->{last}) {
delete $context->{last};
last;
}
} else {
die "unknown command $command";
}
}
}
sub initialise {
my $k = String::Koremutake->new;
my $int = $k->koremutake_to_integer($ENV{SECRET});
my $port = 3141 + ($int % 1024);
my $server = IO::Socket::INET->new(
Listen => 5,
LocalAddr => 'localhost',
LocalPort => $port,
Proto => 'tcp',
ReuseAddr => 1,
Reuse => 1,
)
|| die $!;
$context->{socket} = $server->accept;
foreach my $plugin (__PACKAGE__->plugins) {
my $sub = $plugin->can("register_commands");
next unless $sub;
my %new = &$sub;
foreach my $command (keys %new) {
$commands{$command} = $new{$command};
}
}
$context->{initialise} = 0;
}
sub put {
my ($res) = @_;
my $data = unpack("h*", Dump($res));
$context->{socket}->print($data . "\n");
}
sub get {
exit unless $context->{socket};
my $data = $context->{socket}->getline;
my $req = Load(pack("h*", $data));
push @{ $context->{history} }, $req
if exists $commands{ $req->{command} }->{record};
return $req;
}
sub sub {
my (@args) = @_;
my $sub = $DB::sub;
my $frame = { single => $DB::single, sub => $sub };
push @{ $context->{stack} }, $frame;
$DB::single = 0 if defined $context->{mode} && $context->{mode} eq 'next';
no strict 'refs';
if (wantarray) {
my @ret = &$sub;
my $frame = pop @{ $context->{stack} };
$DB::single = $frame->{single};
$DB::single = 0 if defined $context->{mode} && $context->{mode} eq 'run' && !@{$context->{watch_points}};
if ($frame->{return}) {
return @{ $frame->{return} };
} else {
return @ret;
}
} else {
my $ret = &$sub;
my $frame = pop @{ $context->{stack} };
$DB::single = $frame->{single};
$DB::single = 0 if defined $context->{mode} && $context->{mode} eq 'run' && !@{$context->{watch_points}};
if ($frame->{return}) {
return $frame->{return}->[0];
} else {
return $ret;
}
}
}
sub fetch_codelines {
my ($filename, @lines) = @_;
#use vars qw(@dbline %dbline);
*dbline = $main::{ '_<' . $filename };
my @codelines = @dbline;
# for modules, not sure why
shift @codelines if not defined $codelines[0];
# defined!
@codelines = map { defined($_) ? $_ : "" } @codelines;
# remove newlines
@codelines = map { $_ =~ s/\s+$//; $_ } @codelines;
# we run it with -d:ebug::Backend, so remove this extra line
@codelines = grep { $_ ne 'use Devel::ebug::Backend;' } @codelines;
# for some reasons, the perl internals leave the opening POD line
# around but strip the rest. so let's strip the opening POD line
@codelines =
map { $_ =~ /^=(head|over|item|back|over|cut|pod|begin|end|for)/ ? "" : $_ }
@codelines;
if (@lines) {
@codelines = @codelines[@lines];
}
return @codelines;
}
sub break_point_condition {
my ($filename, $line) = @_;
*dbline = $main::{ '_<' . $filename };
return $dbline{$line};
}
sub END {
$context->{finished} = 1;
$DB::single = 1;
DB::fake::at_exit();
}
package DB::fake;
sub at_exit {
1;
}
package DB; # Do not trace this 1; below!
1;
syntax highlighted by Code2HTML, v. 0.9.1