run.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. #!/usr/bin/env node
  2. 'use strict';
  3. // Mapping between signals and x86 exceptions:
  4. // "Program received signal SIGILL, Illegal instruction." -> #UD
  5. // "Program received signal SIGFPE, Arithmetic exception." -> #GP
  6. // to be determined -> #GP
  7. // to be determined -> #NM
  8. // to be determined -> #TS
  9. // to be determined -> #NP
  10. // to be determined -> #SS
  11. // to be determined -> #PF
  12. // A #UD might indicate a bug in the test generation
  13. const fs = require('fs');
  14. const path = require('path');
  15. const os = require('os');
  16. const cluster = require('cluster');
  17. const MAX_PARALLEL_TESTS = +process.env.MAX_PARALLEL_TESTS || 99;
  18. const TEST_DIR = __dirname + "/build/";
  19. const DONE_MSG = 'DONE';
  20. const TERMINATE_MSG = 'DONE';
  21. const MASK_ARITH = 1 | 1 << 2 | 1 << 4 | 1 << 6 | 1 << 7 | 1 << 11;
  22. try {
  23. var V86 = require('../../build/libv86.js').V86Starter;
  24. }
  25. catch(e) {
  26. console.error('Failed to import build/libv86.js. Run ' +
  27. '`make build/libv86.js` first.');
  28. process.exit(1);
  29. }
  30. function h(n, len)
  31. {
  32. // pad string with zeros on the left
  33. function pad0(str, len)
  34. {
  35. str = str ? str + "" : "";
  36. while(str.length < len)
  37. {
  38. str = "0" + str;
  39. }
  40. return str;
  41. }
  42. if(!n)
  43. {
  44. var str = "";
  45. }
  46. else
  47. {
  48. var str = n.toString(16);
  49. }
  50. return "0x" + pad0(str.toUpperCase(), len || 1);
  51. }
  52. if (cluster.isMaster) {
  53. function extract_json(name, fixture_text) {
  54. if(fixture_text.includes("SIGFPE, Arithmetic exception"))
  55. {
  56. return { exception: "DE", };
  57. }
  58. if(fixture_text.includes("SIGILL, Illegal instruction"))
  59. {
  60. return { exception: "UD", };
  61. }
  62. if(fixture_text.includes("Program received signal") || fixture_text.includes("SIGILL"))
  63. {
  64. throw new Error("Test was killed during execution by gdb: " + name);
  65. }
  66. const json_regex = /---BEGIN JSON---([\s\[\]\w":\-,]*)---END JSON---/;
  67. const regex_match = json_regex.exec(fixture_text);
  68. if (!regex_match || regex_match.length < 2) {
  69. throw new Error('Could not find JSON in fixture text: ' + fixture_text + "\nTest: " + name);
  70. }
  71. try {
  72. let array = JSON.parse(regex_match[1]);
  73. return { array: array };
  74. }
  75. catch (e) {
  76. throw e;
  77. }
  78. }
  79. function send_work_to_worker(worker, message) {
  80. if(current_test < tests.length) {
  81. const test = tests[current_test];
  82. worker.send(test);
  83. current_test++;
  84. }
  85. else {
  86. worker.send(TERMINATE_MSG);
  87. worker.disconnect();
  88. setTimeout(() => {
  89. // The emulator currently doesn't cleanly exit, so this is necessary
  90. console.log("Worker killed");
  91. worker.kill();
  92. }, 100);
  93. finished_workers++;
  94. if(finished_workers === nr_of_cpus)
  95. {
  96. test_finished();
  97. }
  98. }
  99. }
  100. const dir_files = fs.readdirSync(TEST_DIR);
  101. const files = dir_files.filter((name) => {
  102. return name.endsWith(".asm");
  103. }).map(name => {
  104. return name.slice(0, -4);
  105. });
  106. const tests = files.map(name => {
  107. let fixture_name = name + ".fixture";
  108. let img_name = name + ".img";
  109. let fixture_text = fs.readFileSync(TEST_DIR + fixture_name);
  110. let fixture = extract_json(name, fixture_text);
  111. return {
  112. img_name: img_name,
  113. fixture: fixture,
  114. };
  115. });
  116. const nr_of_cpus = Math.min(
  117. os.cpus().length || 1,
  118. tests.length,
  119. MAX_PARALLEL_TESTS
  120. );
  121. console.log('Using %d cpus', nr_of_cpus);
  122. let current_test = 0;
  123. let failed_tests = [];
  124. let finished_workers = 0;
  125. for (let i = 0; i < nr_of_cpus; i++) {
  126. let worker = cluster.fork();
  127. worker.on('message', function(message) {
  128. if (message !== DONE_MSG) {
  129. failed_tests.push(message);
  130. }
  131. send_work_to_worker(this);
  132. });
  133. worker.on('online', send_work_to_worker.bind(null, worker));
  134. worker.on('exit', function(code, signal) {
  135. if(code !== 0 && code !== null) {
  136. console.log('Worker error code:', code);
  137. process.exit(code);
  138. }
  139. });
  140. worker.on('error', function(error) {
  141. console.error('Worker error: ', error.toString(), error);
  142. process.exit(1);
  143. });
  144. }
  145. function test_finished()
  146. {
  147. console.log(
  148. '\n[+] Passed %d/%d tests.',
  149. tests.length - failed_tests.length,
  150. tests.length
  151. );
  152. if (failed_tests.length > 0) {
  153. console.log('[-] Failed %d test(s).', failed_tests.length);
  154. failed_tests.forEach(function(test_failure) {
  155. console.error('\n[-] %s:', test_failure.img_name);
  156. test_failure.failures.forEach(function(individual_failure) {
  157. console.error("\n\t" + individual_failure.name);
  158. console.error("\tActual: 0x" + (individual_failure.actual >>> 0).toString(16));
  159. console.error("\tExpected: 0x" + (individual_failure.expected >>> 0).toString(16));
  160. });
  161. });
  162. process.exit(1);
  163. }
  164. }
  165. }
  166. else {
  167. function run_test(test)
  168. {
  169. if(!loaded)
  170. {
  171. first_test = test;
  172. return;
  173. }
  174. current_test = test;
  175. console.info('Testing', test.img_name);
  176. var cpu = emulator.v86.cpu;
  177. cpu.reset();
  178. cpu.reset_memory();
  179. cpu.load_multiboot(new Uint8Array(fs.readFileSync(TEST_DIR + current_test.img_name)).buffer);
  180. emulator.run();
  181. }
  182. let loaded = false;
  183. let current_test = undefined;
  184. let first_test = undefined;
  185. let emulator = new V86({
  186. autostart: false,
  187. memory_size: 2 * 1024 * 1024,
  188. });
  189. emulator.add_listener("emulator-loaded", function()
  190. {
  191. loaded = true;
  192. if(first_test)
  193. {
  194. run_test(first_test);
  195. }
  196. });
  197. emulator.bus.register('cpu-event-halt', function() {
  198. emulator.stop();
  199. var cpu = emulator.v86.cpu;
  200. const filename = TEST_DIR + current_test.img_name;
  201. const evaluated_mmxs = cpu.reg_mmxs;
  202. const evaluated_xmms = cpu.reg_xmm32s;
  203. const esp = cpu.reg32s[4];
  204. const evaluated_memory = new Int32Array(cpu.mem8.slice(0x120000 - 16 * 4, 0x120000).buffer);
  205. let individual_failures = [];
  206. if(current_test.exception)
  207. {
  208. throw "TODO: Handle exceptions";
  209. }
  210. console.assert(current_test.fixture.array);
  211. if(current_test.fixture.array)
  212. {
  213. let offset = 0;
  214. const expected_reg32s = current_test.fixture.array.slice(offset, offset += 8);
  215. const expected_mmx_registers = current_test.fixture.array.slice(offset, offset += 16);
  216. const expected_xmm_registers = current_test.fixture.array.slice(offset, offset += 32);
  217. const expected_memory = current_test.fixture.array.slice(offset, offset += 16);
  218. const expected_eflags = current_test.fixture.array[offset] & MASK_ARITH;
  219. for (let i = 0; i < cpu.reg32s.length; i++) {
  220. let reg = cpu.reg32s[i];
  221. if (reg !== expected_reg32s[i]) {
  222. individual_failures.push({
  223. name: "cpu.reg32s[" + i + "]",
  224. expected: expected_reg32s[i],
  225. actual: reg,
  226. });
  227. }
  228. }
  229. for (let i = 0; i < evaluated_mmxs.length; i++) {
  230. if (evaluated_mmxs[i] !== expected_mmx_registers[i]) {
  231. individual_failures.push({
  232. name: "mm" + (i >> 1) + ".int32[" + (i & 1) + "] (cpu.reg_mmx[" + i + "])",
  233. expected: expected_mmx_registers[i],
  234. actual: evaluated_mmxs[i],
  235. });
  236. }
  237. }
  238. for (let i = 0; i < evaluated_xmms.length; i++) {
  239. if (evaluated_xmms[i] !== expected_xmm_registers[i]) {
  240. individual_failures.push({
  241. name: "xmm" + (i >> 2) + ".int32[" + (i & 3) + "] (cpu.reg_xmm[" + i + "])",
  242. expected: expected_xmm_registers[i],
  243. actual: evaluated_xmms[i],
  244. });
  245. }
  246. }
  247. for (let i = 0; i < evaluated_memory.length; i++) {
  248. if (evaluated_memory[i] !== expected_memory[i]) {
  249. individual_failures.push({
  250. name: "mem[" + i + "]",
  251. expected: expected_memory[i],
  252. actual: evaluated_memory[i],
  253. });
  254. }
  255. }
  256. const seen_eflags = cpu.get_eflags() & MASK_ARITH;
  257. if(seen_eflags !== expected_eflags)
  258. {
  259. individual_failures.push({
  260. name: "eflags",
  261. expected: expected_eflags,
  262. actual: seen_eflags,
  263. });
  264. }
  265. }
  266. if (individual_failures.length > 0) {
  267. process.send({
  268. failures: individual_failures,
  269. img_name: current_test.img_name
  270. });
  271. }
  272. else {
  273. process.send(DONE_MSG);
  274. }
  275. });
  276. cluster.worker.on('message', function(message) {
  277. if(message === TERMINATE_MSG)
  278. {
  279. emulator.stop();
  280. emulator = null;
  281. }
  282. else
  283. {
  284. run_test(message);
  285. }
  286. });
  287. }