123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- #!/usr/bin/env node
- "use strict";
- process.on("unhandledRejection", exn => { throw exn; });
- // Mapping between signals and x86 exceptions:
- // "Program received signal SIGILL, Illegal instruction." -> #UD (6)
- // "Program received signal SIGFPE, Arithmetic exception." -> #DE (0)
- // to be determined -> #GP
- // to be determined -> #NM
- // to be determined -> #TS
- // to be determined -> #NP
- // to be determined -> #SS
- // to be determined -> #PF
- // A #UD might indicate a bug in the test generation
- const assert = require("assert").strict;
- const fs = require("fs");
- const path = require("path");
- const os = require("os");
- const cluster = require("cluster");
- const MAX_PARALLEL_TESTS = +process.env.MAX_PARALLEL_TESTS || 99;
- const TEST_NAME = process.env.TEST_NAME;
- const SINGLE_TEST_TIMEOUT = 10000;
- const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD;
- const TEST_DIR = __dirname + "/build/";
- const DONE_MSG = "DONE";
- const TERMINATE_MSG = "DONE";
- const FORCE_JIT = process.argv.includes("--force-jit");
- // see --section-start= in makefile
- const V86_TEXT_OFFSET = 0x8000;
- const NASM_TEXT_OFFSET = 0x800000;
- // alternative representation for infinity for json
- const JSON_POS_INFINITY = "+INFINITY";
- const JSON_NEG_INFINITY = "-INFINITY";
- const JSON_POS_NAN = "+NAN";
- const JSON_NEG_NAN = "-NAN";
- const MASK_ARITH = 1 | 1 << 2 | 1 << 4 | 1 << 6 | 1 << 7 | 1 << 11;
- const FPU_TAG_ALL_INVALID = 0xAAAA;
- const FPU_STATUS_MASK = 0xFFFF & ~(1 << 9 | 1 << 5 | 1 << 3); // bits that are not correctly implemented by v86
- const FP_COMPARISON_SIGNIFICANT_DIGITS = 7;
- try {
- var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86;
- }
- catch(e) {
- console.error(e);
- console.error("Failed to import build/libv86-debug.js. Run " +
- "`make build/libv86-debug.js` first.");
- process.exit(1);
- }
- function float_equal(x, y)
- {
- assert(typeof x === "number");
- assert(typeof y === "number");
- if(x === Infinity && y === Infinity || x === -Infinity && y === -Infinity || isNaN(x) && isNaN(y))
- {
- return true;
- }
- const epsilon = Math.pow(10, -FP_COMPARISON_SIGNIFICANT_DIGITS);
- return Math.abs(x - y) < epsilon;
- }
- function format_value(v)
- {
- if(typeof v === "number")
- {
- if((v >>> 0) !== v && (v | 0) !== v)
- {
- return String(v);
- }
- else
- {
- return "0x" + (v >>> 0).toString(16);
- }
- }
- else
- {
- return String(v);
- }
- }
- if(cluster.isMaster)
- {
- function extract_json(name, fixture_text)
- {
- let exception;
- if(fixture_text.includes("(signal SIGFPE)"))
- {
- exception = "DE";
- }
- if(fixture_text.includes("(signal SIGILL)"))
- {
- exception = "UD";
- }
- if(fixture_text.includes("(signal SIGSEGV)"))
- {
- exception = "GP";
- }
- if(fixture_text.includes("(signal SIGBUS)"))
- {
- exception = "PF";
- }
- if(!exception && fixture_text.includes("Program received signal"))
- {
- throw new Error("Test was killed during execution by gdb: " + name + "\n" + fixture_text);
- }
- fixture_text = fixture_text.toString()
- .replace(/-inf\b/g, JSON.stringify(JSON_NEG_INFINITY))
- .replace(/\binf\b/g, JSON.stringify(JSON_POS_INFINITY))
- .replace(/-nan\b/g, JSON.stringify(JSON_NEG_NAN))
- .replace(/\bnan\b/g, JSON.stringify(JSON_POS_NAN));
- const json_regex = /---BEGIN JSON---([\s\[\]\.\+\w":\-,]*)---END JSON---/;
- const regex_match = json_regex.exec(fixture_text);
- if (!regex_match || regex_match.length < 2) {
- throw new Error("Could not find JSON in fixture text: " + fixture_text + "\nTest: " + name);
- }
- let array = JSON.parse(regex_match[1]);
- return {
- array: array,
- exception,
- };
- }
- function send_work_to_worker(worker, message) {
- if(current_test < tests.length) {
- const test = tests[current_test];
- worker.send(test);
- current_test++;
- }
- else {
- worker.send(TERMINATE_MSG);
- worker.disconnect();
- setTimeout(() => {
- // The emulator currently doesn't cleanly exit, so this is necessary
- console.log("Worker killed");
- worker.kill();
- }, 100);
- finished_workers++;
- if(finished_workers === nr_of_cpus)
- {
- test_finished();
- }
- }
- }
- const dir_files = fs.readdirSync(TEST_DIR);
- const files = dir_files.filter((name) => {
- return name.endsWith(".asm");
- }).map(name => {
- return name.slice(0, -4);
- }).filter(name => {
- return !TEST_NAME || name === TEST_NAME;
- });
- const tests = files.map(name => {
- let fixture_name = name + ".fixture";
- let img_name = name + ".img";
- let fixture_text = fs.readFileSync(TEST_DIR + fixture_name);
- let fixture = extract_json(name, fixture_text);
- return {
- img_name: img_name,
- fixture: fixture,
- };
- });
- const nr_of_cpus = Math.min(
- os.cpus().length || 1,
- tests.length,
- MAX_PARALLEL_TESTS
- );
- console.log("Using %d cpus", nr_of_cpus);
- let current_test = 0;
- let failed_tests = [];
- let finished_workers = 0;
- for(let i = 0; i < nr_of_cpus; i++)
- {
- let worker = cluster.fork();
- worker.on("message", function(message) {
- if (message !== DONE_MSG) {
- failed_tests.push(message);
- }
- send_work_to_worker(this);
- });
- worker.on("online", send_work_to_worker.bind(null, worker));
- worker.on("exit", function(code, signal) {
- if(code !== 0 && code !== null) {
- console.log("Worker error code:", code);
- process.exit(code);
- }
- });
- worker.on("error", function(error) {
- console.error("Worker error: ", error.toString(), error);
- process.exit(1);
- });
- }
- function test_finished()
- {
- console.log(
- "\n[+] Passed %d/%d tests.",
- tests.length - failed_tests.length,
- tests.length
- );
- if (failed_tests.length > 0) {
- console.log("[-] Failed %d test(s).", failed_tests.length);
- failed_tests.forEach(function(test_failure) {
- console.error("\n[-] %s:", test_failure.img_name);
- test_failure.failures.forEach(function(failure) {
- console.error("\n\t" + failure.name);
- console.error("\tActual: " + failure.actual);
- console.error("\tExpected: " + failure.expected);
- });
- });
- process.exit(1);
- }
- }
- }
- else {
- function run_test(test)
- {
- if(!loaded)
- {
- first_test = test;
- return;
- }
- waiting_to_receive_next_test = false;
- current_test = test;
- console.info("Testing", test.img_name);
- var cpu = emulator.v86.cpu;
- assert(!emulator.running);
- cpu.reset();
- cpu.reset_memory();
- cpu.load_multiboot(fs.readFileSync(TEST_DIR + current_test.img_name).buffer);
- test_timeout = setTimeout(() => {
- console.error("Test " + test.img_name + " timed out after " + (SINGLE_TEST_TIMEOUT / 1000) + " seconds.");
- process.exit(2);
- }, SINGLE_TEST_TIMEOUT);
- if(FORCE_JIT)
- {
- cpu.jit_force_generate(cpu.instruction_pointer[0]);
- cpu.test_hook_did_finalize_wasm = function()
- {
- cpu.test_hook_did_finalize_wasm = null;
- // don't synchronously call into the emulator from this callback
- setTimeout(() => {
- emulator.run();
- }, 0);
- };
- }
- else
- {
- emulator.run();
- }
- }
- let loaded = false;
- let current_test = undefined;
- let first_test = undefined;
- let waiting_to_receive_next_test = false;
- let recorded_exceptions = [];
- let test_timeout;
- let emulator = new V86({
- autostart: false,
- memory_size: 2 * 1024 * 1024,
- log_level: 0,
- });
- emulator.add_listener("emulator-loaded", function()
- {
- loaded = true;
- if(first_test)
- {
- run_test(first_test);
- }
- });
- emulator.cpu_exception_hook = function(n)
- {
- emulator.v86.cpu.timestamp_counter[0] += 100000; // always make progress
- if(waiting_to_receive_next_test)
- {
- return true;
- }
- const exceptions = {
- 0: "DE",
- 6: "UD",
- 13: "GP",
- };
- const exception = exceptions[n];
- if(exception === undefined)
- {
- console.error("Unexpected CPU exception: " + n);
- process.exit(1);
- }
- const eip = emulator.v86.cpu.instruction_pointer[0];
- // XXX: On gdb execution is stopped at this point. On v86 we
- // currently don't have this ability, so we record the exception
- // and continue execution
- recorded_exceptions.push({ exception, eip });
- finish_test();
- return true;
- };
- emulator.bus.register("cpu-event-halt", function() {
- finish_test();
- });
- function finish_test()
- {
- if(waiting_to_receive_next_test)
- {
- return;
- }
- waiting_to_receive_next_test = true;
- clearTimeout(test_timeout);
- emulator.stop();
- var cpu = emulator.v86.cpu;
- const evaluated_fpu_regs = new Float64Array(8).map((_, i) => cpu.fpu_st[i + cpu.fpu_stack_ptr[0] & 7]);
- const evaluated_mmxs = cpu.reg_mmxs;
- const evaluated_xmms = cpu.reg_xmm32s;
- const evaluated_memory = new Int32Array(cpu.mem8.slice(0x120000 - 16 * 4, 0x120000).buffer);
- const evaluated_fpu_tag = cpu.fpu_load_tag_word();
- const evaluated_fpu_status = cpu.fpu_load_status_word() & FPU_STATUS_MASK;
- let individual_failures = [];
- assert(current_test.fixture.array);
- const FLOAT_TRANSLATION = {
- [JSON_POS_INFINITY]: Infinity,
- [JSON_NEG_INFINITY]: -Infinity,
- [JSON_POS_NAN]: NaN,
- [JSON_NEG_NAN]: NaN, // XXX: Ignore sign of NaN
- };
- let offset = 0;
- const expected_reg32 = current_test.fixture.array.slice(offset, offset += 8);
- const expected_eip = current_test.fixture.array[offset++];
- const expected_fpu_regs =
- current_test.fixture.array.slice(offset, offset += 8) .map(x => x in FLOAT_TRANSLATION ? FLOAT_TRANSLATION[x] : x);
- const expected_mmx_registers = current_test.fixture.array.slice(offset, offset += 16);
- const expected_xmm_registers = current_test.fixture.array.slice(offset, offset += 32);
- const expected_memory = current_test.fixture.array.slice(offset, offset += 16);
- const expected_eflags = current_test.fixture.array[offset++] & MASK_ARITH;
- const fpu_tag = current_test.fixture.array[offset++];
- const fpu_status = current_test.fixture.array[offset++] & FPU_STATUS_MASK;
- if(!current_test.fixture.exception)
- {
- for (let i = 0; i < cpu.reg32.length; i++) {
- let reg = cpu.reg32[i];
- if (reg !== expected_reg32[i]) {
- individual_failures.push({
- name: "cpu.reg32[" + i + "]",
- expected: expected_reg32[i],
- actual: reg,
- });
- }
- }
- if(fpu_tag !== FPU_TAG_ALL_INVALID)
- {
- for (let i = 0; i < evaluated_fpu_regs.length; i++) {
- if (expected_fpu_regs[i] !== "invalid" &&
- !float_equal(evaluated_fpu_regs[i], expected_fpu_regs[i])) {
- individual_failures.push({
- name: "st" + i,
- expected: expected_fpu_regs[i],
- actual: evaluated_fpu_regs[i],
- });
- }
- }
- if(fpu_status !== evaluated_fpu_status)
- {
- individual_failures.push({
- name: "fpu status word",
- expected: fpu_status,
- actual: evaluated_fpu_status,
- });
- }
- }
- else
- {
- for (let i = 0; i < evaluated_mmxs.length; i++) {
- if (evaluated_mmxs[i] !== expected_mmx_registers[i]) {
- individual_failures.push({
- name: "mm" + (i >> 1) + ".int32[" + (i & 1) + "] (cpu.reg_mmx[" + i + "])",
- expected: expected_mmx_registers[i],
- actual: evaluated_mmxs[i],
- });
- }
- }
- }
- for (let i = 0; i < evaluated_xmms.length; i++) {
- if (evaluated_xmms[i] !== expected_xmm_registers[i]) {
- individual_failures.push({
- name: "xmm" + (i >> 2) + ".int32[" + (i & 3) + "] (cpu.reg_xmm[" + i + "])",
- expected: expected_xmm_registers[i],
- actual: evaluated_xmms[i],
- });
- }
- }
- for (let i = 0; i < evaluated_memory.length; i++) {
- if (evaluated_memory[i] !== expected_memory[i]) {
- individual_failures.push({
- name: "mem[" + i + "]",
- expected: expected_memory[i],
- actual: evaluated_memory[i],
- });
- }
- }
- const seen_eflags = cpu.get_eflags() & MASK_ARITH;
- if(seen_eflags !== expected_eflags)
- {
- individual_failures.push({
- name: "eflags",
- expected: expected_eflags,
- actual: seen_eflags,
- });
- }
- }
- if(current_test.fixture.exception)
- {
- const seen_eip = (recorded_exceptions[0] || {}).eip;
- if(seen_eip - V86_TEXT_OFFSET !== expected_eip - NASM_TEXT_OFFSET)
- {
- individual_failures.push({
- name: "exception eip",
- expected: expected_eip - NASM_TEXT_OFFSET,
- actual: seen_eip === undefined ? "(none)" : seen_eip - V86_TEXT_OFFSET,
- });
- }
- }
- const seen_exception = (recorded_exceptions[0] || {}).exception;
- if(current_test.fixture.exception !== seen_exception)
- {
- individual_failures.push({
- name: "Exception",
- actual: seen_exception || "(none)",
- expected: current_test.fixture.exception,
- });
- }
- individual_failures = individual_failures.map(({ name, actual, expected }) => {
- return {
- name,
- actual: format_value(actual),
- expected: format_value(expected),
- };
- });
- recorded_exceptions = [];
- if (individual_failures.length > 0) {
- process.send({
- failures: individual_failures,
- img_name: current_test.img_name
- });
- }
- else {
- process.send(DONE_MSG);
- }
- }
- cluster.worker.on("message", function(message) {
- if(message === TERMINATE_MSG)
- {
- emulator.stop();
- emulator = null;
- }
- else
- {
- run_test(message);
- }
- });
- }
|