3
0
mirror of https://github.com/pragma-/pbot.git synced 2024-11-02 18:19:33 +01:00
pbot/modules/compiler_vm/compiler_watchdog.pl
Pragmatic Software 38d109059a Improvements to watchdog
Expand escape sequences
Ignore various text related to no symbol table and to threads
Automatically continue on breakpoints for functions prefixed with underscores (mostly because of C++)
2015-04-09 11:02:20 -07:00

513 lines
16 KiB
Perl
Executable File

#!/usr/bin/perl
use warnings;
use strict;
use IPC::Open2;
my $stdin_input = (join ' ', @ARGV) || "Lorem ipsum dolor sit amet.\n";
$stdin_input =~ s/\\n/\n/g;
$stdin_input =~ s/\\r/\r/g;
$stdin_input =~ s/\\t/\t/g;
open my $fh, '>', '.input' or die "Couldn't open .input: $!";
print $fh $stdin_input;
close $fh;
my $debug = 0;
#my $opening = "<";
#my $closing = ">\n";
my $opening = "\n";
my $closing = "\n\n";
my $watching = 0;
my $got_output = 0;
my $local_vars = "";
sub flushall;
sub gdb;
sub execute {
my ($cmdline) = @_;
my ($ret, $result);
my ($out, $in);
open2($out, $in, "$cmdline 2>&1");
while(my $line = <$out>) {
chomp $line;
print "--- got: [$line]\n" if $debug >= 1;
my $ignore_response = 0;
next if not length $line;
<$out> and next if $line =~ m/^\(gdb\) No line \d+ in/;
next if $line =~ m/^\(gdb\) No symbol table/;
next if $line =~ m/^\[New Thread/;
next if $line =~ m/^\(gdb\) Continuing/;
next if $line =~ m/^\(gdb\) \$\d+ = "Ok\."/;
next if $line =~ m/^(\(gdb\) )*Breakpoint \d+ at 0x/;
next if $line =~ m/^\(gdb\) Breakpoint \d+ at 0x/;
next if $line =~ m/^\(gdb\) Note: breakpoint \d+ also set/;
next if $line =~ m/^(\(gdb\) )*Starting program/;
next if $line =~ m/PRETTY_FUNCTION__ =/;
next if $line =~ m/libc_start_main/;
next if $line =~ m/Thread debugging using libthread_db enabled/;
next if $line =~ m/Using host libthread_db library/;
if($line =~ m/^\d+: (.*? = .*)/) {
print "$opening$1$closing";
$got_output = 1;
next;
}
if($line =~ m/^Reading symbols from.*done\.$/) {
gdb $in, "break gdb\n";
gdb $in, "list main,9001\n";
gdb $in, "print \"Ok.\"\n";
my $break = 0;
my $bracket = 0;
my $main_ended = 0;
while(my $line = <$out>) {
chomp $line;
print "list got: [$line]\n" if $debug >= 4;
my ($line_number) = $line =~ m/^(\d+)/g;
while($line =~ m/(.)/g) {
my $char = $1;
if($char eq '{') {
$bracket++;
} elsif($char eq '}') {
$bracket--;
if($bracket == 0 and not $main_ended) {
$break = $line_number - 1;
$main_ended = 1;
last;
}
}
}
last if $line =~ m/^\(gdb\) \$\d+ = "Ok."/;
}
gdb $in, "break $break\n";
gdb $in, "set width 0\n";
gdb $in, "set height 0\n";
gdb $in, "run < .input\n";
next;
}
if($line =~ m/^Breakpoint \d+, main/) {
my $line = <$out>;
print "== got: $line\n" if $debug >= 5;
if($line =~ m/^\d+\s+return.*?;\s*$/ or $line =~ m/^\d+\s+}\s*$/) {
if($got_output == 0) {
print "no output, checking locals\n" if $debug >= 5;
gdb $in, "print \"Go.\"\ninfo locals\nprint \"Ok.\"\n";
while(my $peep = <$out>) {
chomp $peep;
last if $peep =~ m/\(gdb\) \$\d+ = "Go."/;
# fix this
$peep =~ s/^\d+: (.*?) =/$1 =/;
print "$opening$peep$closing";
$got_output = 1;
}
my $result = "";
my $vars = "";
my $varsep = "";
while(my $line = <$out>) {
chomp $line;
print "got: [$line]\n" if $debug >= 5;
last if $line =~ m/\(gdb\) \$\d+ = "Ok."/;
if($line =~ m/([^=]+)=\s+(.*)/) {
$vars .= "$varsep$1= $2";
$varsep = "; ";
}
}
$result =~ s/^\s+//;
$result =~ s/\s+$//;
$vars =~ s/\(gdb\)\s*//g;
$local_vars = $opening . "no output: $vars$closing" if length $vars;
}
}
gdb $in, "cont\n";
next;
}
if($line =~ m/Breakpoint \d+, gdb/) {
gdb $in, "up\n";
$line = <$out>;
print "ignored $line\n" if $debug >= 2;
$line = <$out>;
print "ignored $line\n" if $debug >= 2;
next;
}
if($line =~ m/^Breakpoint \d+, _(.*?) at/) {
<$out>;
gdb $in, "cont\n";
next;
}
if($line =~ m/^Breakpoint \d+, (.*?) at/) {
my $func = $1;
my $direction = "entered";
my $return_value = "";
my $nextline = <$out>;
chomp $nextline;
print "got bt nextline: <$nextline>\n" if $debug >= 5;
if($nextline =~ m/^\d+\s+}$/) {
$direction = "leaving";
gdb $in, "finish\n";
while(my $retval = <$out>) {
chomp $retval;
print "got retval line: <$retval>\n" if $debug >= 5;
$retval =~ s/^\(gdb\)\s+//;
if($retval =~ m/^Run till exit/) {
<$out>;
<$out>;
next;
}
if($retval =~ m/Value returned is \$\d+ = (.*)/) {
$return_value = ", returned $1";
last;
}
next if not length $retval;
next if $retval =~ m/^\$\d+ = 0/;
print "$retval\n";
$got_output = 1;
}
}
flushall $in, $out;
my $indent = 0;
gdb $in, "bt\n";
while(my $bt = <$out>) {
chomp $bt;
print "got bt: <$bt>\n" if $debug >= 5;
$bt =~ s/^\(gdb\) //;
if($bt =~ m/^#(\d+) .* main .* at prog/) {
$indent = $1;
last;
}
}
$indent++ if $direction eq "leaving";
print "$opening$direction [$indent]", ' ' x $indent, "$func$return_value$closing";
gdb $in, "cont\n";
next;
}
if($line =~ m/^\d+\s+.*\btrace\((.*)\)/) {
$line = "1 gdb(\"break $1\");";
}
if($line =~ m/^\d+\s+.*\bwatch\((.*)\)/) {
$line = "1 gdb(\"watch $1\");";
}
if($line =~ m/^\d+\s+.*\bdump\((.*)\)/) {
$line = "1 gdb(\"print $1\");";
}
if($line =~ m/^\d+\s+.*\bprint\((.*)\)/) {
$line = "1 gdb(\"print $1\");";
}
if($line =~ m/^\d+\s+.*\bptype\((.*)\)/) {
$line = "1 gdb(\"ptype $1\");";
}
if($line =~ m/^\d+\s+.*\bwhatis\((.*)\)/) {
$line = "1 gdb(\"whatis $1\");";
}
if($line =~ m/^\d+\s+.*\bgdb\("(.*)"\)/) {
my $command = $1;
my ($cmd, $args) = split / /, $command, 2;
$args = "" if not defined $args;
print "got command [$command]\n" if $debug >= 10;
flushall $in, $out;
if($cmd eq "break") {
$ignore_response = 1;
gdb $in, "list $args,9001\n";
gdb $in, "print \"Ok.\"\n";
my $break = 0;
my $bracket = 0;
my $func_ended = 0;
while(my $line = <$out>) {
chomp $line;
print "list break got: [$line]\n" if $debug >= 4;
my ($line_number) = $line =~ m/^(\d+)/g;
while($line =~ m/(.)/g) {
my $char = $1;
if($char eq '{') {
$bracket++;
} elsif($char eq '}') {
$bracket--;
if($bracket == 0 and not $func_ended) {
gdb $in, "break $line_number\n";
print "func ended, breaking at $line_number\n" if $debug >= 5;
$func_ended = 1;
last;
}
}
}
last if $line =~ m/^\(gdb\) \$\d+ = "Ok."/;
}
}
if($cmd eq "watch") {
gdb $in, "display $args\n";
<$out>;
$watching++;
$ignore_response = 1;
}
my $final_closing = "";
gdb $in, "$command\nprint \"Ok.\"\n";
while(my $next_line = <$out>) {
chomp $next_line;
print "nextline: $next_line\n" if $debug >= 1;
print $final_closing and last if $next_line =~ m/\$\d+ = "Ok."/;
$next_line =~ s/^\(gdb\)\s*\(gdb\)\s+\$\d+ = "Ok."//;
$next_line =~ s/^\(gdb\)\s+\$\d+//;
$next_line =~ s/^\(gdb\)\s+type//;
$next_line =~ s/^\(gdb\)\s*//;
next if not length $next_line;
if(not $ignore_response) {
if($next_line =~ m/=/) { # ptype
$got_output = 1;
print "$opening$args$next_line";
$final_closing = $closing;
} else {
$got_output = 1;
$next_line =~ s/^\s+//;
print "\n$next_line";
}
}
}
gdb $in, "cont\n";
next;
}
if($line =~ m/^Watchpoint \d+: (.*)/) {
my $var = $1;
my $ignore = <$out>;
print "ignored $ignore\n" if $debug >= 5;
my $old = <$out>;
my $new = <$out>;
$ignore = <$out>;
print "ignored $ignore\n" if $debug >= 5;
$ignore = <$out>;
print "ignored $ignore\n" if $debug >= 5;
my ($val1) = $old =~ m/Old value = (.*)/;
my ($val2) = $new =~ m/New value = (.*)/;
$got_output = 1;
print "$opening$var = $val2$closing";
gdb $in, "cont\n";
next;
}
if($line =~ m/^Hardware watchpoint \d+: (.*)/) {
my $var = $1;
my $ignore = <$out>;
my $old = <$out>;
my $new = <$out>;
$ignore = <$out>;
$ignore = <$out>;
my ($val1) = $old =~ m/Old value = (.*)/;
my ($val2) = $new =~ m/New value = (.*)/;
$got_output = 1;
my $output = "$opening$var changed: $val1 => $val2$closing";
flushall $in, $out;
print $output;
gdb $in, "cont\n";
next;
}
if($line =~ m/^Watchpoint \d+ deleted/) {
my $ignore = <$out>;
print "ignored $ignore\n" if $debug >= 5;
gdb $in, "cont\n";
next;
}
if($line =~ m/^Program exited/) {
print " $local_vars\n" if length $local_vars and not $got_output;
exit 0;
}
if($line =~ s/\[Inferior .* exited with code (\d+)\]//) {
print "$line\n" if length $line;
print $opening . "Exit " . (oct $1) . $closing;
print " $local_vars\n" if length $local_vars and not $got_output;
exit 0;
}
if($line =~ s/\[Inferior .* exited normally\]//) {
print "$line\n" if length $line;
$got_output = 1 if length $line;
print " $local_vars\n" if length $local_vars and not $got_output;
exit 0;
}
if($line =~ m/Program terminated with signal SIGKILL/) {
print "[Killed]\n";
return 0;
}
if($line =~ m/Program received signal SIGTRAP/) {
my $output = "";
my $line = <$out>;
print "ignored $line\n" if $debug >= 5;
$line = <$out>;
print "ignored $line\n" if $debug >= 5;
for(my $i = 0; $i < $watching; $i++) {
$line = <$out>;
chomp $line;
$line =~ s/^\d+:\s//;
$got_output = 1;
$output .= "$opening$line$closing";
}
flushall $in, $out;
print $output;
gdb $in, "cont\n";
next;
}
if($line =~ m/Program received signal/) {
my $result = "";
my $vars = "";
my $varsep = "";
$line =~ s/\.$//;
$got_output = 1;
print "$line ";
while(my $line = <$out>) {
chomp $line;
$line =~ s/^\(gdb\)\s+//;
$line =~ s/main \(.*?\)/main ()/g;
print "signal got: [$line]\n" if $debug >= 5;
next if $line =~ m/__PRETTY_FUNCTION__ =/;
if($line =~ s/^(#\d+\s+)?0x[0-9A-Fa-f]+\s//) {
$line =~ s/\s+at .*:\d+//;
$line =~ s/\s+from \/lib.*//;
if($line =~ s/^\s*in\s+//) {
if(not length $result) {
$result .= "in $line ";
} else {
$result .= "called by $line ";
}
gdb $in, "info locals\n";
} else {
$result = "in $line from ";
gdb $in, "info locals\n";
}
}
elsif($line =~ m/^No symbol table info available/) {
gdb $in, "up\n";
}
elsif($line =~ s/^\d+\s+//) {
next if $line =~ /No such file/;
$result .= "at statement: $line ";
gdb $in, "up\n";
}
elsif($line =~ m/([^=]+)=\s+(.*)/) {
$vars .= "$varsep$1= $2";
$varsep = "; ";
}
elsif($line =~ m/^Initial frame selected; you cannot go up/) {
last;
}
}
$result =~ s/^\s+//;
$result =~ s/\s+$//;
$result =~ s/in main \(\) //;
$vars = " <local variables: $vars>" if length $vars;
print "$result$vars\n";
exit 0;
}
if($line =~ s/^\(gdb\)\s*//) {
$got_output = 1;
print "$opening$line$closing";
next;
}
next if $line =~ m/^\d+\s+void gdb\(\) {}/;
next if not length $line;
$got_output = 1;
print "$line\n";
}
}
sub gdb {
my ($in, $command) = @_;
chomp $command;
print "+++ gdb command [$command]\n" if $debug >= 2;
print $in "$command\n";
}
sub flushall {
my ($in, $out) = @_;
gdb $in, "call fflush(0)\nprint \"Ok.\"\n";
while(my $line = <$out>) {
chomp $line;
$line =~ s/^\(gdb\)\s*//;
$line =~ s/\$\d+ = 0$//;
last if $line =~ m/\$\d+ = "Ok."/;
next unless length $line;
$got_output = 1;
print "$line\n";
}
}
execute("LIBC_FATAL_STDERR=1 MALLOC_CHECK_=1 gdb -silent -q -nx ./prog 2>&1");