Add "-k" option to pintos script. When used, pintos will scan the
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 27 May 2006 20:21:09 +0000 (20:21 +0000)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 27 May 2006 20:21:09 +0000 (20:21 +0000)
serial output for key words like "Kernel PANIC" and, if found,
terminate the run.  This is useful for making sure that things like
triple faults or recursive panics don't waste a lot of time by finally
terminating only upon a timeout that might be as much as 10 minutes.
Add "-k" to the command line used in the test scripts.

Also, pintos now saves and restores the terminal settings across the
call to the simulator.  This is because qemu turns off local echo and
doesn't restore it if killed by a signal.

TODO
src/tests/Make.tests
src/tests/filesys/extended/Make.tests
src/utils/pintos

diff --git a/TODO b/TODO
index 463cb88457023fa1ab82af25120f68cbc3dfbc48..a1dd761d8089f4743efe994ca3c801919a515712 100644 (file)
--- a/TODO
+++ b/TODO
@@ -7,10 +7,6 @@
 * Add serial input support.  Also, modify tests to redirect input from
   /dev/null, to avoid stray keystrokes getting sent into the VM.
 
-* Make pintos script read the serial output and kill the subprocess if
-  it panics (after waiting a few seconds) or triple-faults.  Might
-  want it to be optional, so that interactive users don't get killed.
-
 * Godmar: Introduce memory leak robustness tests - both for the
   well-behaved as well as the mis-behaved case - that tests that the
   kernel handles low-mem conditions well.
index b98a75d33e65d460a565373d7682865551c7b448..230e03af729c081789301318afaf3d5cbf4307d0 100644 (file)
@@ -47,7 +47,7 @@ $(foreach prog,$(PROGS),$(eval $(prog).output: $(prog)))
 $(foreach test,$(TESTS),$(eval $(test).output: $($(test)_PUTFILES)))
 $(foreach test,$(TESTS),$(eval $(test).output: TEST = $(test)))
 
-TESTCMD = pintos -v -T $(TIMEOUT)
+TESTCMD = pintos -v -k -T $(TIMEOUT)
 TESTCMD += $(SIMULATOR)
 TESTCMD += $(PINTOSOPTS)
 ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog)
index 1aeb8dd1acb68ba0071d6580b1b38f3d88662fd1..2ba4d76e5d1e2a5005512e45bc67518f2283bc93 100644 (file)
@@ -27,7 +27,7 @@ tests/filesys/extended/syn-rw_PUTFILES += tests/filesys/extended/child-syn-rw
 
 GETTIMEOUT = 60
 
-GETCMD = pintos -v -T $(GETTIMEOUT)
+GETCMD = pintos -v -k -T $(GETTIMEOUT)
 GETCMD += $(PINTOSOPTS)
 GETCMD += $(SIMULATOR)
 GETCMD += --fs-disk=$(FSDISK)
index f0ab60c25d717c424a48dda0cc3e29cd79328083..856ebfa473ac270c70858547aabef498d7360f8d 100755 (executable)
@@ -16,6 +16,7 @@ our ($vga);                   # VGA output: window, terminal, or none.
 our ($jitter);                 # Seed for random timer interrupts, if set.
 our ($realtime);               # Synchronize timer interrupts with real time?
 our ($timeout);                        # Maximum runtime in seconds, if set.
+our ($kill_on_failure);                # Abort quickly on test failure?
 our (@puts);                   # Files to copy into the VM.
 our (@gets);                   # Files to copy out of the VM.
 our ($as_ref);                 # Reference to last addition to @gets or @puts.
