fcml_vs_objdump.py 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. #!/usr/bin/env python3
  2. """fcml_vs_objdump.py -- differential test between two 16-bit disassemblors.
  3. Compares the *length of the first instruction* over many byte windows. A
  4. length disagreement is the sharpest possible signal that the two tools read
  5. the ModRM/displacement/SIB fields differently, because if the lengths agree
  6. the tools walked the same opcode table.
  7. Usage: fcml_vs_objdump.py [trials] [--seed N]
  8. """
  9. import os
  10. import random
  11. import re
  12. import subprocess
  13. import sys
  14. import tempfile
  15. RE_LEN = re.compile(r"^\s*Instruction code length:\s*(\d+)\s*$", re.M)
  16. RE_ADDR = re.compile(r"^\s*([0-9a-f]+):\t", re.M)
  17. def fcml_first_len(win):
  18. hexs = "".join("%02X" % b for b in win)
  19. out = subprocess.run(["fcml-disasm", "-m16", "-rh", "-rz", "0x" + hexs],
  20. capture_output=True, text=True)
  21. if out.returncode != 0:
  22. return None
  23. m = RE_LEN.search(out.stdout)
  24. return int(m.group(1)) if m else None
  25. def objdump_first_len(win, tmp):
  26. with open(tmp, "wb") as f:
  27. f.write(win)
  28. out = subprocess.run(["objdump", "-D", "-b", "binary", "-m", "i8086", tmp],
  29. capture_output=True, text=True)
  30. if out.returncode != 0:
  31. return None
  32. addrs = [int(a, 16) for a in RE_ADDR.findall(out.stdout)]
  33. if not addrs:
  34. return None
  35. if len(addrs) == 1:
  36. # single instruction covering the whole window
  37. return len(win)
  38. return addrs[1] - addrs[0]
  39. def main(argv):
  40. trials = 3000
  41. seed = 1
  42. args = [a for a in argv[1:]]
  43. if "--seed" in args:
  44. i = args.index("--seed")
  45. seed = int(args[i + 1])
  46. del args[i:i + 2]
  47. if args:
  48. trials = int(args[0])
  49. rnd = random.Random(seed)
  50. tmp = os.path.join(tempfile.gettempdir(), "diffprobe.bin")
  51. agree = 0
  52. one_err = 0
  53. diffs = []
  54. for t in range(trials):
  55. n = rnd.choice([4, 6, 8, 10, 12, 15])
  56. win = bytes(rnd.randrange(256) for _ in range(n))
  57. a = fcml_first_len(win)
  58. b = objdump_first_len(win, tmp)
  59. if a is None or b is None:
  60. one_err += 1
  61. continue
  62. if a == b:
  63. agree += 1
  64. else:
  65. diffs.append((win.hex(" ").upper(), a, b))
  66. total = agree + len(diffs)
  67. print("seed=%d trials=%d compared=%d agree=%d (%d skipped: one tool "
  68. "rejected the input) disagreements=%d"
  69. % (seed, trials, total, agree, one_err, len(diffs)))
  70. for h, a, b in diffs[:25]:
  71. print(" %-32s fcml=%-3s objdump=%-3s" % (h, a, b))
  72. return 1 if diffs else 0
  73. if __name__ == "__main__":
  74. sys.exit(main(sys.argv))