Files
vt1200/tools/ptr_constants.py
T

113 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""Find the TMS34010 bit-address constants in a VT1200 ROM image.
The firmware stores absolute **bit** addresses: a 32-bit value ``P`` refers to
physical byte ``P >> 3``. A byte-addressed tool such as Ghidra therefore cannot
resolve those constants directly. This tool scans an address-ordered image
(see ``reconstruct.py``) for 32-bit little-endian values whose ``>> 3`` falls
inside the ROM, and reports where each one points.
Typical uses:
python3 tools/ptr_constants.py vt1200_v22.bin
python3 tools/ptr_constants.py vt1200_v22.bin --tsv pointers.tsv
python3 tools/ptr_constants.py vt1200_v22.bin --strings
The report is the input for the next step: a Ghidra script that rewrites these
constants (``P`` -> ``P >> 3``) so cross-references resolve. See MEMORY_MAP.md.
"""
import argparse
import struct
import sys
ROM_BASE = 0x07E40000 # physical byte base of the ROM
DEFAULT_STEP = 2 # TMS34010 is bit-addressed; 2-byte alignment
MIN_STRING = 4 # printable run length that counts as a string
def classify(image, target):
"""Return 'string' if *target* starts a printable run, else 'other'.
Returns None when *target* is outside the image.
"""
if not 0 <= target < len(image):
return None
if 0x20 <= image[target] < 0x7F:
end = target
while end < len(image) and 0x20 <= image[end] < 0x7F:
end += 1
if end - target >= MIN_STRING:
return "string"
return "other"
def scan(image, base=ROM_BASE, step=DEFAULT_STEP):
"""Yield (offset, value, target_offset, kind) for each pointer constant.
*offset* is the file offset of the 32-bit constant, *value* its raw value,
*target_offset* the ROM-relative byte it refers to.
"""
size = len(image)
for off in range(0, size - 3, step):
value = struct.unpack_from("<I", image, off)[0]
rel = (value >> 3) - base
if 0 <= rel < size:
yield off, value, rel, classify(image, rel)
def cstring(image, offset, limit=64):
end = offset
while end < len(image) and end - offset < limit and 0x20 <= image[end] < 0x7F:
end += 1
return image[offset:end].decode("latin1")
def main(argv=None):
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("image", help="address-ordered ROM image")
ap.add_argument("--base", type=lambda s: int(s, 0), default=ROM_BASE,
help=f"ROM physical byte base (default 0x{ROM_BASE:X})")
ap.add_argument("--step", type=int, default=DEFAULT_STEP,
help=f"scan step in bytes (default {DEFAULT_STEP})")
ap.add_argument("--tsv", metavar="FILE",
help="write every hit as offset<TAB>value<TAB>target<TAB>kind")
ap.add_argument("--strings", action="store_true",
help="list distinct string targets")
args = ap.parse_args(argv)
image = open(args.image, "rb").read()
hits = list(scan(image, base=args.base, step=args.step))
strings = {}
other = 0
for off, value, tgt, kind in hits:
if kind == "string":
strings.setdefault(tgt, (off, value))
else:
other += 1
print(f"image : {args.image} ({len(image)} bytes)")
print(f"pointer constants: {len(hits)} "
f"({len(strings)} distinct string targets, {other} other)")
print(f"ROM byte range : 0x{args.base:08X}-0x{args.base + len(image) - 1:08X}")
print(f"bit range : 0x{args.base << 3:08X}-0x{((args.base + len(image)) << 3) - 1:08X}")
if args.strings:
print(f"\ndistinct string targets ({len(strings)}):")
for tgt in sorted(strings):
off, value = strings[tgt]
print(f" 0x{tgt:06X} <- 0x{value:08X} @file 0x{off:06X} {cstring(image, tgt)!r}")
if args.tsv:
with open(args.tsv, "w") as fh:
fh.write("offset\tvalue\ttarget\tkind\n")
for off, value, tgt, kind in hits:
fh.write(f"0x{off:06X}\t0x{value:08X}\t0x{tgt:06X}\t{kind}\n")
print(f"\nwrote {len(hits)} hits to {args.tsv}")
return 0
if __name__ == "__main__":
sys.exit(main())