#!/bin/sh verbose=false if [ "x$1" = "x-v" ]; then verbose=true out=/dev/stdout err=/dev/stderr else out=/dev/null err=/dev/null fi ## make & makeopts if gmake --version > /dev/null 2>&1; then make=gmake; else make=make; fi makeopts="--quiet --no-print-directory -j" make_print() { echo `$make $makeopts print-$1` } ## command tools awk='awk' bc='bc' date='date' grep='grep' rm='rm -f' sed='sed' ## symbol table sym_table='obj/kernel.sym' ## gdb & gdbopts gdb="$(make_print GDB)" gdbport='1234' gdb_in="$(make_print GRADE_GDB_IN)" ## qemu & qemuopts qemu="$(make_print qemu)" qemu_out="$(make_print GRADE_QEMU_OUT)" if $qemu -nographic -help | grep -q '^-gdb'; then qemugdb="-gdb tcp::$gdbport" else qemugdb="-s -p $gdbport" fi ## default variables default_timeout=30 default_pts=5 pts=5 part=0 part_pos=0 total=0 total_pos=0 ## default functions update_score() { total=`expr $total + $part` total_pos=`expr $total_pos + $part_pos` part=0 part_pos=0 } get_time() { echo `$date +%s.%N 2> /dev/null` } show_part() { echo "Part $1 Score: $part/$part_pos" echo update_score } show_final() { update_score echo "Total Score: $total/$total_pos" if [ $total -lt $total_pos ]; then exit 1 fi } show_time() { t1=$(get_time) time=`echo "scale=1; ($t1-$t0)/1" | $sed 's/.N/.0/g' | $bc 2> /dev/null` echo "(${time}s)" } show_build_tag() { echo "$1:" | $awk '{printf "%-24s ", $0}' } show_check_tag() { echo "$1:" | $awk '{printf " -%-40s ", $0}' } show_msg() { echo $1 shift if [ $# -gt 0 ]; then echo -e "$@" | awk '{printf " %s\n", $0}' echo fi } pass() { show_msg OK "$@" part=`expr $part + $pts` part_pos=`expr $part_pos + $pts` } fail() { show_msg WRONG "$@" part_pos=`expr $part_pos + $pts` } run_qemu() { # Run qemu with serial output redirected to $qemu_out. If $brkfun is non-empty, # wait until $brkfun is reached or $timeout expires, then kill QEMU qemuextra= if [ "$brkfun" ]; then qemuextra="-S $qemugdb" fi if [ -z "$timeout" ] || [ $timeout -le 0 ]; then timeout=$default_timeout; fi t0=$(get_time) ( ulimit -t $timeout exec $qemu -nographic $qemuopts -serial file:$qemu_out -monitor null -no-reboot $qemuextra ) > $out 2> $err & pid=$! # wait for QEMU to start sleep 1 if [ -n "$brkfun" ]; then # find the address of the kernel $brkfun function brkaddr=`$grep " $brkfun\$" $sym_table | $sed -e's/ .*$//g'` brkaddr_phys=`echo $brkaddr | sed "s/^c0/00/g"` ( echo "target remote localhost:$gdbport" echo "break *0x$brkaddr" if [ "$brkaddr" != "$brkaddr_phys" ]; then echo "break *0x$brkaddr_phys" fi echo "continue" ) > $gdb_in $gdb -batch -nx -x $gdb_in > /dev/null 2>&1 # make sure that QEMU is dead # on OS X, exiting gdb doesn't always exit qemu kill $pid > /dev/null 2>&1 fi } build_run() { # usage: build_run show_build_tag "$1" shift if $verbose; then echo "$make $@ ..." fi $make $makeopts $@ 'DEFS+=-DDEBUG_GRADE' > $out 2> $err if [ $? -ne 0 ]; then echo $make $@ failed exit 1 fi # now run qemu and save the output run_qemu show_time } check_result() { # usage: check_result show_check_tag "$1" shift # give qemu some time to run (for asynchronous mode) if [ ! -s $qemu_out ]; then sleep 4 fi if [ ! -s $qemu_out ]; then fail > /dev/null echo 'no $qemu_out' else check=$1 shift $check "$@" fi } check_regexps() { okay=yes not=0 reg=0 error= for i do if [ "x$i" = "x!" ]; then not=1 elif [ "x$i" = "x-" ]; then reg=1 else if [ $reg -ne 0 ]; then $grep '-E' "^$i\$" $qemu_out > /dev/null else $grep '-F' "$i" $qemu_out > /dev/null fi found=$(($? == 0)) if [ $found -eq $not ]; then if [ $found -eq 0 ]; then msg="!! error: missing '$i'" else msg="!! error: got unexpected line '$i'" fi okay=no if [ -z "$error" ]; then error="$msg" else error="$error\n$msg" fi fi not=0 reg=0 fi done if [ "$okay" = "yes" ]; then pass else fail "$error" if $verbose; then exit 1 fi fi } run_test() { # usage: run_test [-tag ] [-prog ] [-Ddef...] [-check ] checkargs ... tag= prog= check=check_regexps while true; do select= case $1 in -tag|-prog) select=`expr substr $1 2 ${#1}` eval $select='$2' ;; esac if [ -z "$select" ]; then break fi shift shift done defs= while expr "x$1" : "x-D.*" > /dev/null; do defs="DEFS+='$1' $defs" shift done if [ "x$1" = "x-check" ]; then check=$2 shift shift fi if [ -z "$prog" ]; then $make $makeopts touch > /dev/null 2>&1 args="$defs" else if [ -z "$tag" ]; then tag="$prog" fi args="build-$prog $defs" fi build_run "$tag" "$args" check_result 'check result' "$check" "$@" } quick_run() { # usage: quick_run [-Ddef...] tag="$1" shift defs= while expr "x$1" : "x-D.*" > /dev/null; do defs="DEFS+='$1' $defs" shift done $make $makeopts touch > /dev/null 2>&1 build_run "$tag" "$defs" } quick_check() { # usage: quick_check checkargs ... tag="$1" shift check_result "$tag" check_regexps "$@" } ## kernel image osimg=$(make_print ucoreimg) ## swap image swapimg=$(make_print swapimg) ## set default qemu-options qemuopts="-hda $osimg -drive file=$swapimg,media=disk,cache=writeback" ## set break-function, default is readline brkfun=readline default_check() { pts=7 check_regexps "$@" pts=3 quick_check 'check output' \ 'memory management: default_pmm_manager' \ 'check_alloc_page() succeeded!' \ 'check_pgdir() succeeded!' \ 'check_boot_pgdir() succeeded!' \ 'PDE(0e0) c0000000-f8000000 38000000 urw' \ ' |-- PTE(38000) c0000000-f8000000 38000000 -rw' \ 'PDE(001) fac00000-fb000000 00400000 -rw' \ ' |-- PTE(000e0) faf00000-fafe0000 000e0000 urw' \ ' |-- PTE(00001) fafeb000-fafec000 00001000 -rw' \ 'check_vma_struct() succeeded!' \ 'page fault at 0x00000100: K/W [no page found].' \ 'check_pgfault() succeeded!' \ 'check_vmm() succeeded.' \ 'page fault at 0x00001000: K/W [no page found].' \ 'page fault at 0x00002000: K/W [no page found].' \ 'page fault at 0x00003000: K/W [no page found].' \ 'page fault at 0x00004000: K/W [no page found].' \ 'write Virt Page e in fifo_check_swap' \ 'page fault at 0x00005000: K/W [no page found].' \ 'page fault at 0x00001000: K/W [no page found]' \ 'page fault at 0x00002000: K/W [no page found].' \ 'page fault at 0x00003000: K/W [no page found].' \ 'page fault at 0x00004000: K/W [no page found].' \ 'check_swap() succeeded!' \ '++ setup timer interrupts' } ## check now!! run_test -prog 'badsegment' -check default_check \ 'kernel_execve: pid = 2, name = "badsegment".' \ - 'trapframe at 0xc.......' \ 'trap 0x0000000d General Protection' \ ' err 0x00000028' \ - ' eip 0x008.....' \ - ' esp 0xaff.....' \ ' cs 0x----001b' \ ' ss 0x----0023' \ ! - 'user panic at .*' run_test -prog 'divzero' -check default_check \ 'kernel_execve: pid = 2, name = "divzero".' \ - 'trapframe at 0xc.......' \ 'trap 0x00000000 Divide error' \ - ' eip 0x008.....' \ - ' esp 0xaff.....' \ ' cs 0x----001b' \ ' ss 0x----0023' \ ! - 'user panic at .*' run_test -prog 'softint' -check default_check \ 'kernel_execve: pid = 2, name = "softint".' \ - 'trapframe at 0xc.......' \ 'trap 0x0000000d General Protection' \ ' err 0x00000072' \ - ' eip 0x008.....' \ - ' esp 0xaff.....' \ ' cs 0x----001b' \ ' ss 0x----0023' \ ! - 'user panic at .*' pts=10 run_test -prog 'faultread' -check default_check \ 'kernel_execve: pid = 2, name = "faultread".' \ - 'trapframe at 0xc.......' \ 'trap 0x0000000e Page Fault' \ ' err 0x00000004' \ - ' eip 0x008.....' \ ! - 'user panic at .*' run_test -prog 'faultreadkernel' -check default_check \ 'kernel_execve: pid = 2, name = "faultreadkernel".' \ - 'trapframe at 0xc.......' \ 'trap 0x0000000e Page Fault' \ ' err 0x00000005' \ - ' eip 0x008.....' \ ! - 'user panic at .*' run_test -prog 'hello' -check default_check \ 'kernel_execve: pid = 2, name = "hello".' \ 'Hello world!!.' \ 'I am process 2.' \ 'hello pass.' run_test -prog 'testbss' -check default_check \ 'kernel_execve: pid = 2, name = "testbss".' \ 'Making sure bss works right...' \ 'Yes, good. Now doing a wild write off the end...' \ 'testbss may pass.' \ - 'trapframe at 0xc.......' \ 'trap 0x0000000e Page Fault' \ ' err 0x00000006' \ - ' eip 0x008.....' \ 'killed by kernel.' \ ! - 'user panic at .*' run_test -prog 'pgdir' -check default_check \ 'kernel_execve: pid = 2, name = "pgdir".' \ 'I am 2, print pgdir.' \ 'PDE(001) 00800000-00c00000 00400000 urw' \ ' |-- PTE(00002) 00800000-00802000 00002000 ur-' \ ' |-- PTE(00001) 00802000-00803000 00001000 urw' \ 'PDE(001) afc00000-b0000000 00400000 urw' \ ' |-- PTE(00004) afffc000-b0000000 00004000 urw' \ 'PDE(0e0) c0000000-f8000000 38000000 urw' \ ' |-- PTE(38000) c0000000-f8000000 38000000 -rw' \ 'pgdir pass.' run_test -prog 'yield' -check default_check \ 'kernel_execve: pid = 2, name = "yield".' \ 'Hello, I am process 2.' \ 'Back in process 2, iteration 0.' \ 'Back in process 2, iteration 1.' \ 'Back in process 2, iteration 2.' \ 'Back in process 2, iteration 3.' \ 'Back in process 2, iteration 4.' \ 'All done in process 2.' \ 'yield pass.' run_test -prog 'badarg' -check default_check \ 'kernel_execve: pid = 2, name = "badarg".' \ 'fork ok.' \ 'badarg pass.' \ 'all user-mode processes have quit.' \ 'init check memory pass.' \ ! - 'user panic at .*' pts=10 run_test -prog 'exit' -check default_check \ 'kernel_execve: pid = 2, name = "exit".' \ 'I am the parent. Forking the child...' \ 'I am the parent, waiting now..' \ 'I am the child.' \ - 'waitpid [0-9]+ ok\.' \ 'exit pass.' \ 'all user-mode processes have quit.' \ 'init check memory pass.' \ ! - 'user panic at .*' run_test -prog 'spin' -check default_check \ 'kernel_execve: pid = 2, name = "spin".' \ 'I am the parent. Forking the child...' \ 'I am the parent. Running the child...' \ 'I am the child. spinning ...' \ 'I am the parent. Killing the child...' \ 'kill returns 0' \ 'wait returns 0' \ 'spin may pass.' \ 'all user-mode processes have quit.' \ 'init check memory pass.' \ ! - 'user panic at .*' run_test -prog 'waitkill' -check default_check \ 'kernel_execve: pid = 2, name = "waitkill".' \ 'wait child 1.' \ 'child 2.' \ 'child 1.' \ 'kill parent ok.' \ 'kill child1 ok.' \ 'all user-mode processes have quit.' \ 'init check memory pass.' \ ! - 'user panic at .*' pts=15 run_test -prog 'forktest' -check default_check \ 'kernel_execve: pid = 2, name = "forktest".' \ 'I am child 31' \ 'I am child 19' \ 'I am child 13' \ 'I am child 0' \ 'forktest pass.' \ 'all user-mode processes have quit.' \ 'init check memory pass.' \ ! - 'fork claimed to work [0-9]+ times!' \ ! 'wait stopped early' \ ! 'wait got too many' \ ! - 'user panic at .*' pts=10 run_test -prog 'forktree' -check default_check \ 'kernel_execve: pid = 2, name = "forktree".' \ - '....: I am '\'''\' \ - '....: I am '\''0'\' \ - '....: I am '\'''\' \ - '....: I am '\''1'\' \ - '....: I am '\''0'\' \ - '....: I am '\''01'\' \ - '....: I am '\''00'\' \ - '....: I am '\''11'\' \ - '....: I am '\''10'\' \ - '....: I am '\''101'\' \ - '....: I am '\''100'\' \ - '....: I am '\''111'\' \ - '....: I am '\''110'\' \ - '....: I am '\''001'\' \ - '....: I am '\''000'\' \ - '....: I am '\''011'\' \ - '....: I am '\''010'\' \ - '....: I am '\''0101'\' \ - '....: I am '\''0100'\' \ - '....: I am '\''0111'\' \ - '....: I am '\''0110'\' \ - '....: I am '\''0001'\' \ - '....: I am '\''0000'\' \ - '....: I am '\''0011'\' \ - '....: I am '\''0010'\' \ - '....: I am '\''1101'\' \ - '....: I am '\''1100'\' \ - '....: I am '\''1111'\' \ - '....: I am '\''1110'\' \ - '....: I am '\''1001'\' \ - '....: I am '\''1000'\' \ - '....: I am '\''1011'\' \ - '....: I am '\''1010'\' \ 'all user-mode processes have quit.' \ 'init check memory pass.' ## print final-score show_final