Files
adsp219x-re/tools/analyze_dm.py

252 lines
7.6 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""analyze_dm.py -- Analyze ADSP-219x DM (Data Memory) dumps.
Scans a 16-bit DM binary for:
- Null regions (uninitialized / BSS)
- Coefficient tables (Q15 fixed-point patterns)
- Lookup tables (periodic waveforms)
- ASCII strings
- Non-zero data regions
Usage:
python3 analyze_dm.py firmware_dm.bin
python3 analyze_dm.py firmware_dm.bin --endian little
"""
import argparse
import math
import os
import struct
import sys
def read_words(path, endian="big"):
"""Read 16-bit words from a binary file."""
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):
"""Convert unsigned 16-bit to signed."""
return v - 0x10000 if v >= 0x8000 else v
def find_null_regions(words, min_run=8):
"""Find contiguous runs of zero words."""
regions = []
start = None
for i, w in enumerate(words):
if w == 0:
if start is None:
start = i
else:
if start is not None and (i - start) >= min_run:
regions.append((start, i - start))
start = None
if start is not None and (len(words) - start) >= min_run:
regions.append((start, len(words) - start))
return regions
def find_coeff_tables(words, min_len=8, max_magnitude=0x7FFF):
"""Find potential Q15 coefficient tables.
Looks for runs of non-zero values where all values are
within the Q15 range and show some variance (not constant).
"""
tables = []
start = None
run = []
for i, w in enumerate(words):
sw = signed16(w)
if w != 0 and abs(sw) <= max_magnitude:
if start is None:
start = i
run = []
run.append(sw)
else:
if start is not None and len(run) >= min_len:
mn = min(run)
mx = max(run)
if mx != mn: # not constant
tables.append((start, len(run), mn, mx,
sum(run) / len(run)))
start = None
run = []
if start is not None and len(run) >= min_len:
mn = min(run)
mx = max(run)
if mx != mn:
tables.append((start, len(run), mn, mx,
sum(run) / len(run)))
return tables
def detect_periodicity(words, start, length):
"""Check if a region has periodic content (sine/cosine table)."""
if length < 16:
return None
vals = [signed16(words[start + i]) for i in range(length)]
# Try periods from 16 to length/2
best_period = None
best_err = float("inf")
for period in range(16, min(length // 2 + 1, 1025)):
if length < period * 2:
continue
err = 0.0
count = 0
for i in range(period, min(length, period * 4)):
diff = vals[i] - vals[i % period]
err += diff * diff
count += 1
if count > 0:
rms = math.sqrt(err / count)
if rms < best_err:
best_err = rms
best_period = period
if best_period and best_err < 500:
return best_period
return None
def find_strings(data, min_len=4):
"""Find ASCII strings in raw bytes."""
strings = []
current = []
start = None
for i, byte in enumerate(data):
if 0x20 <= byte < 0x7F:
if start is None:
start = i
current.append(chr(byte))
else:
if current and len(current) >= min_len:
strings.append((start, "".join(current)))
current = []
start = None
if current and len(current) >= min_len:
strings.append((start, "".join(current)))
return strings
def analyze(path, endian="big"):
"""Run full analysis on a DM dump."""
data = open(path, "rb").read()
size = len(data)
words = read_words(path, endian)
n_words = len(words)
print(f"File: {path}")
print(f"Size: {size} bytes ({n_words} words, 16-bit {endian}-endian)")
print()
# Null regions
nulls = find_null_regions(words)
if nulls:
total_null = sum(n for _, n in nulls)
print(f"=== Null Regions ({len(nulls)} found, "
f"{total_null} words = {total_null*100//n_words}%) ===")
for addr, length in nulls:
print(f" DM 0x{addr:04X} - 0x{addr+length-1:04X}"
f" ({length} words, {length*2} bytes)")
print()
# Coefficient tables
coeffs = find_coeff_tables(words)
if coeffs:
print(f"=== Potential Coefficient Tables ({len(coeffs)} found) ===")
for addr, length, mn, mx, avg in coeffs:
q15_min = mn / 32768.0
q15_max = mx / 32768.0
period = detect_periodicity(words, addr, length)
pstr = f", period~{period}" if period else ""
print(f" DM 0x{addr:04X} - 0x{addr+length-1:04X}"
f" ({length} words)")
print(f" Range: {mn}..{mx} (Q15: {q15_min:.4f}"
f"..{q15_max:.4f}), avg={avg:.1f}{pstr}")
print()
# Strings
strings = find_strings(data)
if strings:
print(f"=== ASCII Strings ({len(strings)} found) ===")
for offset, s in strings:
dm_addr = offset // 2
print(f" Byte 0x{offset:04X} (DM ~0x{dm_addr:04X}):"
f" \"{s}\"")
print()
# Non-null, non-coefficient data regions
covered = set()
for addr, length in nulls:
for i in range(addr, addr + length):
covered.add(i)
for addr, length, _, _, _ in coeffs:
for i in range(addr, addr + length):
covered.add(i)
data_regions = []
start = None
for i in range(n_words):
if i not in covered and words[i] != 0:
if start is None:
start = i
else:
if start is not None:
length = i - start
if length >= 4:
data_regions.append((start, length))
start = None
if start is not None and (n_words - start) >= 4:
data_regions.append((start, n_words - start))
if data_regions:
print(f"=== Other Data Regions ({len(data_regions)} found) ===")
for addr, length in data_regions:
sample = [f"0x{words[addr+j]:04X}"
for j in range(min(length, 8))]
dots = " ..." if length > 8 else ""
print(f" DM 0x{addr:04X} - 0x{addr+length-1:04X}"
f" ({length} words)")
print(f" [{', '.join(sample)}{dots}]")
print()
# Summary
total_nonzero = sum(1 for w in words if w != 0)
print(f"=== Summary ===")
print(f" Total words: {n_words}")
print(f" Non-zero words: {total_nonzero}"
f" ({total_nonzero*100//max(n_words,1)}%)")
print(f" Null regions: {len(nulls)}")
print(f" Coeff tables: {len(coeffs)}")
print(f" Strings: {len(strings)}")
print(f" Other data: {len(data_regions)}")
def main():
parser = argparse.ArgumentParser(
description="Analyze ADSP-219x DM (Data Memory) dumps.")
parser.add_argument("file", help="DM binary file")
parser.add_argument("--endian", choices=["big", "little"],
default="big",
help="Byte order (default: big)")
args = parser.parse_args()
if not os.path.isfile(args.file):
print(f"Error: {args.file} not found", file=sys.stderr)
sys.exit(1)
analyze(args.file, args.endian)
if __name__ == "__main__":
main()