modemwars/tools/traceCode.py
2026-08-23 02:09:40 -05:00

44 lines
2 KiB
Python

#!/usr/bin/env python3
# traceCode.py - recursive-descent trace of the game from known entry points over the runtime images;
# writes code coverage per image and reports indirect jumps / illegal opcodes / unreached areas.
import sys, json
from m6502 import trace, decode
LOADED = {
"boot": [(0x0400,0x8800),(0x8C00,0x9000),(0x9300,0xC400),(0xFBB8,0xFFD2)],
"runtime": [(0x0200,0x8A00),(0x8C00,0x9000),(0x9300,0xD000),(0xE000,0xF700),(0xFBB8,0xFFD2)],
}
def validFor(ranges):
return lambda a: any(lo <= a < hi for lo, hi in ranges)
def entriesFor(mem, extra):
e = {0x0800}
e |= set(extra)
return e
def run(imageName, ranges, extra, noReturn):
mem = open(imageName, "rb").read()
code, calls, jumps, indirects, illegal = trace(mem, entriesFor(mem, extra), validFor(ranges), noReturn=noReturn)
return mem, code, calls, jumps, indirects, illegal
def report(tag, mem, code, calls, jumps, indirects, illegal, ranges):
print(f"== {tag}: code bytes {len(code)}, subroutines {len(calls)}, jump targets {len(jumps)}, indirect jmps {len(indirects)}, illegal {len(illegal)}")
for a in indirects:
i = decode(mem, a)
print(f" jmp (${i.operand:04X}) at ${a:04X}")
for a in illegal[:20]:
print(f" illegal opcode ${mem[a]:02X} at ${a:04X}")
# unreached runs inside loaded ranges
total = sum(hi-lo for lo,hi in ranges)
print(f" coverage {len(code)}/{total} = {100*len(code)/total:.1f}%")
if __name__ == "__main__":
extra = [int(x,16) for x in sys.argv[1:]]
results = {}
for tag, img, rng in [("boot","../extracted/bootImage.bin",LOADED["boot"]),
("A","../extracted/runtimeImage_A.bin",LOADED["runtime"]),
("B","../extracted/runtimeImage_B.bin",LOADED["runtime"])]:
mem, code, calls, jumps, ind, ill = run(img, rng, extra, frozenset())
report(tag, mem, code, calls, jumps, ind, ill, rng)
results[tag] = sorted(code)
json.dump({k:[f"{a:04X}" for a in v] for k,v in results.items()}, open("../extracted/traceCode.json","w"))