diff --git a/modules/compiler_vm/README b/modules/compiler_vm/README index 7048ef47..ed2a61bd 100644 --- a/modules/compiler_vm/README +++ b/modules/compiler_vm/README @@ -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. diff --git a/modules/compiler_vm/compiler_vm_client.pl b/modules/compiler_vm/compiler_vm_client.pl index ee595348..bca471ca 100755 --- a/modules/compiler_vm/compiler_vm_client.pl +++ b/modules/compiler_vm/compiler_vm_client.pl @@ -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 \n#include \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; diff --git a/modules/compiler_vm/compiler_vm_server.pl b/modules/compiler_vm/compiler_vm_server.pl index c11a0205..231b2f16 100755 --- a/modules/compiler_vm/compiler_vm_server.pl +++ b/modules/compiler_vm/compiler_vm_server.pl @@ -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; diff --git a/modules/compiler_vm/compiler_watchdog.pl b/modules/compiler_vm/compiler_watchdog.pl index 0d49a3fb..8012bb9c 100755 --- a/modules/compiler_vm/compiler_watchdog.pl +++ b/modules/compiler_vm/compiler_watchdog.pl @@ -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; }