@@ -58,7 +59,9 @@ sub parse_command_line {
                    "m|memory=i" => \$mem,
                    "j|jitter=i" => sub { set_jitter (@_) },
                    "r|realtime" => sub { set_realtime () },
+
                    "T|timeout=i" => \$timeout,
+                   "k|kill-on-failure" => \$kill_on_failure,
 
                    "v|no-vga" => sub { set_vga ('none'); },
                    "s|no-serial" => sub { $serial_out = 0; },
@@ -86,8 +89,11 @@ sub parse_command_line {
     $debug = "none" if !defined $debug;
     $vga = "window" if !defined $vga;
 
-    print "warning: -T or --timeout should not be used with --$debug\n"
+    undef $timeout, print "warning: disabling timeout with --$debug\n"
       if defined ($timeout) && $debug ne 'none';
+
+    print "warning: enabling serial output for -k or --kill-on-failure\n"
+      if $kill_on_failure && !$serial_out;
 }
 
 # usage($exitcode).
@@ -115,8 +121,11 @@ Display options: (default is both VGA and serial)
 Timing options: (Bochs only)
   -j SEED                  Randomize timer interrupts
   -r, --realtime           Use realistic, not reproducible, timings
+Testing options:
   -T, --timeout=N          Kill Pintos after N seconds CPU time or N*load_avg
                            seconds wall-clock time (whichever comes first)
+  -k, --kill-on-failure    Kill Pintos a few seconds after a kernel or user
+                           panic, test failure, or triple fault
 Configuration options:
   -m, --mem=N              Give Pintos N MB physical RAM (default: 4)
 File system commands (for `run' command):
@@ -471,6 +480,7 @@ sub run_gsx {
     gsx_unsup ("--no-vga") if $vga eq 'none';
     gsx_unsup ("--terminal") if $vga eq 'terminal';
     gsx_unsup ("--jitter") if defined $jitter;
+    gsx_unsup ("--kill-on-failure") if defined $kill_on_failure;
 
     unlink ("pintos.out");
 
@@ -656,21 +666,69 @@ sub run_command {
 # Relays common signals to the subprocess.
 # If $timeout is set then the subprocess will be killed after that long.
 sub xsystem {
+    # qemu turns off local echo and does not restore it if killed by a signal.
+    # We compensate by restoring it ourselves.
+    my $cleanup = sub {};
+    if (isatty (0)) {
+       my $termios = POSIX::Termios->new;
+       $termios->getattr (0);
+       $cleanup = sub { $termios->setattr (0, &POSIX::TCSANOW); }
+    }
+
+    # Create pipe for filtering output.
+    pipe (my $in, my $out) or die "pipe: $!\n" if $kill_on_failure;
+
     my ($pid) = fork;
     if (!defined ($pid)) {
        # Fork failed.
        die "fork: $!\n";
     } elsif (!$pid) {
        # Running in child process.
+       dup2 (fileno ($out), STDOUT_FILENO) or die "dup2: $!\n"
+         if $kill_on_failure;
        exec_setitimer (@_);
     } else {
        # Running in parent process.
-       local $SIG{ALRM} = sub { timeout ($pid); };
-       local $SIG{INT} = sub { relay_signal ($pid, "INT"); };
-       local $SIG{TERM} = sub { relay_signal ($pid, "TERM"); };
+       close $out if $kill_on_failure;
+
+       my ($cause);
+       local $SIG{ALRM} = sub { timeout ($pid, $cause, $cleanup); };
+       local $SIG{INT} = sub { relay_signal ($pid, "INT", $cleanup); };
+       local $SIG{TERM} = sub { relay_signal ($pid, "TERM", $cleanup); };
        alarm ($timeout * get_load_average () + 1) if defined ($timeout);
-       waitpid ($pid, 0);
+
+       if ($kill_on_failure) {
+           # Filter output.
+           my ($buf) = "";
+           my ($boots) = 0;
+           while (waitpid ($pid, WNOHANG) == 0) {
+               # Read and print out pipe data.
+               my ($len) = length ($buf);
+               waitpid ($pid, 0), last
+                 if sysread ($in, $buf, 4096, $len) <= 0;
+               print STDOUT substr ($buf, $len);
+
+               # Remove full lines from $buf and scan them for keywords.
+               while ((my $idx = index ($buf, "\n")) >= 0) {
+                   local $_ = substr ($buf, 0, $idx + 1, '');
+                   next if defined ($cause);
+                   if (/(Kernel PANIC|User process ABORT)/ ) {
+                       $cause = "\L$1\E";
+                       alarm (5);
+                   } elsif (/Pintos booting/ && ++$boots > 1) {
+                       $cause = "triple fault";
+                       alarm (5);
+                   } elsif (/FAILED/) {
+                       $cause = "test failure";
+                       alarm (5);
+                   }
+               }
+           }
+       } else {
+           waitpid ($pid, 0);
+       }
        alarm (0);
+       &$cleanup ();
 
        if (WIFSIGNALED ($?) && WTERMSIG ($?) == SIGVTALRM ()) {
            seek (STDOUT, 0, 2);
@@ -682,31 +740,38 @@ sub xsystem {
     }
 }
 
-# relay_signal($pid, $signal)
+# relay_signal($pid, $signal, &$cleanup)
 #
 # Relays $signal to $pid and then reinvokes it for us with the default
-# handler.  Also cleans up temporary files.
+# handler.  Also cleans up temporary files and invokes $cleanup.
 sub relay_signal {
-    my ($pid, $signal) = @_;
+    my ($pid, $signal, $cleanup) = @_;
     kill $signal, $pid;
     File::Temp::cleanup();
+    &$cleanup ();
     $SIG{$signal} = 'DEFAULT';
     kill $signal, getpid ();
 }
 
-# timeout($pid)
+# timeout($pid, $cause, &$cleanup)
 #
-# Interrupts $pid and dies with a timeout error message.
+# Interrupts $pid and dies with a timeout error message,
+# after invoking $cleanup.
 sub timeout {
-    my ($pid) = @_;
+    my ($pid, $cause, $cleanup) = @_;
     kill "INT", $pid;
     waitpid ($pid, 0);
+    &$cleanup ();
     seek (STDOUT, 0, 2);
-    my ($load_avg) = `uptime` =~ /(load average:.*)$/i;
-    print "\nTIMEOUT after ", time () - $start_time,
-      " seconds of wall-clock time";
-    print  " - $load_avg" if defined $load_avg;
-    print "\n";
+    if (!defined ($cause)) {
+       my ($load_avg) = `uptime` =~ /(load average:.*)$/i;
+       print "\nTIMEOUT after ", time () - $start_time,
+         " seconds of wall-clock time";
+       print  " - $load_avg" if defined $load_avg;
+       print "\n";
+    } else {
+       print "Simulation terminated due to $cause.\n";
+    }
     exit 0;
 }