run.js 11 KB

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