create_tests.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. #!/usr/bin/env node
  2. "use strict";
  3. // number of tests per instruction
  4. const NO_TESTS = 1;
  5. const FLAGS_IGNORE = 0xFFFF3200;
  6. const assert = require("assert").strict;
  7. const fs = require("fs");
  8. const encodings = require("../../gen/x86_table.js");
  9. const Prand = require("./prand.js");
  10. generate_tests();
  11. function generate_tests()
  12. {
  13. const build_folder = __dirname + "/build/";
  14. try
  15. {
  16. fs.mkdirSync(build_folder);
  17. }
  18. catch(e)
  19. {
  20. if(e.code !== "EEXIST")
  21. {
  22. throw e;
  23. }
  24. }
  25. for(const op of encodings)
  26. {
  27. const configurations = [
  28. { mem: 0, size: 16, },
  29. { mem: 0, size: 32, },
  30. { mem: 1, size: 16, },
  31. { mem: 1, size: 32, },
  32. ];
  33. let i = 0;
  34. for(const config of configurations)
  35. {
  36. for(let nth_test = 0; nth_test < NO_TESTS; nth_test++)
  37. {
  38. if(nth_test > 0 && op.opcode === 0x8D)
  39. {
  40. // is already tested exhaustively in first run
  41. continue;
  42. }
  43. for(const code of create_nasm(op, config, nth_test))
  44. {
  45. const filename = "gen_" + format_opcode(op.opcode) + "_" + (op.fixed_g || 0) + "_" + i + ".asm";
  46. const dirname = build_folder + filename;
  47. let old_code = undefined;
  48. try
  49. {
  50. old_code = fs.readFileSync(dirname, { encoding: "ascii" });
  51. }
  52. catch(e)
  53. {
  54. }
  55. if(old_code !== code)
  56. {
  57. console.log("Creating %s", filename);
  58. fs.writeFileSync(dirname, code);
  59. }
  60. i++;
  61. }
  62. }
  63. }
  64. }
  65. }
  66. function format_opcode(n)
  67. {
  68. let x = n.toString(16);
  69. return (x.length === 1 || x.length === 3) ? "0" + x : x;
  70. }
  71. function create_nasm_modrm_combinations_16()
  72. {
  73. let result = [];
  74. for(let modrm = 0; modrm < 0xC0; modrm++)
  75. {
  76. let mod = modrm >> 6;
  77. let rm = modrm & 7;
  78. let has_imm8 = mod === 1;
  79. let has_imm16 = mod === 2 || rm === 6 && mod === 0;
  80. assert(!has_imm8 || !has_imm16);
  81. let line = ["db " + modrm];
  82. if(has_imm8) line.push("db 9ah");
  83. if(has_imm16) line.push("dw 9a1fh");
  84. result.push(line);
  85. }
  86. return result;
  87. }
  88. function create_nasm_modrm_combinations_32()
  89. {
  90. let result = [];
  91. let sample_sib_bytes = [0x05, 0x65, 0xAD, 0xCD, 0x20, 0xFF];
  92. let exhaustive_sib_bytes = [];
  93. for(let sib = 0; sib < 0x100; sib++) exhaustive_sib_bytes.push(sib);
  94. for(let modrm = 0; modrm < 0xC0; modrm++)
  95. {
  96. let mod = modrm >> 6;
  97. let reg = modrm >> 3 & 7;
  98. let rm = modrm & 7;
  99. let has_imm8 = mod === 1;
  100. let has_imm32 = mod === 2 || rm === 5 && mod === 0;
  101. let has_sib = rm === 4;
  102. assert(!has_imm8 || !has_imm32);
  103. if(has_sib)
  104. {
  105. // avoid generating an excessive number of tests
  106. let sib_bytes = reg === 0 ? exhaustive_sib_bytes : sample_sib_bytes;
  107. for(let sib of sib_bytes)
  108. {
  109. let line = ["db " + modrm, "db " + sib];
  110. if(has_imm8) line.push("db 9ah");
  111. if(has_imm32 || mod === 0 && (sib & 7) === 5) line.push("dd 9a1fbcdeh");
  112. result.push(line);
  113. }
  114. }
  115. else
  116. {
  117. let line = ["db " + modrm];
  118. if(has_imm8) line.push("db 9ah");
  119. if(has_imm32) line.push("dd 9a1fbcdeh");
  120. result.push(line);
  121. }
  122. }
  123. return result;
  124. }
  125. function create_nasm(op, config, nth_test)
  126. {
  127. if(op.prefix || op.skip)
  128. {
  129. return [];
  130. }
  131. if(config.mem ? op.skip_mem : op.skip_reg)
  132. {
  133. // Not supported by test
  134. return [];
  135. }
  136. if(!op.e)
  137. {
  138. if(config.mem)
  139. {
  140. // doesn't use memory, don't test both
  141. return [];
  142. }
  143. }
  144. if(!op.os)
  145. {
  146. if(config.size === 16)
  147. {
  148. // equivalent to 32-bit version, don't test both
  149. return [];
  150. }
  151. }
  152. const op_rand = new Prand(op.opcode + nth_test * 0x10000);
  153. const size = (op.os || op.opcode % 2 === 1) ? config.size : 8;
  154. const is_modrm = op.e || op.fixed_g !== undefined;
  155. const codes = [];
  156. for(let reg of ["eax", "ecx", "edx", "ebx", "ebp", "esi", "edi"])
  157. {
  158. let rand = op_rand.next();
  159. codes.push("mov " + reg + ", " + rand);
  160. }
  161. if(!op.is_fpu) // generate random mmx registers
  162. {
  163. codes.push("sub esp, 8");
  164. for(let i = 0; i < 8; i++)
  165. {
  166. codes.push("mov dword [esp], " + op_rand.next());
  167. codes.push("mov dword [esp + 4], " + op_rand.next());
  168. codes.push("movq mm" + i + ", [esp]");
  169. }
  170. codes.push("add esp, 8");
  171. }
  172. else // generate random fpu registers
  173. {
  174. codes.push("finit");
  175. codes.push("sub esp, 8");
  176. for(let i = 0; i < 8; i++)
  177. {
  178. codes.push("mov dword [esp], " + op_rand.next());
  179. codes.push("mov dword [esp + 4], " + op_rand.next());
  180. codes.push("fld qword [esp]");
  181. }
  182. for(let i = 0; i < 4; i++) // half full stack
  183. {
  184. codes.push("fstp qword [esp]");
  185. }
  186. codes.push("add esp, 8");
  187. }
  188. if(true) // generate random xmm registers
  189. {
  190. codes.push("sub esp, 16");
  191. for(let i = 0; i < 8; i++)
  192. {
  193. codes.push("mov dword [esp], " + op_rand.next());
  194. codes.push("mov dword [esp + 4], " + op_rand.next());
  195. codes.push("mov dword [esp + 8], " + op_rand.next());
  196. codes.push("mov dword [esp + 12], " + op_rand.next());
  197. codes.push("movdqu xmm" + i + ", [esp]");
  198. }
  199. codes.push("add esp, 16");
  200. }
  201. if(true) // generate random stack memory
  202. {
  203. for(let i = 0; i < 8; i++)
  204. {
  205. codes.push("sub esp, 4");
  206. codes.push("mov dword [esp], " + op_rand.next());
  207. }
  208. }
  209. codes.push("push dword " + (op_rand.next() & ~(1 << 8 | 1 << 9)));
  210. codes.push("popf");
  211. if(true)
  212. {
  213. // generate random flags using arithmatic instruction
  214. // not well-distributed, but can trigger bugs in lazy flag calculation
  215. if(true)
  216. {
  217. // rarely sets zero flag, other flags mostly well-distributed
  218. codes.push("add al, ah");
  219. }
  220. else
  221. {
  222. // always sets zero flag
  223. codes.push("sub al, al");
  224. }
  225. }
  226. if(op.is_string)
  227. {
  228. codes.push("mov ecx, 3");
  229. codes.push("mov edi, (102000h-16)");
  230. codes.push("mov esi, (102000h-20)");
  231. }
  232. if(size === 16)
  233. {
  234. codes.push("db 66h ; 16 bit");
  235. }
  236. let opcode = op.opcode;
  237. if([0x0FA5, 0x0FAD].includes(op.opcode) && size === 16)
  238. {
  239. // shld/shrd: immediates larger than opsize are undefined behaviour,
  240. // but it's anded with 31 automatically, so only bit 4 needs to be cleared
  241. codes.push("and cl, ~16");
  242. }
  243. if(opcode === 0x8D)
  244. {
  245. // special case: lea: generate 16-bit addressing and all modrm combinations
  246. assert(is_modrm);
  247. codes.push([].concat(
  248. create_nasm_modrm_combinations_16().map(lines => ["db 67h", "db 8dh"].concat(lines).join("\n")),
  249. create_nasm_modrm_combinations_32().map(lines => ["db 8dh"].concat(lines).join("\n"))
  250. ));
  251. }
  252. else
  253. {
  254. assert(opcode < 0x1000000);
  255. if(opcode >= 0x10000)
  256. {
  257. let c = opcode >> 16;
  258. assert(c === 0x66 || c === 0xF3 || c === 0xF2);
  259. codes.push("db " + c);
  260. opcode &= ~0xFF0000;
  261. }
  262. if(opcode >= 0x100)
  263. {
  264. let c = opcode >> 8;
  265. assert(c === 0x0F || c === 0xF2 || c === 0xF3, "Expected 0F, F2, or F3 prefix, got " + c.toString(16));
  266. codes.push("db " + c);
  267. opcode &= ~0xFF00;
  268. }
  269. codes.push("db " + opcode);
  270. if(is_modrm)
  271. {
  272. let g = 7; // edi / di / bh
  273. if(op.fixed_g !== undefined)
  274. {
  275. g = op.fixed_g;
  276. }
  277. if(config.mem)
  278. {
  279. const e = 0x04; // [esp]
  280. const sib = 0x24;
  281. codes.push("db " + (e | g << 3));
  282. codes.push("db " + sib);
  283. }
  284. else
  285. {
  286. const es = op.is_fpu ? [0, 1, 2, 3, 4, 5, 6, 7] : [
  287. 2 // edx
  288. ];
  289. const modrm_bytes = es.map(e => "db " + (0xC0 | g << 3 | e));
  290. codes.push(modrm_bytes);
  291. }
  292. }
  293. }
  294. if(op.opcode === 0xC8) // special case: enter
  295. {
  296. codes.push("dw 8h");
  297. codes.push("db 0h");
  298. }
  299. else if(op.imm8 || op.imm8s || op.imm16 || op.imm1632 || op.imm32 || op.immaddr)
  300. {
  301. if(op.imm8 || op.imm8s)
  302. {
  303. if([0x0FA4, 0x0FAC].includes(op.opcode))
  304. {
  305. // shld/shrd: immediates larger than opsize are undefined behaviour
  306. codes.push("db 0fh");
  307. }
  308. else
  309. {
  310. codes.push("db 12h");
  311. }
  312. }
  313. else
  314. {
  315. if(op.immaddr)
  316. {
  317. // immaddr: depends on address size
  318. // generate valid pointer into bss section
  319. codes.push("dd (102000h-16)");
  320. }
  321. else
  322. {
  323. assert(op.imm1632 || op.imm16 || op.imm32);
  324. if(op.imm1632 && size === 16 || op.imm16)
  325. {
  326. codes.push("dw 34cdh");
  327. }
  328. else
  329. {
  330. assert(op.imm1632 && size === 32 || op.imm32);
  331. codes.push("dd 1234abcdh");
  332. }
  333. }
  334. }
  335. }
  336. if(op.mask_flags)
  337. {
  338. codes.push(
  339. "pushf",
  340. "and dword [esp], ~" + (op.mask_flags | FLAGS_IGNORE),
  341. "popf"
  342. );
  343. }
  344. if(op.opcode === 0x06 || op.opcode === 0x0E || op.opcode === 0x16 || op.opcode === 0x1E ||
  345. op.opcode === 0x0FA0 || op.opcode === 0x0FA8)
  346. {
  347. // push sreg: mask result
  348. if(size === 16)
  349. {
  350. codes.push("mov word [esp], 0");
  351. }
  352. else
  353. {
  354. // NOTE: upper word is undefined behaviour (unchanged on Intel, zero on AMD)
  355. codes.push("mov dword [esp], 0");
  356. }
  357. }
  358. return all_combinations(codes).map(c => {
  359. return (
  360. "global _start\n" +
  361. '%include "header.inc"\n\n' +
  362. c.join("\n") + "\n" +
  363. '%include "footer.inc"\n'
  364. );
  365. });
  366. }
  367. function all_combinations(xs)
  368. {
  369. let result = [xs];
  370. for(let i = 0; i < xs.length; i++)
  371. {
  372. let x = xs[i];
  373. if(x instanceof Array)
  374. {
  375. let new_result = [];
  376. for(let r of result)
  377. {
  378. for(let x_ of x)
  379. {
  380. r = r.slice();
  381. r[i] = x_;
  382. new_result.push(r);
  383. }
  384. }
  385. result = new_result;
  386. }
  387. }
  388. return result;
  389. }