3
0
mirror of https://github.com/pragma-/pbot.git synced 2024-12-23 03:02:47 +01:00

compiler_vm: added functionality to be executed without qemu, via cc script; added gdb debugging to watchdog

This commit is contained in:
Pragmatic Software 2011-02-01 00:41:51 +00:00
parent cd8791a854
commit 0b8216e13c
4 changed files with 123 additions and 44 deletions

View File

@ -1,22 +1,49 @@
Installation:
If you want to run the compiler inside a virtual machine for more security,
these scripts are designed to work with qemu 0.11.1. This is required in
order to use PBot's trigger.
In addition, you can use the provided 'cc' script to use your local compiler tools
without installing qemu. No PBot installation or configuration is required in this case.
To use the local non-vm 'cc' script, you will need to have gcc, gdb and astyle installed locally.
Be aware that you need to single-quote or escape the code if you use the local 'cc' within a shell,
e.g.: ./cc 'char s[] = "hello, world"; puts(s); if(s[0] == \'h\') puts("true");'
WARNING: Using the local 'cc' script outside of the virtual machine will not use qemu at all;
it will affect local system -- compile "safe" code!
Virtual machine installation:
You will need to download qemu and set up a virtual machine containing a system
with a compiler and, optionally, sensible ulimits/fork-preventation/other security.
Then you will need to edit some of the following files and replace IP addresses
and paths where appropriate (some locations may not yet be documented).
1) copy compiler_vm_server.pl and compiler_watchdog.pl to the virtual machine.
2) then start up the compiler_vm_server.pl script inside the virtual machine
3) then connect to qemu's monitor and issue the 'savevm 1' command to save the virtual state
(After compiles, this state will be loaded via 'loadvm 1' to reset everything within the machine
for a clean and working environment for subsequent compiles.)
Once the files have been configured and copied to the appropriate locations,
you will need to first start up the compiler_vm_server.pl script, and then
connect to qemu's monitor and issue the 'savevm 1' command to save the virtual
machine in this state. Between compiles, this state will be loaded via
'loadvm 1' in order to reset everything within the machine for a clean and working
environment for subsequent compiles.
Now the virtual machine state 1 is saved in a state where it is listening for incoming code. You can
go ahead and quit qemu without shutting down the guest operating sytem.
Starting the virtual machine for PBot:
Now that the virtual machine is configured and saved, you may launch the local server to listen for
code from PBot to send to the virtual machine's server. To do so, run the compiler_server.pl script.
Files:
(Read within each file for configuration instructions.)
- cc: Allows you to use the compiler locally with or without qemu installed.
Can be used within virtual machine for testing.
Must have gcc, gdb and astyle installed locally if not used within virtual machine.
WARNING: If not used within virtual machine will not use qemu at all and will
affect local system -- compile "safe" code!
- compiler_client.pl: Main entry point for compiling snippets. Sends over TCP to
compiler_server.pl. This file can be run be run from the
client machine or anywhere.

View File

