diff --git a/Makefile b/Makefile index 8a5a9f0e..4e6a0130 100644 --- a/Makefile +++ b/Makefile @@ -225,6 +225,7 @@ tests: build/libv86.js build/v86.wasm nasmtests: build/libv86-debug.js build/v86-debug.wasm $(MAKE) -C $(NASM_TEST_DIR) all + $(NASM_TEST_DIR)/gen_fixtures.js $(NASM_TEST_DIR)/run.js jitpagingtests: build/libv86.js build/v86.wasm diff --git a/tests/nasm/Makefile b/tests/nasm/Makefile index adc00155..0d48411e 100644 --- a/tests/nasm/Makefile +++ b/tests/nasm/Makefile @@ -4,11 +4,10 @@ source_files += $(addprefix build/,$(wildcard *.asm)) obj_files := $(patsubst %.asm,%.o,$(source_files)) host_executables := $(patsubst %.asm,%.bin,$(source_files)) v86_executables := $(patsubst %.asm,%.img,$(source_files)) -host_fixtures := $(patsubst %.asm,%.fixture,$(source_files)) inc_files := $(addprefix build/,$(wildcard *.inc)) -all: $(source_files) $(obj_files) $(inc_files) $(host_executables) $(v86_executables) $(host_fixtures) +all: $(source_files) $(obj_files) $(inc_files) $(host_executables) $(v86_executables) .PHONY: all build/%.o: build/%.asm $(inc_files) @@ -18,10 +17,6 @@ build/%.o: build/%.asm $(inc_files) build/%.bin: build/%.o ld -g $< -m elf_i386 --section-start=.bss=0x100000 -o $@ -# To generate a fixture using gdb -build/%.fixture: build/%.bin - gdb -quiet -batch -x gdbauto $< > $@ - # To use as a multiboot kernel image for v86 build/%.img: build/%.o ld -g $< -m elf_i386 --section-start=.bss=0x100000 --section-start=.text=0x8000 -o $@ diff --git a/tests/nasm/gdb-extract-def b/tests/nasm/gdb-extract-def new file mode 100644 index 00000000..8105e80b --- /dev/null +++ b/tests/nasm/gdb-extract-def @@ -0,0 +1,111 @@ +# Invocation: gdb -x gdb-extract-def + +# Set a breakpoint "in the future", which all the test binaries can then share +set breakpoint pending on +break loop + +# extract-state /path/to/foo.bin /path/to/foo.fixture +define extract-state + file $arg0 + + set $STACK_TOP=0x120000 + + # Disables logging to stdout - only log to file + set logging redirect on + + set logging file $arg1 + set logging overwrite on + set logging on + + run + + printf "---BEGIN JSON---\n" + printf "[\n" + printf " %d,\n", $eax + printf " %d,\n", $ecx + printf " %d,\n", $edx + printf " %d,\n", $ebx + printf " %d,\n", $esp + printf " %d,\n", $ebp + printf " %d,\n", $esi + printf " %d,\n", $edi + printf " \n" + + printf " %d,\n", $mm0.v2_int32[0] + printf " %d,\n", $mm0.v2_int32[1] + printf " %d,\n", $mm1.v2_int32[0] + printf " %d,\n", $mm1.v2_int32[1] + printf " %d,\n", $mm2.v2_int32[0] + printf " %d,\n", $mm2.v2_int32[1] + printf " %d,\n", $mm3.v2_int32[0] + printf " %d,\n", $mm3.v2_int32[1] + printf " %d,\n", $mm4.v2_int32[0] + printf " %d,\n", $mm4.v2_int32[1] + printf " %d,\n", $mm5.v2_int32[0] + printf " %d,\n", $mm5.v2_int32[1] + printf " %d,\n", $mm6.v2_int32[0] + printf " %d,\n", $mm6.v2_int32[1] + printf " %d,\n", $mm7.v2_int32[0] + printf " %d,\n", $mm7.v2_int32[1] + printf " \n" + + printf " %d,\n", $xmm0.v4_int32[0] + printf " %d,\n", $xmm0.v4_int32[1] + printf " %d,\n", $xmm0.v4_int32[2] + printf " %d,\n", $xmm0.v4_int32[3] + printf " %d,\n", $xmm1.v4_int32[0] + printf " %d,\n", $xmm1.v4_int32[1] + printf " %d,\n", $xmm1.v4_int32[2] + printf " %d,\n", $xmm1.v4_int32[3] + printf " %d,\n", $xmm2.v4_int32[0] + printf " %d,\n", $xmm2.v4_int32[1] + printf " %d,\n", $xmm2.v4_int32[2] + printf " %d,\n", $xmm2.v4_int32[3] + printf " %d,\n", $xmm3.v4_int32[0] + printf " %d,\n", $xmm3.v4_int32[1] + printf " %d,\n", $xmm3.v4_int32[2] + printf " %d,\n", $xmm3.v4_int32[3] + printf " %d,\n", $xmm4.v4_int32[0] + printf " %d,\n", $xmm4.v4_int32[1] + printf " %d,\n", $xmm4.v4_int32[2] + printf " %d,\n", $xmm4.v4_int32[3] + printf " %d,\n", $xmm5.v4_int32[0] + printf " %d,\n", $xmm5.v4_int32[1] + printf " %d,\n", $xmm5.v4_int32[2] + printf " %d,\n", $xmm5.v4_int32[3] + printf " %d,\n", $xmm6.v4_int32[0] + printf " %d,\n", $xmm6.v4_int32[1] + printf " %d,\n", $xmm6.v4_int32[2] + printf " %d,\n", $xmm6.v4_int32[3] + printf " %d,\n", $xmm7.v4_int32[0] + printf " %d,\n", $xmm7.v4_int32[1] + printf " %d,\n", $xmm7.v4_int32[2] + printf " %d,\n", $xmm7.v4_int32[3] + printf " \n" + + printf " %d,\n", *(int*)($STACK_TOP-64) + printf " %d,\n", *(int*)($STACK_TOP-60) + printf " %d,\n", *(int*)($STACK_TOP-56) + printf " %d,\n", *(int*)($STACK_TOP-52) + printf " %d,\n", *(int*)($STACK_TOP-48) + printf " %d,\n", *(int*)($STACK_TOP-44) + printf " %d,\n", *(int*)($STACK_TOP-40) + printf " %d,\n", *(int*)($STACK_TOP-36) + printf " %d,\n", *(int*)($STACK_TOP-32) + printf " %d,\n", *(int*)($STACK_TOP-28) + printf " %d,\n", *(int*)($STACK_TOP-24) + printf " %d,\n", *(int*)($STACK_TOP-20) + printf " %d,\n", *(int*)($STACK_TOP-16) + printf " %d,\n", *(int*)($STACK_TOP-12) + printf " %d,\n", *(int*)($STACK_TOP-8) + printf " %d,\n", *(int*)($STACK_TOP-4) + printf " \n" + + printf " %d\n", $eflags + + printf "]\n" + printf "---END JSON---\n" + + set logging off + + end diff --git a/tests/nasm/gen_fixtures.js b/tests/nasm/gen_fixtures.js new file mode 100755 index 00000000..e1b6a705 --- /dev/null +++ b/tests/nasm/gen_fixtures.js @@ -0,0 +1,112 @@ +#!/usr/bin/env node +"use strict"; + +const fs = require("fs"); +const os = require("os"); +const { spawn } = require("child_process"); + +const DEBUG = process.env.DEBUG || false; +// Limit the number of binaries for which fixtures are generated in DEBUG mode +const DEBUG_FILE_LIMIT = 0; +// Maximum number of gdb processes to spawn in parallel +const MAX_PARALLEL_PROCS = +process.env.MAX_PARALLEL_PROCS || 32; + +// Usage: console.log(CYAN_FMT, "This shows up in cyan!") +const CYAN_FMT = "\x1b[36m%s\x1b[0m"; +const YELLOW_FMT = "\x1b[33m%s\x1b[0m"; + +const TEST_DIR = __dirname + "/"; +const BUILD_DIR = TEST_DIR + "build/"; + +const GDB_DEFAULT_ARGS = [ + "-batch", + `--command=${TEST_DIR}gdb-extract-def` +]; + +/* Split up a string into semi-evenly sized chunks + * Eg. chunk("0 0 1 1 2 2 2 3 3 3".split(" "), 4) -> + * [ [ "0", "0" ], + * [ "1", "1" ], + * [ "2", "2", "2" ], + * [ "3", "3", "3" ] ] + */ +function chunk(source, num_chunks=4) +{ + const arr = source.slice(); + const ret = []; + + let rem_chunks = num_chunks; + while(rem_chunks > 0) + { + // We guarantee that the entire array is processed because when rem_chunk=1 -> len/1 = len + ret.push(arr.splice(0, Math.floor(arr.length / rem_chunks))); + rem_chunks--; + } + return ret; +} + +const dir_files = fs.readdirSync(BUILD_DIR); +let test_files = dir_files.filter((name) => { + return name.endsWith(".bin"); +}).map(name => { + return name.slice(0, -4); +}); + +if(DEBUG && DEBUG_FILE_LIMIT) +{ + console.log(CYAN_FMT, "[DEBUG]", `Limiting to ${DEBUG_FILE_LIMIT} fixtures`); + test_files = test_files.slice(0, DEBUG_FILE_LIMIT); +} + +const nr_of_cpus = Math.min( + os.cpus().length || 1, + test_files.length, + MAX_PARALLEL_PROCS +); +console.log("[+] Using %d cpus to generate fixtures", nr_of_cpus); + +const workloads = chunk(test_files, nr_of_cpus); + +function test_arg_formatter(workload) +{ + return workload.map(test => { + const test_path = BUILD_DIR + test; + return `--eval-command=extract-state ${test_path}.bin ${test_path}.fixture`; + }); +} + +function setProcHandlers(proc, n) +{ + proc.on("close", (code) => { + console.log(`[+] child process ${n} exited with code ${code}`); + if(code !== 0) + { + process.exit(code); + } + }); + + if(DEBUG) + { + proc.stdout.on("data", (data) => { + console.log(CYAN_FMT, "stdout", `${n}: ${data}`); + }); + + proc.stderr.on("data", (data) => { + console.log(YELLOW_FMT, "stderr", `${n}: ${data}`); + }); + } +} + +const gdb_args = []; +for(let i = 0; i < nr_of_cpus; i++) +{ + gdb_args[i] = GDB_DEFAULT_ARGS.concat(test_arg_formatter(workloads[i])); + + if(DEBUG) + { + console.log(CYAN_FMT, "[DEBUG]", "gdb", gdb_args[i].join(" ")); + } + + const gdb = spawn("gdb", gdb_args[i]); + setProcHandlers(gdb, i); +}