run.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. #!/usr/bin/env node
  2. "use strict";
  3. process.on("unhandledRejection", exn => { throw exn; });
  4. // Mapping between signals and x86 exceptions:
  5. // "Program received signal SIGILL, Illegal instruction." -> #UD (6)
  6. // "Program received signal SIGFPE, Arithmetic exception." -> #DE (0)
  7. // to be determined -> #GP
  8. // to be determined -> #NM
  9. // to be determined -> #TS
  10. // to be determined -> #NP
  11. // to be determined -> #SS
  12. // to be determined -> #PF
  13. // A #UD might indicate a bug in the test generation
  14. const assert = require("assert").strict;
  15. const fs = require("fs");
  16. const path = require("path");
  17. const os = require("os");
  18. const cluster = require("cluster");
  19. const MAX_PARALLEL_TESTS = +process.env.MAX_PARALLEL_TESTS || 99;
  20. const TEST_NAME = new RegExp(process.env.TEST_NAME || "", "i");
  21. const SINGLE_TEST_TIMEOUT = 10000;
  22. const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD;
  23. const TEST_DIR = __dirname + "/build/";
  24. const DONE_MSG = "DONE";
  25. const TERMINATE_MSG = "DONE";
  26. const BSS = 0x100000;
  27. const STACK_TOP = 0x102000;
  28. const FORCE_JIT = process.argv.includes("--force-jit");
  29. // alternative representation for infinity for json
  30. const JSON_POS_INFINITY = "+INFINITY";
  31. const JSON_NEG_INFINITY = "-INFINITY";
  32. const JSON_POS_NAN = "+NAN";
  33. const JSON_NEG_NAN = "-NAN";
  34. const MASK_ARITH = 1 | 1 << 2 | 1 << 4 | 1 << 6 | 1 << 7 | 1 << 11;
  35. const FPU_TAG_ALL_INVALID = 0xAAAA;
  36. const FPU_STATUS_MASK = 0xFFFF & ~(1 << 9 | 1 << 5 | 1 << 3 | 1 << 1); // bits that are not correctly implemented by v86
  37. const FP_COMPARISON_SIGNIFICANT_DIGITS = 7;
  38. try {
  39. var V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86;
  40. }
  41. catch(e) {
  42. console.error(e);
  43. console.error("Failed to import build/libv86-debug.js. Run " +
  44. "`make build/libv86-debug.js` first.");
  45. process.exit(1);
  46. }
  47. function float_equal(x, y)
  48. {
  49. assert(typeof x === "number");
  50. assert(typeof y === "number");
  51. if(x === Infinity && y === Infinity || x === -Infinity && y === -Infinity || isNaN(x) && isNaN(y))
  52. {
  53. return true;
  54. }
  55. const epsilon = Math.pow(10, -FP_COMPARISON_SIGNIFICANT_DIGITS);
  56. return Math.abs(x - y) < epsilon;
  57. }
  58. function format_value(v)
  59. {
  60. if(typeof v === "number")
  61. {
  62. if((v >>> 0) !== v && (v | 0) !== v)
  63. {
  64. return String(v);
  65. }
  66. else
  67. {
  68. return "0x" + (v >>> 0).toString(16);
  69. }
  70. }
  71. else
  72. {
  73. return String(v);
  74. }
  75. }
  76. if(cluster.isMaster)
  77. {
  78. function extract_json(name, fixture_text)
  79. {
  80. let exception;
  81. if(fixture_text.includes("(signal SIGFPE)"))
  82. {
  83. exception = "DE";
  84. }
  85. if(fixture_text.includes("(signal SIGILL)"))
  86. {
  87. exception = "UD";
  88. }
  89. if(fixture_text.includes("(signal SIGSEGV)"))
  90. {
  91. exception = "GP";
  92. }
  93. if(fixture_text.includes("(signal SIGBUS)"))
  94. {
  95. exception = "PF";
  96. }
  97. if(!exception && fixture_text.includes("Program received signal"))
  98. {
  99. throw new Error("Test was killed during execution by gdb: " + name + "\n" + fixture_text);
  100. }
  101. fixture_text = fixture_text.toString()
  102. .replace(/-inf\b/g, JSON.stringify(JSON_NEG_INFINITY))
  103. .replace(/\binf\b/g, JSON.stringify(JSON_POS_INFINITY))
  104. .replace(/-nan\b/g, JSON.stringify(JSON_NEG_NAN))
  105. .replace(/\bnan\b/g, JSON.stringify(JSON_POS_NAN));
  106. const json_regex = /---BEGIN JSON---([\s\[\]\.\+\w":\-,]*)---END JSON---/;
  107. const regex_match = json_regex.exec(fixture_text);
  108. if (!regex_match || regex_match.length < 2) {
  109. throw new Error("Could not find JSON in fixture text: " + fixture_text + "\nTest: " + name);
  110. }
  111. let array = JSON.parse(regex_match[1]);
  112. return {
  113. array: array,
  114. exception,
  115. };
  116. }
  117. function send_work_to_worker(worker, message) {
  118. if(current_test < tests.length) {
  119. const test = tests[current_test];
  120. worker.send(test);
  121. current_test++;
  122. }
  123. else {
  124. worker.send(TERMINATE_MSG);
  125. worker.disconnect();
  126. setTimeout(() => {
  127. // The emulator currently doesn't cleanly exit, so this is necessary
  128. console.log("Worker killed");
  129. worker.kill();
  130. }, 100);
  131. finished_workers++;
  132. if(finished_workers === nr_of_cpus)
  133. {
  134. test_finished();
  135. }
  136. }
  137. }
  138. const dir_files = fs.readdirSync(TEST_DIR);
  139. const files = dir_files.filter((name) => {
  140. return name.endsWith(".img");
  141. }).map(name => {
  142. return name.slice(0, -4);
  143. }).filter(name => {
  144. return TEST_NAME.test(name + ".img");
  145. });
  146. const tests = files.map(name => {
  147. let fixture_name = name + ".fixture";
  148. let img_name = name + ".img";
  149. let fixture_text = fs.readFileSync(TEST_DIR + fixture_name);
  150. let fixture = extract_json(name, fixture_text);
  151. return {
  152. img_name: img_name,
  153. fixture: fixture,
  154. };
  155. });
  156. const nr_of_cpus = Math.min(
  157. os.cpus().length || 1,
  158. tests.length,
  159. MAX_PARALLEL_TESTS
  160. );
  161. console.log("Using %d cpus", nr_of_cpus);
  162. let current_test = 0;
  163. let failed_tests = [];
  164. let finished_workers = 0;
  165. for(let i = 0; i < nr_of_cpus; i++)
  166. {
  167. let worker = cluster.fork();
  168. worker.on("message", function(message) {
  169. if (message !== DONE_MSG) {
  170. failed_tests.push(message);
  171. }
  172. send_work_to_worker(this);
  173. });
  174. worker.on("online", send_work_to_worker.bind(null, worker));
  175. worker.on("exit", function(code, signal) {
  176. if(code !== 0 && code !== null) {
  177. console.log("Worker error code:", code);
  178. process.exit(code);
  179. }
  180. });
  181. worker.on("error", function(error) {
  182. console.error("Worker error: ", error.toString(), error);
  183. process.exit(1);
  184. });
  185. }
  186. function test_finished()
  187. {
  188. console.log(
  189. "\n[+] Passed %d/%d tests.",
  190. tests.length - failed_tests.length,
  191. tests.length
  192. );
  193. if (failed_tests.length > 0) {
  194. console.log("[-] Failed %d test(s).", failed_tests.length);
  195. failed_tests.forEach(function(test_failure) {
  196. console.error("\n[-] %s:", test_failure.img_name);
  197. test_failure.failures.forEach(function(failure) {
  198. console.error("\n\t" + failure.name);
  199. console.error("\tActual: " + failure.actual);
  200. console.error("\tExpected: " + failure.expected);
  201. });
  202. });
  203. process.exit(1);
  204. }
  205. }
  206. }
  207. else {
  208. function run_test(test)
  209. {
  210. if(!loaded)
  211. {
  212. first_test = test;
  213. return;
  214. }
  215. waiting_to_receive_next_test = false;
  216. current_test = test;
  217. console.info("Testing", test.img_name);
  218. var cpu = emulator.v86.cpu;
  219. assert(!emulator.running);
  220. cpu.reboot_internal();
  221. cpu.reset_memory();
  222. cpu.load_multiboot(fs.readFileSync(TEST_DIR + current_test.img_name).buffer);
  223. test_timeout = setTimeout(() => {
  224. console.error("Test " + test.img_name + " timed out after " + (SINGLE_TEST_TIMEOUT / 1000) + " seconds.");
  225. process.exit(2);
  226. }, SINGLE_TEST_TIMEOUT);
  227. if(FORCE_JIT)
  228. {
  229. let eip = cpu.instruction_pointer[0];
  230. cpu.test_hook_did_finalize_wasm = function()
  231. {
  232. eip += 4096;
  233. const last_word = cpu.mem32s[eip - 4 >> 2];
  234. if(last_word === 0 || last_word === undefined)
  235. {
  236. cpu.test_hook_did_finalize_wasm = null;
  237. // don't synchronously call into the emulator from this callback
  238. setTimeout(() => {
  239. emulator.run();
  240. }, 0);
  241. }
  242. else
  243. {
  244. cpu.jit_force_generate(eip);
  245. }
  246. };
  247. cpu.jit_force_generate(eip);
  248. }
  249. else
  250. {
  251. emulator.run();
  252. }
  253. }
  254. let loaded = false;
  255. let current_test = undefined;
  256. let first_test = undefined;
  257. let waiting_to_receive_next_test = false;
  258. let recorded_exceptions = [];
  259. let test_timeout;
  260. let emulator = new V86({
  261. autostart: false,
  262. memory_size: 2 * 1024 * 1024,
  263. disable_jit: +process.env.DISABLE_JIT,
  264. log_level: 0,
  265. });
  266. emulator.add_listener("emulator-loaded", function()
  267. {
  268. loaded = true;
  269. if(first_test)
  270. {
  271. run_test(first_test);
  272. }
  273. });
  274. emulator.cpu_exception_hook = function(n)
  275. {
  276. emulator.v86.cpu.instruction_counter[0] += 100000; // always make progress
  277. if(waiting_to_receive_next_test)
  278. {
  279. return true;
  280. }
  281. const exceptions = {
  282. 0: "DE",
  283. 6: "UD",
  284. 13: "GP",
  285. };
  286. const exception = exceptions[n];
  287. if(exception === undefined)
  288. {
  289. console.error("Unexpected CPU exception: " + n);
  290. process.exit(1);
  291. }
  292. const eip = emulator.v86.cpu.instruction_pointer[0];
  293. emulator.v86.cpu.write32(emulator.v86.cpu.translate_address_system_read(eip), 0xF4F4F4F4); // hlt
  294. // XXX: On gdb execution is stopped at this point. On v86 we
  295. // currently don't have this ability, so we record the exception
  296. // and continue execution
  297. recorded_exceptions.push({ exception, eip });
  298. finish_test();
  299. return true;
  300. };
  301. emulator.bus.register("cpu-event-halt", function() {
  302. finish_test();
  303. });
  304. function finish_test()
  305. {
  306. if(waiting_to_receive_next_test)
  307. {
  308. return;
  309. }
  310. waiting_to_receive_next_test = true;
  311. clearTimeout(test_timeout);
  312. emulator.stop();
  313. var cpu = emulator.v86.cpu;
  314. const evaluated_fpu_regs = new Float64Array(8).map((_, i) => cpu.fpu_get_sti_f64(i));
  315. const evaluated_mmxs = new Int32Array(16).map((_, i) => cpu.fpu_st[(i & ~1) << 1 | (i & 1)]);
  316. const evaluated_xmms = cpu.reg_xmm32s;
  317. const evaluated_memory = new Int32Array(cpu.mem8.buffer, cpu.mem8.byteOffset + BSS, STACK_TOP - BSS >> 2);
  318. const evaluated_fpu_tag = cpu.fpu_load_tag_word();
  319. const evaluated_fpu_status = cpu.fpu_load_status_word() & FPU_STATUS_MASK;
  320. let individual_failures = [];
  321. assert(current_test.fixture.array);
  322. const FLOAT_TRANSLATION = {
  323. [JSON_POS_INFINITY]: Infinity,
  324. [JSON_NEG_INFINITY]: -Infinity,
  325. [JSON_POS_NAN]: NaN,
  326. [JSON_NEG_NAN]: NaN, // XXX: Ignore sign of NaN
  327. };
  328. let offset = 0;
  329. const expected_reg32 = current_test.fixture.array.slice(offset, offset += 8);
  330. const expected_eip = current_test.fixture.array[offset++];
  331. const expected_fpu_regs =
  332. current_test.fixture.array.slice(offset, offset += 8) .map(x => x in FLOAT_TRANSLATION ? FLOAT_TRANSLATION[x] : x);
  333. const expected_mmx_registers = current_test.fixture.array.slice(offset, offset += 16);
  334. const expected_xmm_registers = current_test.fixture.array.slice(offset, offset += 32);
  335. const expected_memory = current_test.fixture.array.slice(offset, offset += 8192 / 4);
  336. const expected_eflags = current_test.fixture.array[offset++] & MASK_ARITH;
  337. const fpu_tag = current_test.fixture.array[offset++];
  338. const fpu_status = current_test.fixture.array[offset++] & FPU_STATUS_MASK;
  339. if(offset !== current_test.fixture.array.length)
  340. {
  341. throw new Error("Bad fixture length in test " + current_test.img_name);
  342. }
  343. if(!current_test.fixture.exception)
  344. {
  345. for (let i = 0; i < cpu.reg32.length; i++) {
  346. let reg = cpu.reg32[i];
  347. if (reg !== expected_reg32[i]) {
  348. individual_failures.push({
  349. name: "cpu.reg32[" + i + "]",
  350. expected: expected_reg32[i],
  351. actual: reg,
  352. });
  353. }
  354. }
  355. if(fpu_tag !== FPU_TAG_ALL_INVALID)
  356. {
  357. for (let i = 0; i < evaluated_fpu_regs.length; i++) {
  358. if (expected_fpu_regs[i] !== "invalid" &&
  359. !float_equal(evaluated_fpu_regs[i], expected_fpu_regs[i])) {
  360. individual_failures.push({
  361. name: "st" + i,
  362. expected: expected_fpu_regs[i],
  363. actual: evaluated_fpu_regs[i],
  364. });
  365. }
  366. }
  367. if(fpu_status !== evaluated_fpu_status)
  368. {
  369. individual_failures.push({
  370. name: "fpu status word",
  371. expected: fpu_status,
  372. actual: evaluated_fpu_status,
  373. });
  374. }
  375. }
  376. else
  377. {
  378. for (let i = 0; i < evaluated_mmxs.length; i++) {
  379. if (evaluated_mmxs[i] !== expected_mmx_registers[i]) {
  380. individual_failures.push({
  381. name: "mm" + (i >> 1) + ".int32[" + (i & 1) + "]",
  382. expected: expected_mmx_registers[i],
  383. actual: evaluated_mmxs[i],
  384. });
  385. }
  386. }
  387. }
  388. for (let i = 0; i < evaluated_xmms.length; i++) {
  389. if (evaluated_xmms[i] !== expected_xmm_registers[i]) {
  390. individual_failures.push({
  391. name: "xmm" + (i >> 2) + ".int32[" + (i & 3) + "] (cpu.reg_xmm[" + i + "])",
  392. expected: expected_xmm_registers[i],
  393. actual: evaluated_xmms[i],
  394. });
  395. }
  396. }
  397. for (let i = 0; i < evaluated_memory.length; i++) {
  398. if (evaluated_memory[i] !== expected_memory[i]) {
  399. individual_failures.push({
  400. name: "mem[" + (BSS + 4 * i).toString(16).toUpperCase() + "]",
  401. expected: expected_memory[i],
  402. actual: evaluated_memory[i],
  403. });
  404. }
  405. }
  406. const seen_eflags = cpu.get_eflags() & MASK_ARITH;
  407. if(seen_eflags !== expected_eflags)
  408. {
  409. individual_failures.push({
  410. name: "eflags",
  411. expected: expected_eflags,
  412. actual: seen_eflags,
  413. });
  414. }
  415. }
  416. if(current_test.fixture.exception)
  417. {
  418. const seen_eip = (recorded_exceptions[0] || {}).eip;
  419. if(seen_eip !== expected_eip)
  420. {
  421. individual_failures.push({
  422. name: "exception eip",
  423. expected: expected_eip,
  424. actual: seen_eip === undefined ? "(none)" : seen_eip,
  425. });
  426. }
  427. }
  428. const seen_exception = (recorded_exceptions[0] || {}).exception;
  429. if(current_test.fixture.exception !== seen_exception)
  430. {
  431. individual_failures.push({
  432. name: "Exception",
  433. actual: seen_exception || "(none)",
  434. expected: current_test.fixture.exception,
  435. });
  436. }
  437. individual_failures = individual_failures.map(({ name, actual, expected }) => {
  438. return {
  439. name,
  440. actual: format_value(actual),
  441. expected: format_value(expected),
  442. };
  443. });
  444. recorded_exceptions = [];
  445. if (individual_failures.length > 0) {
  446. process.send({
  447. failures: individual_failures,
  448. img_name: current_test.img_name
  449. });
  450. }
  451. else {
  452. process.send(DONE_MSG);
  453. }
  454. }
  455. cluster.worker.on("message", function(message) {
  456. if(message === TERMINATE_MSG)
  457. {
  458. emulator.stop();
  459. emulator = null;
  460. }
  461. else
  462. {
  463. run_test(message);
  464. }
  465. });
  466. }