@ -9,6 +9,7 @@ use Text::Balanced qw(extract_codeblock extract_delimited);
use IO::Socket;
use LWP::UserAgent;
my $USE_LOCAL = defined $ENV{'CC_LOCAL'};
my $MAX_UNDO_HISTORY = 100;
my $output = "";
@ -28,19 +29,6 @@ my %preludes = (
'C++' => "#include <iostream>\n#include <cstdio>\n\nusing namespace std;\n\n",
);
sub reset_vm {
my $sock = IO::Socket::INET->new(PeerAddr => '127.0.0.1', PeerPort => '4445', Proto => 'tcp', Type => 'SOCK_STREAM');
die "Could not create socket: $!" unless $sock;
$sock->autoflush();
print $sock "loadvm 2\r\n";
while(my $line = <$sock>) {
last if $line =~ /loadvm 2/;
}
sleep 2;
$sock->shutdown(1);
}
sub pretty {
my $code = join '', @_;
my $result;
@ -76,20 +64,28 @@ sub paste_codepad {
}
sub compile {
my ($lang, $code, $args, $input) = @_;
my ($lang, $code, $args, $input, $local) = @_;
my $sock = IO::Socket::INET->new(PeerAddr => '127.0.0.1', PeerPort => '4444', Proto => 'tcp', Type => 'SOCK_STREAM');
my ($compiler, $compiler_output, $pid);
if(defined $local and $local != 0) {
print "Using local compiler instead of virtual machine\n";
$pid = open2($compiler_output, $compiler, './compiler_vm_server.pl') || die "repl failed: $@\n";
print "Started compiler, pid: $pid\n";
} else {
$compiler = IO::Socket::INET->new(PeerAddr => '127.0.0.1', PeerPort => '4444', Proto => 'tcp', Type => 'SOCK_STREAM');
die "Could not create socket: $!" unless $compiler;
$compiler_output = $compiler;
}
die "Could not create socket: $!" unless $sock;
print $sock "compile:$lang:$args:$input\n";
print $sock "$code\n";
print $sock "compile:end\n";
print $compiler "compile:$lang:$args:$input\n";
print $compiler "$code\n";
print $compiler "compile:end\n";
my $result = "";
my $got_result = 0;
while(my $line = <$sock>) {
while(my $line = <$compiler_output>) {
$line =~ s/[\r\n]+$//;
last if $line =~ /^result:end/;
@ -106,7 +102,9 @@ sub compile {
}
}
close $sock;
close $compiler;
close $output if defined $output;
waitpid($pid, 0) if defined $pid;
return $result;
}
@ -640,7 +638,7 @@ if(defined $got_run and $got_run eq "paste") {
print FILE "$nick: [lang:$lang][args:$args][input:$input]\n$code\n";
$output = compile($lang, $code, $args, $input);
$output = compile($lang, pretty($code), $args, $input, $USE_LOCAL);
$output =~ s/cc1: warnings being treated as errors//;
$output =~ s/ Line \d+ ://g;
@ -653,12 +651,16 @@ $output =~ s/\/tmp\/.*\.o://g;
$output =~ s/collect2: ld returned \d+ exit status//g;
$output =~ s/\(\.text\+[^)]+\)://g;
$output =~ s/\[ In/[In/;
$output =~ s/warning: Can't read pathname for load map: Input.output error.//g;
my $left_quote = chr(226) . chr(128) . chr(152);
my $right_quote = chr(226) . chr(128) . chr(153);
$output =~ s/$left_quote/'/g;
$output =~ s/$right_quote/'/g;
$output =~ s/[\r\n]+/ /g;
$output =~ s/\s+/ /g;
$output = $nooutput if $output =~ m/^\s+$/;
unless($got_run) {
@ -668,5 +670,3 @@ unless($got_run) {
}
print "$nick: $output\n";
#reset_vm;

View File

@ -3,27 +3,36 @@
use warnings;
use strict;
my $USE_LOCAL = defined $ENV{'CC_LOCAL'};
my %languages = (
'C' => {
'cmdline' => 'gcc $args $file -o prog',
'cmdline' => 'gcc $args $file -o prog -ggdb',
'args' => '-Wextra -Wall -Wno-unused -std=gnu89',
'file' => 'prog.c',
},
'C++' => {
'cmdline' => 'g++ $args $file -o prog',
'cmdline' => 'g++ $args $file -o prog -ggdb',
'args' => '',
'file' => 'prog.cpp',
},
'C99' => {
'cmdline' => 'gcc $args $file -o prog',
'args' => '-Wextra -Wall -Wno-unused -pedantic -std=c99',
'cmdline' => 'gcc $args $file -o prog -ggdb',
'args' => '-Wextra -Wall -Wno-unused -pedantic -std=c99 -lm',
'file' => 'prog.c',
},
);
sub runserver {
open(my $input, '<', "/dev/ttyS0") or die $!;
open(my $output, '>', "/dev/ttyS0") or die $!;
my ($input, $output);
if(not defined $USE_LOCAL or $USE_LOCAL == 0) {
open($input, '<', "/dev/ttyS0") or die $!;
open($output, '>', "/dev/ttyS0") or die $!;
} else {
open($input, '<', "/dev/stdin") or die $!;
open($output, '>', "/dev/stdout") or die $!;
}
my $lang;
my $code;
@ -49,8 +58,12 @@ sub runserver {
print $output "result:$result\n";
print $output "result:end\n";
print "input: ";
next;
if(not defined $USE_LOCAL or $USE_LOCAL == 0) {
print "input: ";
next;
} else {
exit;
}
}
if($line =~ m/^compile:\s*(.*)/) {
@ -107,14 +120,18 @@ sub interpret {
print "Executing [$cmdline]\n";
my ($ret, $result) = execute(60, $cmdline);
print "Got result: ($ret) [$result]\n";
# print "Got result: ($ret) [$result]\n";
# if exit code was not 0, then there was a problem compiling, such as an error diagnostic
# so return the compiler output
if($ret != 0) {
return $result;
}
my $output = "";
# no errors compiling, but if $result contains something, it must be a warning message
# so prepend it to the output
if(length $result) {
$result =~ s/^\s+//;
$result =~ s/\s+$//;
@ -123,11 +140,11 @@ sub interpret {
($ret, $result) = execute(5, "./compiler_watchdog.pl");
print "Executed prog; got result: ($ret) [$result]\n";
$result =~ s/^\s+//;
$result =~ s/\s+$//;
# print "Executed prog; got result: ($ret) [$result]\n";
if(not length $result) {
$result = "Success (no output).\n" if $ret == 0;
$result = "Success (exit code $ret).\n" if $ret != 0;

View File

@ -4,6 +4,7 @@ use warnings;
use strict;
use POSIX ":sys_wait_h";
use IPC::Open2;
my @signame;
$signame[0] = 'SIGZERO';
@ -76,6 +77,39 @@ $signame[66] = 'SIGCLD';
$signame[67] = 'SIGPOLL';
$signame[68] = 'SIGUNUSED';
sub debug_program {
my ($input, $output);
my $pid = open2($output, $input, 'gdb -silent -batch -x debugcommands ./prog ./core 2>/dev/null');
if(not $pid) {
print "Error debugging program.\n";
exit;
}
my $result = "";
while(my $line = <$output>) {
if($line =~ s/^#\d+//) {
$line =~ s/\s*0x[0-9a-fA-F]+\s*//;
$result .= "$line ";
}
elsif($line =~ s/^\d+//) {
$result .= "statement: $line";
last;
}
}
close $output;
close $input;
waitpid($pid, 0);
$result =~ s/^\s+//;
$result =~ s/\s+$//;
print "$result\n";
exit;
}
sub reaper {
my $child;
while (($child=waitpid(-1,WNOHANG))>0) {
@ -92,6 +126,7 @@ sub reaper {
if($wifsignaled == 1) {
print "\nProgram received signal $wtermsig ($signame[$wtermsig])\n";
debug_program if $wtermsig != 0;
exit;
}