run.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  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 fs = require("fs");
  15. const path = require("path");
  16. const os = require("os");
  17. const cluster = require("cluster");
  18. const MAX_PARALLEL_TESTS = +process.env.MAX_PARALLEL_TESTS || 99;
  19. const TEST_DIR = __dirname + "/build/";
  20. const DONE_MSG = "DONE";
  21. const TERMINATE_MSG = "DONE";
  22. const FORCE_JIT = process.argv.includes("--force-jit");
  23. const MASK_ARITH = 1 | 1 << 2 | 1 << 4 | 1 << 6 | 1 << 7 | 1 << 11;
  24. try {
  25. var V86 = require("../../build/libv86-debug.js").V86;
  26. }
  27. catch(e) {
  28. console.error(e);
  29. console.error("Failed to import build/libv86-debug.js. Run " +
  30. "`make build/libv86-debug.js` first.");
  31. process.exit(1);
  32. }
  33. if(cluster.isMaster)
  34. {
  35. function extract_json(name, fixture_text)
  36. {
  37. if(fixture_text.includes("(signal SIGFPE)"))
  38. {
  39. return { exception: "DE", };
  40. }
  41. if(fixture_text.includes("(signal SIGILL)"))
  42. {
  43. return { exception: "UD", };
  44. }
  45. if(fixture_text.includes("Program received signal") || fixture_text.includes("SIGILL"))
  46. {
  47. throw new Error("Test was killed during execution by gdb: " + name + "\n" + fixture_text);
  48. }
  49. const json_regex = /---BEGIN JSON---([\s\[\]\w":\-,]*)---END JSON---/;
  50. const regex_match = json_regex.exec(fixture_text);
  51. if (!regex_match || regex_match.length < 2) {
  52. throw new Error("Could not find JSON in fixture text: " + fixture_text + "\nTest: " + name);
  53. }
  54. try {
  55. let array = JSON.parse(regex_match[1]);
  56. return { array: array };
  57. }
  58. catch (e) {
  59. throw e;
  60. }
  61. }
  62. function send_work_to_worker(worker, message) {
  63. if(current_test < tests.length) {
  64. const test = tests[current_test];
  65. worker.send(test);
  66. current_test++;
  67. }
  68. else {
  69. worker.send(TERMINATE_MSG);
  70. worker.disconnect();
  71. setTimeout(() => {
  72. // The emulator currently doesn't cleanly exit, so this is necessary
  73. console.log("Worker killed");
  74. worker.kill();
  75. }, 100);
  76. finished_workers++;
  77. if(finished_workers === nr_of_cpus)
  78. {
  79. test_finished();
  80. }
  81. }
  82. }
  83. const dir_files = fs.readdirSync(TEST_DIR);
  84. const files = dir_files.filter((name) => {
  85. return name.endsWith(".asm");
  86. }).map(name => {
  87. return name.slice(0, -4);
  88. });
  89. const tests = files.map(name => {
  90. let fixture_name = name + ".fixture";
  91. let img_name = name + ".img";
  92. let fixture_text = fs.readFileSync(TEST_DIR + fixture_name);
  93. let fixture = extract_json(name, fixture_text);
  94. return {
  95. img_name: img_name,
  96. fixture: fixture,
  97. };
  98. });
  99. const nr_of_cpus = Math.min(
  100. os.cpus().length || 1,
  101. tests.length,
  102. MAX_PARALLEL_TESTS
  103. );
  104. console.log("Using %d cpus", nr_of_cpus);
  105. let current_test = 0;
  106. let failed_tests = [];
  107. let finished_workers = 0;
  108. for(let i = 0; i < nr_of_cpus; i++)
  109. {
  110. let worker = cluster.fork();
  111. worker.on("message", function(message) {
  112. if (message !== DONE_MSG) {
  113. failed_tests.push(message);
  114. }
  115. send_work_to_worker(this);
  116. });
  117. worker.on("online", send_work_to_worker.bind(null, worker));
  118. worker.on("exit", function(code, signal) {
  119. if(code !== 0 && code !== null) {
  120. console.log("Worker error code:", code);
  121. process.exit(code);
  122. }
  123. });
  124. worker.on("error", function(error) {
  125. console.error("Worker error: ", error.toString(), error);
  126. process.exit(1);
  127. });
  128. }
  129. function test_finished()
  130. {
  131. console.log(
  132. "\n[+] Passed %d/%d tests.",
  133. tests.length - failed_tests.length,
  134. tests.length
  135. );
  136. if (failed_tests.length > 0) {
  137. console.log("[-] Failed %d test(s).", failed_tests.length);
  138. failed_tests.forEach(function(test_failure) {
  139. console.error("\n[-] %s:", test_failure.img_name);
  140. test_failure.failures.forEach(function(failure) {
  141. function format_value(v) {
  142. if(typeof v === "number")
  143. return "0x" + (v >>> 0).toString(16);
  144. else
  145. return String(v);
  146. }
  147. console.error("\n\t" + failure.name);
  148. console.error("\tActual: " + format_value(failure.actual));
  149. console.error("\tExpected: " + format_value(failure.expected));
  150. });
  151. });
  152. process.exit(1);
  153. }
  154. }
  155. }
  156. else {
  157. function run_test(test)
  158. {
  159. if(!loaded)
  160. {
  161. first_test = test;
  162. return;
  163. }
  164. waiting_for_test = false;
  165. current_test = test;
  166. console.info("Testing", test.img_name);
  167. var cpu = emulator.v86.cpu;
  168. console.assert(!emulator.running);
  169. cpu.reset();
  170. cpu.reset_memory();
  171. cpu.load_multiboot(fs.readFileSync(TEST_DIR + current_test.img_name).buffer);
  172. if(FORCE_JIT)
  173. {
  174. cpu.jit_force_generate_unsafe(cpu.instruction_pointer[0]);
  175. cpu.test_hook_did_finalize_wasm = function()
  176. {
  177. cpu.test_hook_did_finalize_wasm = null;
  178. // don't synchronously call into the emulator from this callback
  179. setTimeout(() => {
  180. emulator.run();
  181. }, 0);
  182. };
  183. }
  184. else
  185. {
  186. emulator.run();
  187. }
  188. }
  189. let loaded = false;
  190. let current_test = undefined;
  191. let first_test = undefined;
  192. let waiting_for_test = false;
  193. let emulator = new V86({
  194. autostart: false,
  195. memory_size: 2 * 1024 * 1024,
  196. log_level: 0,
  197. });
  198. emulator.add_listener("emulator-loaded", function()
  199. {
  200. loaded = true;
  201. if(first_test)
  202. {
  203. run_test(first_test);
  204. }
  205. });
  206. emulator.cpu_exception_hook = function(n)
  207. {
  208. if(waiting_for_test)
  209. {
  210. return true;
  211. }
  212. waiting_for_test = true;
  213. emulator.stop();
  214. const exceptions = {
  215. 0: "DE",
  216. 6: "UD",
  217. };
  218. const exception = exceptions[n];
  219. if(exception === undefined)
  220. {
  221. console.error("Unexpected CPU exception: " + n);
  222. process.exit(1);
  223. }
  224. if(current_test.fixture.exception !== exception)
  225. {
  226. process.send({
  227. failures: [{
  228. name: "Exception",
  229. actual: exception,
  230. expected: current_test.fixture.exception || "(none)",
  231. }],
  232. img_name: current_test.img_name,
  233. });
  234. }
  235. else
  236. {
  237. process.send(DONE_MSG);
  238. }
  239. return true;
  240. };
  241. emulator.bus.register("cpu-event-halt", function() {
  242. console.assert(!waiting_for_test);
  243. waiting_for_test = true;
  244. emulator.stop();
  245. var cpu = emulator.v86.cpu;
  246. const filename = TEST_DIR + current_test.img_name;
  247. const evaluated_mmxs = cpu.reg_mmxs;
  248. const evaluated_xmms = cpu.reg_xmm32s;
  249. const esp = cpu.reg32s[4];
  250. const evaluated_memory = new Int32Array(cpu.mem8.slice(0x120000 - 16 * 4, 0x120000).buffer);
  251. let individual_failures = [];
  252. if(current_test.exception)
  253. {
  254. throw "TODO: Handle exceptions";
  255. }
  256. console.assert(current_test.fixture.array);
  257. if(current_test.fixture.array)
  258. {
  259. let offset = 0;
  260. const expected_reg32s = current_test.fixture.array.slice(offset, offset += 8);
  261. const expected_mmx_registers = current_test.fixture.array.slice(offset, offset += 16);
  262. const expected_xmm_registers = current_test.fixture.array.slice(offset, offset += 32);
  263. const expected_memory = current_test.fixture.array.slice(offset, offset += 16);
  264. const expected_eflags = current_test.fixture.array[offset] & MASK_ARITH;
  265. for (let i = 0; i < cpu.reg32s.length; i++) {
  266. let reg = cpu.reg32s[i];
  267. if (reg !== expected_reg32s[i]) {
  268. individual_failures.push({
  269. name: "cpu.reg32s[" + i + "]",
  270. expected: expected_reg32s[i],
  271. actual: reg,
  272. });
  273. }
  274. }
  275. for (let i = 0; i < evaluated_mmxs.length; i++) {
  276. if (evaluated_mmxs[i] !== expected_mmx_registers[i]) {
  277. individual_failures.push({
  278. name: "mm" + (i >> 1) + ".int32[" + (i & 1) + "] (cpu.reg_mmx[" + i + "])",
  279. expected: expected_mmx_registers[i],
  280. actual: evaluated_mmxs[i],
  281. });
  282. }
  283. }
  284. for (let i = 0; i < evaluated_xmms.length; i++) {
  285. if (evaluated_xmms[i] !== expected_xmm_registers[i]) {
  286. individual_failures.push({
  287. name: "xmm" + (i >> 2) + ".int32[" + (i & 3) + "] (cpu.reg_xmm[" + i + "])",
  288. expected: expected_xmm_registers[i],
  289. actual: evaluated_xmms[i],
  290. });
  291. }
  292. }
  293. for (let i = 0; i < evaluated_memory.length; i++) {
  294. if (evaluated_memory[i] !== expected_memory[i]) {
  295. individual_failures.push({
  296. name: "mem[" + i + "]",
  297. expected: expected_memory[i],
  298. actual: evaluated_memory[i],
  299. });
  300. }
  301. }
  302. const seen_eflags = cpu.get_eflags() & MASK_ARITH;
  303. if(seen_eflags !== expected_eflags)
  304. {
  305. individual_failures.push({
  306. name: "eflags",
  307. expected: expected_eflags,
  308. actual: seen_eflags,
  309. });
  310. }
  311. }
  312. if (individual_failures.length > 0) {
  313. process.send({
  314. failures: individual_failures,
  315. img_name: current_test.img_name
  316. });
  317. }
  318. else {
  319. process.send(DONE_MSG);
  320. }
  321. });
  322. cluster.worker.on("message", function(message) {
  323. if(message === TERMINATE_MSG)
  324. {
  325. emulator.stop();
  326. emulator = null;
  327. }
  328. else
  329. {
  330. run_test(message);
  331. }
  332. });
  333. }