271 lines
8.8 KiB
Python
271 lines
8.8 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""dm_xref.py -- Extract all DM address references from PM code
|
||
|
|
and show what's stored at those addresses in a DM dump.
|
||
|
|
|
||
|
|
Finds:
|
||
|
|
- I-register loads (I0=0x100 → DM base address)
|
||
|
|
- Direct DM access (DM(0x1234) = reg / reg = DM(0x1234))
|
||
|
|
- DM immediate modify (reg = DM(I0 += offset))
|
||
|
|
- L-register loads (circular buffer lengths)
|
||
|
|
- CNTR loads (loop counts)
|
||
|
|
|
||
|
|
Usage:
|
||
|
|
python3 dm_xref.py firmware_pm.bin firmware_dm.bin
|
||
|
|
python3 dm_xref.py firmware_pm.bin firmware_dm.bin --context 16
|
||
|
|
"""
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import os
|
||
|
|
import struct
|
||
|
|
import sys
|
||
|
|
|
||
|
|
|
||
|
|
REG0 = ["AX0", "AX1", "MX0", "MX1", "AY0", "AY1", "MY0", "MY1",
|
||
|
|
"MR2", "SR2", "AR", "SI", "MR1", "SR1", "MR0", "SR0"]
|
||
|
|
REG1 = ["I0", "I1", "I2", "I3", "M0", "M1", "M2", "M3",
|
||
|
|
"L0", "L1", "L2", "L3", "IMASK", "IRPTL", "ICNTL", "STACKA"]
|
||
|
|
REG2 = ["I4", "I5", "I6", "I7", "M4", "M5", "M6", "M7",
|
||
|
|
"L4", "L5", "L6", "L7", "RES", "RES", "CNTR", "LPSTACKA"]
|
||
|
|
|
||
|
|
|
||
|
|
def read_pm(path):
|
||
|
|
data = open(path, "rb").read()
|
||
|
|
words = []
|
||
|
|
for i in range(0, len(data) - 2, 3):
|
||
|
|
w = (data[i] << 16) | (data[i + 1] << 8) | data[i + 2]
|
||
|
|
words.append(w)
|
||
|
|
return words
|
||
|
|
|
||
|
|
|
||
|
|
def read_dm(path, endian="big"):
|
||
|
|
fmt = ">H" if endian == "big" else "<H"
|
||
|
|
data = open(path, "rb").read()
|
||
|
|
if len(data) % 2:
|
||
|
|
data += b"\x00"
|
||
|
|
return [struct.unpack_from(fmt, data, i)[0]
|
||
|
|
for i in range(0, len(data), 2)]
|
||
|
|
|
||
|
|
|
||
|
|
def signed16(v):
|
||
|
|
return v - 0x10000 if v >= 0x8000 else v
|
||
|
|
|
||
|
|
|
||
|
|
def extract_dm_refs(pm_words):
|
||
|
|
"""Extract all DM address references from PM code."""
|
||
|
|
refs = []
|
||
|
|
|
||
|
|
for i, w in enumerate(pm_words):
|
||
|
|
pm_addr = i * 3
|
||
|
|
b23_22 = (w >> 22) & 3
|
||
|
|
b21_20 = (w >> 20) & 3
|
||
|
|
|
||
|
|
# Type 6: Dreg = Imm16 (data constants, not DM refs)
|
||
|
|
# Skip — these load values, not addresses
|
||
|
|
|
||
|
|
# Type 7 REG1: I/M/L register loads
|
||
|
|
if b23_22 == 1 and b21_20 == 1:
|
||
|
|
val = (w >> 4) & 0xFFFF
|
||
|
|
reg = REG1[w & 0xF]
|
||
|
|
if reg.startswith("I"):
|
||
|
|
refs.append({
|
||
|
|
"pm_addr": pm_addr,
|
||
|
|
"type": "I-reg load",
|
||
|
|
"reg": reg,
|
||
|
|
"dm_addr": val,
|
||
|
|
"desc": f"{reg} = 0x{val:04X}"
|
||
|
|
})
|
||
|
|
elif reg.startswith("L"):
|
||
|
|
refs.append({
|
||
|
|
"pm_addr": pm_addr,
|
||
|
|
"type": "L-reg (buf len)",
|
||
|
|
"reg": reg,
|
||
|
|
"dm_addr": None,
|
||
|
|
"value": val,
|
||
|
|
"desc": f"{reg} = {val} (buffer length)"
|
||
|
|
})
|
||
|
|
|
||
|
|
# Type 7 REG2: I4-I7/M4-M7/L4-L7/CNTR loads
|
||
|
|
if b23_22 == 0 and b21_20 == 3:
|
||
|
|
val = (w >> 4) & 0xFFFF
|
||
|
|
reg = REG2[w & 0xF]
|
||
|
|
if reg.startswith("I"):
|
||
|
|
refs.append({
|
||
|
|
"pm_addr": pm_addr,
|
||
|
|
"type": "I-reg load",
|
||
|
|
"reg": reg,
|
||
|
|
"dm_addr": val,
|
||
|
|
"desc": f"{reg} = 0x{val:04X}"
|
||
|
|
})
|
||
|
|
elif reg.startswith("L"):
|
||
|
|
refs.append({
|
||
|
|
"pm_addr": pm_addr,
|
||
|
|
"type": "L-reg (buf len)",
|
||
|
|
"reg": reg,
|
||
|
|
"dm_addr": None,
|
||
|
|
"value": val,
|
||
|
|
"desc": f"{reg} = {val} (buffer length)"
|
||
|
|
})
|
||
|
|
elif reg == "CNTR":
|
||
|
|
refs.append({
|
||
|
|
"pm_addr": pm_addr,
|
||
|
|
"type": "CNTR",
|
||
|
|
"reg": reg,
|
||
|
|
"dm_addr": None,
|
||
|
|
"value": val,
|
||
|
|
"desc": f"CNTR = {val} (loop count)"
|
||
|
|
})
|
||
|
|
|
||
|
|
# Type 3: Direct DM access — DM(addr16) = reg / reg = DM(addr16)
|
||
|
|
if b23_22 == 2:
|
||
|
|
d = (w >> 20) & 1
|
||
|
|
addr = (w >> 4) & 0xFFFF
|
||
|
|
reg_idx = w & 0xF
|
||
|
|
is_ireg = (w >> 21) & 1
|
||
|
|
rname = REG1[reg_idx] if is_ireg else REG0[reg_idx]
|
||
|
|
direction = "write" if d else "read"
|
||
|
|
refs.append({
|
||
|
|
"pm_addr": pm_addr,
|
||
|
|
"type": f"DM direct {direction}",
|
||
|
|
"reg": rname,
|
||
|
|
"dm_addr": addr,
|
||
|
|
"desc": (f"DM(0x{addr:04X}) = {rname}" if d
|
||
|
|
else f"{rname} = DM(0x{addr:04X})")
|
||
|
|
})
|
||
|
|
|
||
|
|
return refs
|
||
|
|
|
||
|
|
|
||
|
|
def show_dm_content(dm_words, dm_addr, context=8):
|
||
|
|
"""Format DM content at a given address."""
|
||
|
|
if dm_addr is None or dm_addr >= len(dm_words):
|
||
|
|
return " (outside DM range)"
|
||
|
|
|
||
|
|
end = min(dm_addr + context, len(dm_words))
|
||
|
|
hex_vals = []
|
||
|
|
dec_vals = []
|
||
|
|
for j in range(dm_addr, end):
|
||
|
|
v = dm_words[j]
|
||
|
|
hex_vals.append(f"0x{v:04X}")
|
||
|
|
dec_vals.append(f"{signed16(v):6d}")
|
||
|
|
|
||
|
|
lines = []
|
||
|
|
lines.append(f" Hex: [{', '.join(hex_vals)}]")
|
||
|
|
lines.append(f" Dec: [{', '.join(dec_vals)}]")
|
||
|
|
|
||
|
|
# Check if it looks like Q15 coefficients
|
||
|
|
vals = [signed16(dm_words[j]) for j in range(dm_addr, end)]
|
||
|
|
if all(abs(v) <= 32767 for v in vals):
|
||
|
|
q15 = [f"{v/32768:.4f}" for v in vals]
|
||
|
|
lines.append(f" Q15: [{', '.join(q15)}]")
|
||
|
|
|
||
|
|
# Check for all zeros
|
||
|
|
if all(v == 0 for v in vals):
|
||
|
|
lines.append(f" (all zeros — uninitialized)")
|
||
|
|
|
||
|
|
return "\n".join(lines)
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
parser = argparse.ArgumentParser(
|
||
|
|
description="Extract DM references from PM code and"
|
||
|
|
" show DM contents.")
|
||
|
|
parser.add_argument("pm_file", help="PM binary (packed 3-byte)")
|
||
|
|
parser.add_argument("dm_file", help="DM binary (16-bit words)")
|
||
|
|
parser.add_argument("--endian", choices=["big", "little"],
|
||
|
|
default="big")
|
||
|
|
parser.add_argument("--context", type=int, default=8,
|
||
|
|
help="Words of DM context to show"
|
||
|
|
" (default: 8)")
|
||
|
|
args = parser.parse_args()
|
||
|
|
|
||
|
|
for f in (args.pm_file, args.dm_file):
|
||
|
|
if not os.path.isfile(f):
|
||
|
|
print(f"Error: {f} not found", file=sys.stderr)
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
pm_words = read_pm(args.pm_file)
|
||
|
|
dm_words = read_dm(args.dm_file, args.endian)
|
||
|
|
|
||
|
|
refs = extract_dm_refs(pm_words)
|
||
|
|
|
||
|
|
print(f"PM: {args.pm_file} ({len(pm_words)} instructions)")
|
||
|
|
print(f"DM: {args.dm_file} ({len(dm_words)} words)")
|
||
|
|
print()
|
||
|
|
|
||
|
|
# Group by type
|
||
|
|
ireg_loads = [r for r in refs if r["type"] == "I-reg load"]
|
||
|
|
direct_access = [r for r in refs
|
||
|
|
if r["type"].startswith("DM direct")]
|
||
|
|
buf_lengths = [r for r in refs
|
||
|
|
if r["type"] == "L-reg (buf len)"]
|
||
|
|
counters = [r for r in refs if r["type"] == "CNTR"]
|
||
|
|
|
||
|
|
# I-register loads → buffer base addresses
|
||
|
|
if ireg_loads:
|
||
|
|
print(f"=== Buffer Base Addresses"
|
||
|
|
f" ({len(ireg_loads)} I-reg loads) ===")
|
||
|
|
seen = {}
|
||
|
|
for r in ireg_loads:
|
||
|
|
key = (r["reg"], r["dm_addr"])
|
||
|
|
if key not in seen:
|
||
|
|
seen[key] = r
|
||
|
|
for (reg, dm_addr), r in sorted(seen.items()):
|
||
|
|
print(f"\n{reg} = 0x{dm_addr:04X}"
|
||
|
|
f" (PM @ 0x{r['pm_addr']:06X})")
|
||
|
|
# Find matching L-register for buffer length
|
||
|
|
lreg = reg.replace("I", "L")
|
||
|
|
for lr in buf_lengths:
|
||
|
|
if lr["reg"] == lreg:
|
||
|
|
print(f" {lreg} = {lr['value']}"
|
||
|
|
f" (circular buffer, {lr['value']} words)")
|
||
|
|
break
|
||
|
|
print(show_dm_content(dm_words, dm_addr, args.context))
|
||
|
|
print()
|
||
|
|
|
||
|
|
# Direct DM accesses
|
||
|
|
if direct_access:
|
||
|
|
print(f"=== Direct DM Accesses"
|
||
|
|
f" ({len(direct_access)} found) ===")
|
||
|
|
seen_addrs = {}
|
||
|
|
for r in direct_access:
|
||
|
|
a = r["dm_addr"]
|
||
|
|
if a not in seen_addrs:
|
||
|
|
seen_addrs[a] = []
|
||
|
|
seen_addrs[a].append(r)
|
||
|
|
|
||
|
|
for dm_addr in sorted(seen_addrs.keys()):
|
||
|
|
ops = seen_addrs[dm_addr]
|
||
|
|
descs = [r["desc"] for r in ops[:3]]
|
||
|
|
more = f" +{len(ops)-3} more" if len(ops) > 3 else ""
|
||
|
|
print(f"\nDM 0x{dm_addr:04X}:")
|
||
|
|
for d in descs:
|
||
|
|
print(f" {d}")
|
||
|
|
if more:
|
||
|
|
print(f" {more}")
|
||
|
|
print(show_dm_content(dm_words, dm_addr, args.context))
|
||
|
|
print()
|
||
|
|
|
||
|
|
# Loop counters
|
||
|
|
if counters:
|
||
|
|
print(f"=== Loop Counters ({len(counters)} found) ===")
|
||
|
|
for r in counters:
|
||
|
|
print(f" PM 0x{r['pm_addr']:06X}: {r['desc']}")
|
||
|
|
print()
|
||
|
|
|
||
|
|
# Summary
|
||
|
|
all_dm_addrs = set()
|
||
|
|
for r in refs:
|
||
|
|
if r.get("dm_addr") is not None:
|
||
|
|
all_dm_addrs.add(r["dm_addr"])
|
||
|
|
|
||
|
|
print(f"=== Summary ===")
|
||
|
|
print(f" Unique DM addresses referenced: {len(all_dm_addrs)}")
|
||
|
|
print(f" Buffer bases (I-reg): {len(ireg_loads)}")
|
||
|
|
print(f" Direct DM access: {len(direct_access)}")
|
||
|
|
print(f" Buffer lengths (L-reg): {len(buf_lengths)}")
|
||
|
|
print(f" Loop counters (CNTR): {len(counters)}")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|