forked from arti/vt1200
checkpoint: VT1200 reverse-engineering workspace before board-model work
This commit is contained in:
commit
acb92d3e44
325 files changed
+951894
No files matched your search
@@ -0,0 +1,418 @@
|
||||
#!/usr/bin/env python3
|
||||
"""VT1200 font delivery harness.
|
||||
|
||||
Two jobs:
|
||||
|
||||
1. **PCF fonts** — parse/modify Portable Compiled Format fonts (the format the
|
||||
VT1200's MIT-X11-derived font parser reads; see EXPLOIT_NOTES.md). The
|
||||
primitive we care about is the BITMAPS table's ``num_chars`` field, which
|
||||
``pcfReadBitmaps`` multiplies by 4 without an overflow check.
|
||||
|
||||
2. **TFTP server** — the terminal is the TFTP *client* for its font path, so we
|
||||
serve the font files (and ``fonts.dir``) to it.
|
||||
|
||||
CLI::
|
||||
|
||||
python3 tools/fontgen.py baseline out.pcf
|
||||
python3 tools/fontgen.py mutate base.pcf evil.pcf --num-chars 0x40000001
|
||||
python3 tools/fontgen.py serve evil.pcf --host 0.0.0.0 --port 69
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import socket
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
|
||||
PCF_MAGIC = b"\x01fcp" # PCF_FILE_VERSION, little-endian ('p','c','f',1)
|
||||
PCF_VERSION = 0x70636601
|
||||
FMT_MS_BYTE = 1 << 2 # fontlib.h: format bit 2 -> MSBFirst byte order
|
||||
|
||||
# font.h table labels
|
||||
PROPERTIES = 1 << 0
|
||||
ACCELERATORS = 1 << 1
|
||||
METRICS = 1 << 2
|
||||
BITMAPS = 1 << 3
|
||||
INK_METRICS = 1 << 4
|
||||
ENCODINGS = 1 << 5
|
||||
SWIDTHS = 1 << 6
|
||||
GLYPH_NAMES = 1 << 7
|
||||
BDF_ENCODINGS = 1 << 8
|
||||
|
||||
LABELS = {
|
||||
PROPERTIES: "PROPERTIES", ACCELERATORS: "ACCELERATORS", METRICS: "METRICS",
|
||||
BITMAPS: "BITMAPS", INK_METRICS: "INK_METRICS", ENCODINGS: "ENCODINGS",
|
||||
SWIDTHS: "SWIDTHS", GLYPH_NAMES: "GLYPH_NAMES", BDF_ENCODINGS: "BDF_ENCODINGS",
|
||||
}
|
||||
|
||||
TFTP_RRQ, TFTP_DATA, TFTP_ACK, TFTP_ERROR = 1, 3, 4, 5
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- PCF
|
||||
|
||||
def pcf_tables(data):
|
||||
"""Return the table of contents as a list of dicts."""
|
||||
if len(data) < 8 or data[:4] != PCF_MAGIC:
|
||||
raise ValueError("not a PCF file (bad magic)")
|
||||
n = struct.unpack_from("<I", data, 4)[0]
|
||||
if 8 + 16 * n > len(data):
|
||||
raise ValueError("truncated table of contents")
|
||||
tables = []
|
||||
for i in range(n):
|
||||
label, fmt, size, off = struct.unpack_from("<IIII", data, 8 + 16 * i)
|
||||
tables.append({"label": label, "format": fmt, "size": size, "offset": off})
|
||||
return tables
|
||||
|
||||
|
||||
def find_table(data, label):
|
||||
for t in pcf_tables(data):
|
||||
if t["label"] == label:
|
||||
return t
|
||||
return None
|
||||
|
||||
|
||||
def _order(fmt):
|
||||
"""Byte order of a table body, from its format field (FMT_MS_BYTE)."""
|
||||
return ">" if (fmt & FMT_MS_BYTE) else "<"
|
||||
|
||||
|
||||
def get_num_chars(data):
|
||||
"""The BITMAPS 'num_chars' field at table_offset + 4 (in the table's byte order)."""
|
||||
t = find_table(data, BITMAPS)
|
||||
if t is None:
|
||||
raise ValueError("no BITMAPS table")
|
||||
return struct.unpack_from(_order(t["format"]) + "I", data, t["offset"] + 4)[0]
|
||||
|
||||
|
||||
def set_num_chars(data, n):
|
||||
"""Return *data* with the BITMAPS num_chars rewritten (bytes untouched elsewhere)."""
|
||||
t = find_table(data, BITMAPS)
|
||||
if t is None:
|
||||
raise ValueError("no BITMAPS table")
|
||||
out = bytearray(data)
|
||||
struct.pack_into(_order(t["format"]) + "I", out, t["offset"] + 4, n & 0xFFFFFFFF)
|
||||
return bytes(out)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- BDF
|
||||
|
||||
BDF_TEMPLATE = """STARTFONT 2.1
|
||||
FONT -misc-{name}-medium-r-normal--{h}-{pt}-75-75-c-{avg}-iso8859-1
|
||||
SIZE {pt} 75 75
|
||||
FONTBOUNDINGBOX {w} {h} 0 -{d}
|
||||
STARTPROPERTIES 3
|
||||
FONT_ASCENT {a}
|
||||
FONT_DESCENT {d}
|
||||
DEFAULT_CHAR 0
|
||||
ENDPROPERTIES
|
||||
CHARS 1
|
||||
STARTCHAR {char}
|
||||
ENCODING {enc}
|
||||
SWIDTH 500 0
|
||||
DWIDTH {w} 0
|
||||
BBX {w} {h} 0 -{d}
|
||||
BITMAP
|
||||
{bitmap}
|
||||
ENDCHAR
|
||||
ENDFONT
|
||||
"""
|
||||
|
||||
|
||||
def minimal_bdf(name="vt1200test", char="A", encoding=65, w=8, h=8, ascent=6, descent=2):
|
||||
"""A tiny but valid BDF, good enough for bdftopcf.
|
||||
|
||||
The XLFD uses PIXEL_SIZE={h}, POINT_SIZE={h*10}, AVERAGE_WIDTH={w*10} so the
|
||||
name bdftopcf stores is the canonical, 10x-scaled form.
|
||||
"""
|
||||
rows = ["1C", "3E", "63", "63", "7F", "63", "63", "63"][:h]
|
||||
rows += ["00"] * (h - len(rows))
|
||||
return BDF_TEMPLATE.format(name=name, char=char, enc=encoding, w=w, h=h,
|
||||
a=ascent, d=descent, pt=h * 10, avg=w * 10,
|
||||
bitmap="\n".join(rows))
|
||||
|
||||
|
||||
def build_baseline_pcf(out_path, font_name="vt1200test"):
|
||||
"""Write a valid PCF font produced by bdftopcf from our generated BDF."""
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
bpath = os.path.join(d, "f.bdf")
|
||||
with open(bpath, "w") as fh:
|
||||
fh.write(minimal_bdf(font_name))
|
||||
r = subprocess.run(["bdftopcf", "-o", out_path, bpath],
|
||||
capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
raise SystemExit(f"bdftopcf failed: {r.stderr}")
|
||||
return out_path
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- SNF
|
||||
|
||||
SNF_VERSION = 4
|
||||
SNF_GLYPH = bytes([0x1C, 0x3E, 0x63, 0x63, 0x7F, 0x63, 0x63, 0x63])
|
||||
|
||||
|
||||
def _xcharinfo(metrics, e="<"):
|
||||
return struct.pack(e + "6H", *[m & 0xFFFF for m in metrics])
|
||||
|
||||
|
||||
def make_snf(name, char=65, w=8, h=8, ascent=6, descent=2, e="<", dec_magic=False):
|
||||
"""Build a minimal SNF (Server Normal Font) file.
|
||||
|
||||
Layout per snfstr.h: snfFontInfoRec, char-info array, glyphs (padded to a
|
||||
32-bit boundary), snfFontPropRec array, then the property string table.
|
||||
SNF is CPU/framebuffer dependent, so the byte order must match the host.
|
||||
|
||||
``dec_magic=True`` stamps version1/version2 with DEC's own value
|
||||
(0x01DF5E76 = pi*1e7) instead of the MIT version 4 — the VT1200 firmware
|
||||
checks for DEC's value.
|
||||
"""
|
||||
version = 0x01DF5E76 if dec_magic else SNF_VERSION
|
||||
first_col, last_col = 0, 255
|
||||
first_row, last_row = 0, 0
|
||||
n2d = (last_row - first_row + 1) * (last_col - first_col + 1)
|
||||
|
||||
glyph = SNF_GLYPH[:h]
|
||||
glyphs = glyph + b"\x00" * ((4 - len(glyph) % 4) % 4)
|
||||
|
||||
metrics = (0, w, w, ascent, descent, 0)
|
||||
zero = (0, 0, 0, 0, 0, 0)
|
||||
|
||||
ci = []
|
||||
for code in range(first_col, last_col + 1):
|
||||
if code == char:
|
||||
word = (0 & 0xFFFFFF) | (1 << 24) # byteOffset=0, exists=1
|
||||
ci.append(_xcharinfo(metrics, e) + struct.pack(e + "I", word))
|
||||
else:
|
||||
ci.append(_xcharinfo(zero, e) + struct.pack(e + "I", 0))
|
||||
charinfo = b"".join(ci)
|
||||
|
||||
strings = name.encode("latin1") + b"\x00"
|
||||
nprops = 1
|
||||
props = struct.pack(e + "III", 0, 0, 1) # name=0, value=0, indirect
|
||||
|
||||
minb = _xcharinfo(zero, e) + struct.pack(e + "I", 0)
|
||||
maxb = _xcharinfo(metrics, e) + struct.pack(e + "I", len(glyphs))
|
||||
|
||||
hdr = struct.pack(e + "6I", version, 1, 0, 1, 1, 0)
|
||||
hdr += struct.pack(e + "I", 0x0E) # linear|constantWidth|inkInside
|
||||
hdr += struct.pack(e + "IIII", first_col, last_col, first_row, last_row)
|
||||
hdr += struct.pack(e + "III", nprops, len(strings), char)
|
||||
hdr += struct.pack(e + "ii", -descent, ascent)
|
||||
hdr += minb + maxb
|
||||
hdr += struct.pack(e + "III", 1, 1, version)
|
||||
if len(hdr) != 108:
|
||||
raise AssertionError(f"SNF header is {len(hdr)} bytes, expected 108")
|
||||
return hdr + charinfo + glyphs + props + strings
|
||||
|
||||
|
||||
def make_dec_snf(name="VT1200PROBE1", e="<", hdr=b"\x00" * 0x54, extra=b""):
|
||||
"""Minimal DEC font container satisfying the firmware's entry checks.
|
||||
|
||||
Recovered from the VT1200 reader (0x0ED70A-0x0ED8C0):
|
||||
file[0:4] == 0x01DF5E76 (pi*1e7)
|
||||
file[4:6] == 0x0054
|
||||
file[12:14] == 0x0054 (start of the 0x54-byte header)
|
||||
file[44:46] == 0x0001 (header + 0x20)
|
||||
file[60:62] == 0x0006 (header + 0x30)
|
||||
"""
|
||||
h = bytearray(hdr)
|
||||
struct.pack_into(e + "H", h, 0x00, 0x54)
|
||||
struct.pack_into(e + "H", h, 0x20, 0x01)
|
||||
struct.pack_into(e + "H", h, 0x30, 0x06)
|
||||
return (struct.pack(e + "I", 0x01DF5E76) # file[0:4]
|
||||
+ struct.pack(e + "H", 0x54) # file[4:6]
|
||||
+ b"\x00" * 6 # file[6:12]
|
||||
+ bytes(h)) + extra
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- TFTP
|
||||
|
||||
class TFTPServer:
|
||||
"""Minimal RFC 783 TFTP read server (octet mode), single transfer at a time."""
|
||||
|
||||
def __init__(self, address, files, blksize=512, timeout=2.0, retries=5, resolver=None):
|
||||
self.files = dict(files)
|
||||
self.resolver = resolver
|
||||
self.blksize = blksize
|
||||
self.timeout = timeout
|
||||
self.retries = retries
|
||||
self.log = []
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self._sock.bind(address)
|
||||
self._sock.settimeout(timeout)
|
||||
self.address = self._sock.getsockname()
|
||||
self._stop = threading.Event()
|
||||
self._thread = None
|
||||
|
||||
def start(self):
|
||||
self._thread = threading.Thread(target=self._serve, daemon=True)
|
||||
self._thread.start()
|
||||
return self
|
||||
|
||||
def stop(self):
|
||||
self._stop.set()
|
||||
try:
|
||||
self._sock.close()
|
||||
except OSError:
|
||||
pass
|
||||
if self._thread:
|
||||
self._thread.join(timeout=1)
|
||||
|
||||
def _serve(self):
|
||||
while not self._stop.is_set():
|
||||
try:
|
||||
data, addr = self._sock.recvfrom(4096)
|
||||
except socket.timeout:
|
||||
continue # idle: keep serving
|
||||
except OSError:
|
||||
return # socket closed -> stop
|
||||
if len(data) < 4 or data[:2] != TFTP_RRQ.to_bytes(2, "big"):
|
||||
continue
|
||||
name = data[2:].split(b"\x00", 1)[0].decode("latin1")
|
||||
self.log.append((time.time(), name, addr))
|
||||
try:
|
||||
content = self.files.get(name)
|
||||
if content is None and self.resolver is not None:
|
||||
content = self.resolver(name)
|
||||
if content is None:
|
||||
self._send_error(addr, 1, "File not found")
|
||||
continue
|
||||
self._send_file(addr, content)
|
||||
except Exception as exc: # never let one transfer kill the server
|
||||
print(f"tftp: transfer error for {name!r}: {exc!r}", file=sys.stderr)
|
||||
|
||||
def _send_error(self, addr, code, msg):
|
||||
pkt = TFTP_ERROR.to_bytes(2, "big") + code.to_bytes(2, "big") + msg.encode() + b"\x00"
|
||||
try:
|
||||
self._sock.sendto(pkt, addr)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def _send_file(self, addr, content):
|
||||
if content:
|
||||
blocks = [content[i:i + self.blksize] for i in range(0, len(content), self.blksize)]
|
||||
if len(content) % self.blksize == 0:
|
||||
blocks.append(b"") # short block terminates the transfer
|
||||
else:
|
||||
blocks = [b""]
|
||||
for idx, blk in enumerate(blocks, start=1):
|
||||
num = idx & 0xFFFF
|
||||
pkt = TFTP_DATA.to_bytes(2, "big") + num.to_bytes(2, "big") + blk
|
||||
for _ in range(self.retries):
|
||||
if self._stop.is_set():
|
||||
return
|
||||
try:
|
||||
self._sock.sendto(pkt, addr)
|
||||
except OSError as exc:
|
||||
print(f"tftp: sendto {addr} failed: {exc!r}", file=sys.stderr, flush=True)
|
||||
return
|
||||
try:
|
||||
resp, raddr = self._sock.recvfrom(4096)
|
||||
except OSError:
|
||||
continue
|
||||
if raddr != addr:
|
||||
continue
|
||||
if len(resp) >= 2 and resp[:2] == TFTP_ERROR.to_bytes(2, "big"):
|
||||
print(f"tftp: client aborted transfer: "
|
||||
f"{resp[2:].decode('latin1', 'replace')!r}",
|
||||
file=sys.stderr, flush=True)
|
||||
return
|
||||
if (len(resp) >= 4 and resp[:2] == TFTP_ACK.to_bytes(2, "big")
|
||||
and int.from_bytes(resp[2:4], "big") == num):
|
||||
break
|
||||
else:
|
||||
return
|
||||
if self._stop.is_set():
|
||||
return
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- CLI
|
||||
|
||||
def main(argv=None):
|
||||
ap = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||
|
||||
b = sub.add_parser("baseline", help="generate a valid PCF via bdftopcf")
|
||||
b.add_argument("out")
|
||||
b.add_argument("--name", default="vt1200test")
|
||||
|
||||
m = sub.add_parser("mutate", help="rewrite BITMAPS num_chars")
|
||||
m.add_argument("infile")
|
||||
m.add_argument("out")
|
||||
m.add_argument("--num-chars", type=lambda s: int(s, 0), default=0x40000001)
|
||||
|
||||
s = sub.add_parser("serve", help="serve a font over TFTP")
|
||||
s.add_argument("font")
|
||||
s.add_argument("--host", default="0.0.0.0")
|
||||
s.add_argument("--port", type=int, default=69)
|
||||
s.add_argument("--name", default="font.DECW$FONT")
|
||||
s.add_argument("--fonts-dir", help="also serve this file as 'fonts.dir'")
|
||||
|
||||
a = sub.add_parser("info", help="print a PCF's table of contents")
|
||||
a.add_argument("infile")
|
||||
|
||||
n = sub.add_parser("snf", help="generate a minimal SNF font")
|
||||
n.add_argument("out")
|
||||
n.add_argument("--name", default="VT1200PROBE1")
|
||||
n.add_argument("--dec-magic", action="store_true",
|
||||
help="stamp DEC's version (pi*1e7) instead of MIT's 4")
|
||||
n.add_argument("--dec-layout", action="store_true",
|
||||
help="emit the DEC container layout the firmware checks for")
|
||||
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
if args.cmd == "baseline":
|
||||
build_baseline_pcf(args.out, args.name)
|
||||
print(f"wrote {args.out}")
|
||||
elif args.cmd == "mutate":
|
||||
with open(args.infile, "rb") as fh:
|
||||
data = fh.read()
|
||||
was = get_num_chars(data)
|
||||
out = set_num_chars(data, args.num_chars)
|
||||
with open(args.out, "wb") as fh:
|
||||
fh.write(out)
|
||||
print(f"BITMAPS num_chars: {was} (0x{was:X}) -> {args.num_chars} (0x{args.num_chars:X})")
|
||||
print(f"wrote {args.out} ({len(out)} bytes)")
|
||||
elif args.cmd == "info":
|
||||
with open(args.infile, "rb") as fh:
|
||||
data = fh.read()
|
||||
for t in pcf_tables(data):
|
||||
name = LABELS.get(t["label"], hex(t["label"]))
|
||||
extra = ""
|
||||
if t["label"] == BITMAPS:
|
||||
extra = f" num_chars={get_num_chars(data)}"
|
||||
print(f" {name:14} fmt={t['format']:#x} size={t['size']:6} off={t['offset']:6}{extra}")
|
||||
elif args.cmd == "snf":
|
||||
data = (make_dec_snf(args.name) if args.dec_layout
|
||||
else make_snf(args.name, dec_magic=args.dec_magic))
|
||||
with open(args.out, "wb") as fh:
|
||||
fh.write(data)
|
||||
print(f"wrote {args.out} ({len(data)} bytes), font name {args.name!r}")
|
||||
elif args.cmd == "serve":
|
||||
with open(args.font, "rb") as fh:
|
||||
content = fh.read()
|
||||
files = {args.name: content, os.path.basename(args.font): content}
|
||||
if args.fonts_dir:
|
||||
with open(args.fonts_dir, "rb") as fh:
|
||||
files["fonts.dir"] = fh.read()
|
||||
srv = TFTPServer((args.host, args.port), files).start()
|
||||
print(f"TFTP serving {len(files)} file(s) on {srv.address[0]}:{srv.address[1]}")
|
||||
print(f" names: {', '.join(sorted(files))}")
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
while srv.log:
|
||||
ts, name, addr = srv.log.pop(0)
|
||||
print(f" {time.strftime('%H:%M:%S', time.localtime(ts))} {addr[0]}:{addr[1]} requested {name}")
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
srv.stop()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Submodule
+1
Submodule tools/ghidra-headless-mcp added at 307b461cdb.
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Ghidra Headless MCP launcher for the VT1200 project.
|
||||
#
|
||||
# OpenCode starts this over stdio (see opencode.json in the repo root). It drives
|
||||
# the vendored mrphrazer/ghidra-headless-mcp server against Ghidra 12.1.2 at
|
||||
# /opt/ghidra through PyGhidra. The custom tms34010 processor module is installed
|
||||
# system-wide, so the server can open this project's programs with no extra setup.
|
||||
#
|
||||
# Open the existing analysis (reuses the FixPtrConstants cross-references) with a
|
||||
# tool call such as:
|
||||
#
|
||||
# project.program.open_existing
|
||||
# project_location = /home/arti/code/k-space/vt1200/ghidra
|
||||
# project_name = vt1200
|
||||
# program_name = vt1200_v22.bin
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
venv="$here/ghidra-headless-mcp/.venv"
|
||||
|
||||
# Ghidra 12.1.2 requires Java 21+ (application.java.min=21, no max).
|
||||
export GHIDRA_INSTALL_DIR="${GHIDRA_INSTALL_DIR:-/opt/ghidra}"
|
||||
# If the default JDK is ever rejected, point JAVA_HOME at a JDK 21 here:
|
||||
# export JAVA_HOME=/path/to/jdk-21
|
||||
|
||||
exec "$venv/bin/ghidra-headless-mcp" "$@"
|
||||
@@ -0,0 +1,187 @@
|
||||
diff -ruN -x .git upstream/tms34010.pspec vendored/tms34010.pspec
|
||||
--- upstream/tms34010.pspec 2026-10-15 20:04:50.507977274 +0300
|
||||
+++ vendored/tms34010.pspec 2026-10-15 20:04:28.658144901 +0300
|
||||
@@ -1,130 +1,53 @@
|
||||
-<processor_spec>
|
||||
- <programcounter register="PC"/>
|
||||
-
|
||||
- <data_space space ="ROM" ptr_wordsize="4"/>
|
||||
-
|
||||
- <register_data>
|
||||
- <register name="SP"/>
|
||||
- <register name="ST"/>
|
||||
- <register name="A0"/>
|
||||
- <register name="A1"/>
|
||||
- <register name="A2"/>
|
||||
- <register name="A3"/>
|
||||
- <register name="A4"/>
|
||||
- <register name="A5"/>
|
||||
- <register name="A6"/>
|
||||
- <register name="A7"/>
|
||||
- <register name="A8"/>
|
||||
- <register name="A9"/>
|
||||
- <register name="A10"/>
|
||||
- <register name="A11"/>
|
||||
- <register name="A12"/>
|
||||
- <register name="A13"/>
|
||||
- <register name="A14"/>
|
||||
- <register name="B0"/>
|
||||
- <register name="B1"/>
|
||||
- <register name="B2"/>
|
||||
- <register name="B3"/>
|
||||
- <register name="B4"/>
|
||||
- <register name="B5"/>
|
||||
- <register name="B6"/>
|
||||
- <register name="B7"/>
|
||||
- <register name="B8"/>
|
||||
- <register name="B9"/>
|
||||
- <register name="B10"/>
|
||||
- <register name="B11"/>
|
||||
- <register name="B12"/>
|
||||
- <register name="B13"/>
|
||||
- <register name="B14"/>
|
||||
- </register_data>
|
||||
-
|
||||
- <default_symbols>
|
||||
- <symbol name="Trap_00_Address_Reset" address="rom:0xffffc" type="code_ptr" entry ="true" />
|
||||
- <symbol name="Trap_01_External_Interrupt_1" address="rom:0xffff8" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_02_External_Interrupt_2" address="rom:0xffff4" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_03" address="rom:0xffff0" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_04" address="rom:0xfffec" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_05" address="rom:0xfffe8" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_06" address="rom:0xfffe4" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_07" address="rom:0xfffe0" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_08_NonMaskable" address="rom:0xfffdc" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_09_Host_Interrupt" address="rom:0xfffd8" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_10_Display_Interrupt" address="rom:0xfffd4" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_11_Window_Violation" address="rom:0xfffd0" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_12" address="rom:0xfffcc" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_13" address="rom:0xfffc8" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_14" address="rom:0xfffc4" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_15" address="rom:0xfffc0" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_16" address="rom:0xfffbc" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_17" address="rom:0xfffb8" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_18" address="rom:0xfffb4" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_19" address="rom:0xfffb0" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_20" address="rom:0xfffac" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_21" address="rom:0xfffa8" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_22" address="rom:0xfffa4" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_23" address="rom:0xfffa0" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_24" address="rom:0xfff9c" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_25" address="rom:0xfff98" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_26" address="rom:0xfff94" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_27" address="rom:0xfff90" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_28" address="rom:0xfff8c" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_29" address="rom:0xfff88" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_30_Illegal_Opcode" address="rom:0xfff84" type="code_ptr" entry ="false" />
|
||||
- <symbol name="Trap_31" address="rom:0xfff80" type="code_ptr" entry ="false" />
|
||||
-
|
||||
- <symbol name="HORIZONTAL_END_SYNC" address="ram:0xC0000000" entry ="false" />
|
||||
- <symbol name="HORIZONTAL_END_BLANK" address="ram:0xC0000010" entry ="false" />
|
||||
- <symbol name="HORIZONTAL_START_BLANK" address="ram:0xC0000020" entry ="false" />
|
||||
- <symbol name="HORIZONTAL_TOTAL" address="ram:0xC0000030" entry ="false" />
|
||||
- <symbol name="VERTICAL_SYNC" address="ram:0xC0000040" entry ="false" />
|
||||
- <symbol name="VERTICAL_BLANK" address="ram:0xC0000050" entry ="false" />
|
||||
- <symbol name="VERTICAL_START_BLANK" address="ram:0xC0000060" entry ="false" />
|
||||
- <symbol name="VERTICAL_TOTAL" address="ram:0xC0000070" entry ="false" />
|
||||
- <symbol name="DISPLAY_CONTROL" address="ram:0xC0000080" entry ="false" />
|
||||
- <symbol name="DISPLAY_START" address="ram:0xC0000090" entry ="false" />
|
||||
- <symbol name="DISPLAY_INTERRUPT" address="ram:0xC00000A0" entry ="false" />
|
||||
- <symbol name="CONTROL" address="ram:0xC00000B0" entry ="false" />
|
||||
- <symbol name="HOST_DATA" address="ram:0xC00000C0" entry ="false" />
|
||||
- <symbol name="HOST_ADDRESS_LSB" address="ram:0xC00000D0" entry ="false" />
|
||||
- <symbol name="HOST_ADDRESS_MSB" address="ram:0xC00000E0" entry ="false" />
|
||||
- <symbol name="HOST_CONTROL_LSB" address="ram:0xC00000F0" entry ="false" />
|
||||
- <symbol name="HOST_CONTROL_MSB" address="ram:0xC0000100" entry ="false" />
|
||||
- <symbol name="INTERRUPT_ENABLE" address="ram:0xC0000110" entry ="false" />
|
||||
- <symbol name="INTERRUPT_PENDING" address="ram:0xC0000120" entry ="false" />
|
||||
- <symbol name="SOURCE_CONVERSION_PITCH" address="ram:0xC0000130" entry ="false" />
|
||||
- <symbol name="DESTINATION_CONVERSION_PITCH" address="ram:0xC0000140" entry ="false" />
|
||||
- <symbol name="PIXEL_SIZE" address="ram:0xC0000150" entry ="false" />
|
||||
- <symbol name="PIXEL_MASK" address="ram:0xC0000160" entry ="false" />
|
||||
- <symbol name="RESERVED" address="ram:0xC0000170" entry ="false" />
|
||||
- <symbol name="RESERVED" address="ram:0xC0000180" entry ="false" />
|
||||
- <symbol name="RESERVED" address="ram:0xC0000190" entry ="false" />
|
||||
- <symbol name="RESERVED" address="ram:0xC00001A0" entry ="false" />
|
||||
- <symbol name="DISPLAY_TAP_POINT" address="ram:0xC00001B0" entry ="false" />
|
||||
- <symbol name="HORIZONTAL_COUNT" address="ram:0xC00001C0" entry ="false" />
|
||||
- <symbol name="VERTICAL_COUNT" address="ram:0xC00001D0" entry ="false" />
|
||||
- <symbol name="DISPLAY_ADDRESS" address="ram:0xC00001E0" entry ="false" />
|
||||
- <symbol name="DRAM_REFRESH_COUNT" address="ram:0xC00001F0" entry ="false" />
|
||||
- </default_symbols>
|
||||
-
|
||||
- <default_memory_blocks>
|
||||
- <memory_block name="VRAM" start_address="ram:0x0" length="0xffffff" mode="rw" />
|
||||
- <memory_block name="RAM" start_address="ram:0x01000000" length="0x003fffff" mode="rw" />
|
||||
- <memory_block name="NVRAM" start_address="ram:0x01400000" length="0x1ffff" mode="rw" />
|
||||
- <memory_block name="CMOS_ENABLE" start_address="ram:0x01480000" length="0x7ffff" mode="w" />
|
||||
- <memory_block name="INPUT_0" start_address="ram:0x01600000" length="0xf" mode="r" />
|
||||
- <memory_block name="INPUT_1" start_address="ram:0x01600010" length="0xf" mode="r" />
|
||||
- <memory_block name="INPUT_2" start_address="ram:0x01600020" length="0xf" mode="r" />
|
||||
- <memory_block name="DIPSWITCHES" start_address="ram:0x01600030" length="0xf" mode="r" />
|
||||
- <memory_block name="PALETTE" start_address="ram:0x01800000" length="0x7ffff" mode="rw" />
|
||||
- <memory_block name="DMA" start_address="ram:0x01a80000" length="0xff" mode="rw" />
|
||||
- <memory_block name="midtunit_control_w" start_address="ram:0x01b00000" length="0x1f" mode="w" />
|
||||
- <memory_block name="midtunit_sound_state_r" start_address="ram:0x01d00000" length="0x1f" mode="r" />
|
||||
- <memory_block name="midtunit_sound_rw" start_address="ram:0x01d01020" length="0x1f" mode="rw" />
|
||||
- <memory_block name="WATCHDOG" start_address="ram:0x01d81060" length="0x1f" mode="w" />
|
||||
- <memory_block name="midtunit_control_w" start_address="ram:0x01f00000" length="0x1f" mode="w" />
|
||||
- <memory_block name="REGISTERS" start_address="ram:0xc0000000" length="0x1ff" mode="rw" />
|
||||
- <memory_block name="GFX" start_address="rom:0x02000000" length="0x07ffffff" mode="r" />
|
||||
- <memory_block name="ROM" start_address="rom:0xff800000" length="0x7fffff" mode="r" />
|
||||
- </default_memory_blocks>
|
||||
-
|
||||
-</processor_spec>
|
||||
+<processor_spec>
|
||||
+ <programcounter register="PC"/>
|
||||
+
|
||||
+ <data_space space="rom" ptr_wordsize="4"/>
|
||||
+
|
||||
+ <register_data>
|
||||
+ <register name="SP"/>
|
||||
+ <register name="ST"/>
|
||||
+ <register name="A0"/>
|
||||
+ <register name="A1"/>
|
||||
+ <register name="A2"/>
|
||||
+ <register name="A3"/>
|
||||
+ <register name="A4"/>
|
||||
+ <register name="A5"/>
|
||||
+ <register name="A6"/>
|
||||
+ <register name="A7"/>
|
||||
+ <register name="A8"/>
|
||||
+ <register name="A9"/>
|
||||
+ <register name="A10"/>
|
||||
+ <register name="A11"/>
|
||||
+ <register name="A12"/>
|
||||
+ <register name="A13"/>
|
||||
+ <register name="A14"/>
|
||||
+ <register name="B0"/>
|
||||
+ <register name="B1"/>
|
||||
+ <register name="B2"/>
|
||||
+ <register name="B3"/>
|
||||
+ <register name="B4"/>
|
||||
+ <register name="B5"/>
|
||||
+ <register name="B6"/>
|
||||
+ <register name="B7"/>
|
||||
+ <register name="B8"/>
|
||||
+ <register name="B9"/>
|
||||
+ <register name="B10"/>
|
||||
+ <register name="B11"/>
|
||||
+ <register name="B12"/>
|
||||
+ <register name="B13"/>
|
||||
+ <register name="B14"/>
|
||||
+ </register_data>
|
||||
+
|
||||
+ <!--
|
||||
+ VT1200 specifics are intentionally NOT declared here.
|
||||
+
|
||||
+ The TMS34010 ROM is loaded at byte base 0x07E40000; see MEMORY_MAP.md.
|
||||
+ Upstream shipped a Midway-arcade board map (VRAM/RAM/NVRAM/PALETTE/
|
||||
+ GFX/ROM blocks plus WATCHDOG, DIPSWITCHES, INPUT_n, midtunit_* and a
|
||||
+ rom:0xFFFFC trap table). None of that describes the VT1200, and the
|
||||
+ ROM block collided with the loaded image, so it has been removed.
|
||||
+
|
||||
+ To add a VT1200 map later, use MEMORY_MAP.md; note that the firmware
|
||||
+ stores *bit* addresses while this language models *byte* addresses.
|
||||
+ -->
|
||||
+</processor_spec>
|
||||
@@ -0,0 +1,6 @@
|
||||
# Ghidra_TMS34010
|
||||
A processor module of the Texas Instruments TMS34010 processor for Ghidra.
|
||||
|
||||
Current status is incomplete, some semantics are still needed to be finished as well as other fixes.
|
||||
|
||||
12/27/2022 - Added python script for importing symbols into Ghidra.
|
||||
@@ -0,0 +1,88 @@
|
||||
#
|
||||
#@author ZPaul2Fresh8
|
||||
#@category Import
|
||||
#@keybinding
|
||||
#@menupath
|
||||
#@toolbar
|
||||
|
||||
#format = TYPE("FUNCTION") | NAME | ADDRESS | DESCRIPTION
|
||||
|
||||
from ghidra.program.model.symbol.SourceType import *
|
||||
import string
|
||||
|
||||
functionManager = currentProgram.getFunctionManager()
|
||||
c = 0 # created routines
|
||||
r = 0 # renamed routines
|
||||
o = 0 # omissions
|
||||
l = 0 # labels
|
||||
f = askFile("Give me a file to open", "Go baby go!")
|
||||
|
||||
for line in file(f.absolutePath): # note, cannot use open(), since that is in GhidraScript
|
||||
line = line.strip()
|
||||
pieces = line.split("|")
|
||||
if len((pieces)) < 3:
|
||||
#line_d ="{} has less than 3 pieces, skipping it."
|
||||
#print(line_d.format(line_d))
|
||||
o += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
|
||||
name = pieces[1]
|
||||
value_str = "0x" + pieces[2]
|
||||
value = int(value_str, 16) # convert to dec
|
||||
value = (value / 8) & 0xFFFFF
|
||||
|
||||
#value_h = hex(value_int) # convert to hex
|
||||
|
||||
#value.format(value, format_spec)
|
||||
address = toAddr(value)
|
||||
comment = pieces[3]
|
||||
|
||||
try:
|
||||
function_or_label = pieces[0]
|
||||
except IndexError:
|
||||
function_or_label = "l" or "RAM"
|
||||
|
||||
if function_or_label == "f" or function_or_label == "ROUTINE":
|
||||
func = functionManager.getFunctionAt(address)
|
||||
|
||||
if func is not None:
|
||||
old_name = func.getName()
|
||||
func.setName(name, USER_DEFINED)
|
||||
setPlateComment(address, comment)
|
||||
r+=1
|
||||
else:
|
||||
func = createFunction(address, name)
|
||||
setPlateComment(address, comment)
|
||||
c+=1
|
||||
|
||||
else:
|
||||
#print(value)
|
||||
if value < 0xfffff:
|
||||
#address_line = "ram:" + value
|
||||
#createLabel(address, name, False)
|
||||
l+=1
|
||||
elif value < 0xff800000: # ram locations
|
||||
print(value)
|
||||
#address_line = "rom:{}" + value
|
||||
createLabel(address, name, False)
|
||||
#createLabel(value_str, name, 'ram', False, DEFAULT)
|
||||
l+=1
|
||||
#print("Created label {} at address {}".format(name, address))
|
||||
|
||||
|
||||
|
||||
except:
|
||||
error = "Error: Line was not correct, trying next one."
|
||||
#print(error.format(pieces[0],pieces[1],pieces[2],pieces[3]))
|
||||
o+=1
|
||||
continue
|
||||
print("")
|
||||
print("+=======================================RESULTS========================================".format(c))
|
||||
print("| {} function created".format(c))
|
||||
print("| {} function renamed".format(r))
|
||||
print("| {} labels created".format(l))
|
||||
print("| {} lines omitted due to unexpected format.".format(o))
|
||||
print("+======================================================================================".format(c))
|
||||
print("")
|
||||
@@ -0,0 +1,81 @@
|
||||
#@author ZPaul2Fresh8
|
||||
#@category Import
|
||||
#@keybinding
|
||||
#@menupath
|
||||
#@toolbar
|
||||
|
||||
# Python 3
|
||||
#format = TYPE("FUNCTION") | NAME | ADDRESS | DESCRIPTION
|
||||
|
||||
from ghidra.program.model.symbol import SourceType # FIXED: Don't use * import here
|
||||
import string
|
||||
|
||||
functionManager = currentProgram.getFunctionManager()
|
||||
c = 0 # created routines
|
||||
r = 0 # renamed routines
|
||||
o = 0 # omissions
|
||||
l = 0 # labels
|
||||
f = askFile("Give me a file to open", "Go baby go!")
|
||||
|
||||
# FIXED: 'file()' is removed in Python 3, using 'open()'
|
||||
with open(f.absolutePath, 'r') as file_handle:
|
||||
for line in file_handle:
|
||||
line = line.strip()
|
||||
pieces = line.split("|")
|
||||
|
||||
if len((pieces)) < 3:
|
||||
o += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
name = pieces[1]
|
||||
value_str = "0x" + pieces[2]
|
||||
value = int(value_str, 16) # convert to dec
|
||||
|
||||
# FIXED: Python 3 division returns float, used // for integer division
|
||||
value = (value // 8) & 0xFFFFF
|
||||
|
||||
address = toAddr(value)
|
||||
comment = pieces[3]
|
||||
|
||||
try:
|
||||
function_or_label = pieces[0]
|
||||
except IndexError:
|
||||
function_or_label = "l" # Default
|
||||
|
||||
if function_or_label == "f" or function_or_label == "ROUTINE":
|
||||
func = functionManager.getFunctionAt(address)
|
||||
|
||||
if func is not None:
|
||||
old_name = func.getName()
|
||||
# FIXED: Explicitly calling SourceType.USER_DEFINED
|
||||
func.setName(name, SourceType.USER_DEFINED)
|
||||
setPlateComment(address, comment)
|
||||
r+=1
|
||||
else:
|
||||
func = createFunction(address, name)
|
||||
setPlateComment(address, comment)
|
||||
c+=1
|
||||
|
||||
else:
|
||||
if value < 0xfffff:
|
||||
# createLabel(address, name, False)
|
||||
l+=1
|
||||
elif value < 0xff800000: # ram locations
|
||||
print(value)
|
||||
createLabel(address, name, False)
|
||||
l+=1
|
||||
|
||||
except Exception as e: # FIXED: Catching specific exception to see errors
|
||||
print("Error on line: " + line + " -> " + str(e))
|
||||
o+=1
|
||||
continue
|
||||
|
||||
print("")
|
||||
print("+=======================================RESULTS========================================")
|
||||
print("| {} function created".format(c))
|
||||
print("| {} function renamed".format(r))
|
||||
print("| {} labels created".format(l))
|
||||
print("| {} lines omitted due to unexpected format or error.".format(o))
|
||||
print("+======================================================================================")
|
||||
print("")
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
@@ -0,0 +1,180 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<compiler_spec>
|
||||
|
||||
<data_organization>
|
||||
<absolute_max_alignment value="4" />
|
||||
<machine_alignment value="2" />
|
||||
<default_alignment value="2" />
|
||||
<default_pointer_alignment value ="2" />
|
||||
<pointer_size value ="4"/>
|
||||
<wchar_size value="2"/>
|
||||
<short_size value="2" />
|
||||
<integer_size value="4" />
|
||||
|
||||
<size_alignment_map>
|
||||
<entry size="2" alignment="2" />
|
||||
<entry size="4" alignment="4" />
|
||||
</size_alignment_map>
|
||||
</data_organization>
|
||||
|
||||
<global>
|
||||
<range space="rom"/>
|
||||
<range space="ram"/>
|
||||
</global>
|
||||
|
||||
<readonly>
|
||||
<range space="ram" first="0x01600000" last="0x160003f" />
|
||||
</readonly>
|
||||
|
||||
<stackpointer register="SP" space="ram" />
|
||||
|
||||
<default_proto>
|
||||
<prototype name="default" extrapop="0" stackshift="0" strategy="register">
|
||||
<input>
|
||||
|
||||
<pentry minsize="4" maxsize="4" align="4">
|
||||
<addr offset="0x0" space="register"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="SP"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="ST"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="A0"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="A1"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="A2"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="A3"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="A4"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="A5"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="A6"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="A7"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="A8"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="A9"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="A10"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="A11"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="A12"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="A13"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="A14"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="B0"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="B1"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="B2"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="B3"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="B4"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="B5"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="B6"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="B7"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="B8"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="B9"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="B10"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="B11"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="B12"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="B13"/>
|
||||
</pentry>
|
||||
|
||||
<pentry minsize="4" maxsize="4">
|
||||
<register name="B14"/>
|
||||
</pentry>
|
||||
|
||||
</input>
|
||||
|
||||
<output>
|
||||
</output>
|
||||
|
||||
<unaffected>
|
||||
<register name="SP" />
|
||||
</unaffected>
|
||||
|
||||
</prototype>
|
||||
</default_proto>
|
||||
|
||||
</compiler_spec>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<language_definitions>
|
||||
<language processor="TMS 34010"
|
||||
endian="little"
|
||||
size="32"
|
||||
variant="default"
|
||||
version="1.0"
|
||||
slafile="tms34010.sla"
|
||||
processorspec="tms34010.pspec"
|
||||
id="tms34010:LE:32:default">
|
||||
<description>Texas Instruments 34010</description>
|
||||
<compiler name="default" spec="tms34010.cspec" id="default"/>
|
||||
</language>
|
||||
</language_definitions>
|
||||
@@ -0,0 +1,53 @@
|
||||
<processor_spec>
|
||||
<programcounter register="PC"/>
|
||||
|
||||
<data_space space="rom" ptr_wordsize="4"/>
|
||||
|
||||
<register_data>
|
||||
<register name="SP"/>
|
||||
<register name="ST"/>
|
||||
<register name="A0"/>
|
||||
<register name="A1"/>
|
||||
<register name="A2"/>
|
||||
<register name="A3"/>
|
||||
<register name="A4"/>
|
||||
<register name="A5"/>
|
||||
<register name="A6"/>
|
||||
<register name="A7"/>
|
||||
<register name="A8"/>
|
||||
<register name="A9"/>
|
||||
<register name="A10"/>
|
||||
<register name="A11"/>
|
||||
<register name="A12"/>
|
||||
<register name="A13"/>
|
||||
<register name="A14"/>
|
||||
<register name="B0"/>
|
||||
<register name="B1"/>
|
||||
<register name="B2"/>
|
||||
<register name="B3"/>
|
||||
<register name="B4"/>
|
||||
<register name="B5"/>
|
||||
<register name="B6"/>
|
||||
<register name="B7"/>
|
||||
<register name="B8"/>
|
||||
<register name="B9"/>
|
||||
<register name="B10"/>
|
||||
<register name="B11"/>
|
||||
<register name="B12"/>
|
||||
<register name="B13"/>
|
||||
<register name="B14"/>
|
||||
</register_data>
|
||||
|
||||
<!--
|
||||
VT1200 specifics are intentionally NOT declared here.
|
||||
|
||||
The TMS34010 ROM is loaded at byte base 0x07E40000; see MEMORY_MAP.md.
|
||||
Upstream shipped a Midway-arcade board map (VRAM/RAM/NVRAM/PALETTE/
|
||||
GFX/ROM blocks plus WATCHDOG, DIPSWITCHES, INPUT_n, midtunit_* and a
|
||||
rom:0xFFFFC trap table). None of that describes the VT1200, and the
|
||||
ROM block collided with the loaded image, so it has been removed.
|
||||
|
||||
To add a VT1200 map later, use MEMORY_MAP.md; note that the firmware
|
||||
stores *bit* addresses while this language models *byte* addresses.
|
||||
-->
|
||||
</processor_spec>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,51 @@
|
||||
# TODO: JRcc Long Are not calculating correctly. Shorts Seem Good. 4bf84 ex
|
||||
# DSJS Calcs off as well. (4bff4 ex.)
|
||||
|
||||
define endian=little;
|
||||
define alignment=2;
|
||||
|
||||
define space register type=register_space size=4;
|
||||
define space ram type=ram_space size=4;
|
||||
define space input type=ram_space size=4;
|
||||
define space graphic type=ram_space size=1;
|
||||
define space rom type=ram_space size=4 default; #wordsize=2
|
||||
|
||||
define register offset=0x00 size=4 [ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 ] ;
|
||||
define register offset=0x40 size=4 [ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 B10 B11 B12 B13 B14 ] ;
|
||||
|
||||
define register offset=0x80 size=4 [ PC ];
|
||||
define register offset=0x7C size=4 [ SP ];
|
||||
define register offset=0x84 size=4 [ ST ];
|
||||
|
||||
define context ST
|
||||
N = (31,31)
|
||||
C = (30,30)
|
||||
Z = (29,29)
|
||||
V = (28,28)
|
||||
unused2 = (26,27)
|
||||
pbx = (25,25)
|
||||
unused1 = (22,24)
|
||||
ie = (21,21)
|
||||
reserved1 = (12,20)
|
||||
fs_1_ext = (11,11)
|
||||
fs_1_size = (6,10)
|
||||
fs_0_ext = (5,5)
|
||||
fs_0_size = (0,4)
|
||||
;
|
||||
|
||||
@define N_Flag "ST[31,1]"
|
||||
@define C_Flag "ST[30,1]"
|
||||
@define Z_Flag "ST[29,1]"
|
||||
@define V_Flag "ST[28,1]"
|
||||
@define Interrupt "ST[21,1]"
|
||||
@define FS1_Ext "ST[11,1]"
|
||||
@define FS1_Size "ST[6,5]"
|
||||
@define FS0_Ext "ST[5,1]"
|
||||
@define FS0_Size "ST[0,4]"
|
||||
|
||||
# not used, vectors
|
||||
@define Trap_0 "0xFFFFC" # Reset
|
||||
@define Trap_1 "0xFFFF8" # External Interrupt 1
|
||||
@define Trap_2 "0xFFFF4" # External Interrupt 2
|
||||
|
||||
@include "tms34010.sinc"
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Install (or reinstall) the patched TMS34010 processor module into Ghidra.
|
||||
#
|
||||
# The vendored source in ./ghidra-tms34010 is upstream
|
||||
# https://github.com/ZPaul2Fresh8/Ghidra_TMS34010
|
||||
# with two fixes needed for Ghidra 12 (see ghidra-tms34010-ghidra12.patch):
|
||||
# - pspec <data_space space="ROM"> -> lowercase "rom" (matches the slaspec)
|
||||
# - every <memory_block> gains initialized="false" (required by the new schema)
|
||||
#
|
||||
# Override the install location with GHIDRA_INSTALL_DIR.
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
GHIDRA="${GHIDRA_INSTALL_DIR:-/opt/ghidra}"
|
||||
DEST="$GHIDRA/Ghidra/Processors/TMS34010"
|
||||
SUDO="${SUDO:-sudo}"
|
||||
|
||||
if [[ ! -x "$GHIDRA/support/sleigh" ]]; then
|
||||
echo "error: $GHIDRA/support/sleigh not found (set GHIDRA_INSTALL_DIR)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$SUDO mkdir -p "$DEST/data/languages"
|
||||
$SUDO cp "$HERE/ghidra-tms34010"/tms34010.{slaspec,sinc,ldefs,pspec,cspec} \
|
||||
"$DEST/data/languages/"
|
||||
$SUDO touch "$DEST/Module.manifest"
|
||||
|
||||
echo "compiling sleigh -> sla ..."
|
||||
$SUDO "$GHIDRA/support/sleigh" -a "$DEST/data/languages"
|
||||
|
||||
# The sleigh compiler writes the .sla as root; make it readable by users.
|
||||
$SUDO chmod -R a+rX "$DEST"
|
||||
|
||||
echo "installed TMS34010 language into $DEST"
|
||||
echo "language id: tms34010:LE:32:default"
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/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())
|
||||
@@ -0,0 +1,10 @@
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -O2
|
||||
|
||||
dis34: dis34.c
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f dis34
|
||||
|
||||
.PHONY: clean
|
||||
@@ -0,0 +1,490 @@
|
||||
# TMS34010 Disassembler Project
|
||||
|
||||
This is a (hopefully portable) TMS34010 disassembler which has built-in options to 'intelligently' analyze ROM contents and determine which areas are most likely to be executed, and then disassembles the corresponding areas as code or data. Also include are a couple of options to load multiple ROMs in 16- and 8-bit format. The skeleton with the code base and heuristics was human-written, but I had Claude Code fill in the gaps and test it extensively.
|
||||
|
||||
The output format can be assembled with as34, which is a TMS34010 assembler that (as far as I remember) I got from a Texas Instruments' SDK/Devkit in the early 2000's.
|
||||
|
||||
---
|
||||
|
||||
## dis34 Usage
|
||||
|
||||
```
|
||||
dis34 [-a] [-b base_addr] [-s skip] [file]
|
||||
dis34 [-a] -e file1 [file2 ...]
|
||||
dis34 [-a] -E lo1 hi1 [lo2 hi2 ...]
|
||||
-a analyze mode: heuristic code/data separation
|
||||
-b base bit-address (hex, default 0)
|
||||
-e EPROM mode: concatenate 16-bit files, last byte at top of address space
|
||||
-E 8-bit EPROM mode: interleave pairs (lo,hi), last byte at top
|
||||
-s skip bytes at start of file
|
||||
-p import code entry points from file (one hex address per line)
|
||||
-P export discovered entry points to file
|
||||
-l show address prefix on each output line (off by default)
|
||||
-o show opcode bytes as comment after each instruction
|
||||
-h show help
|
||||
reads stdin if no file given
|
||||
```
|
||||
|
||||
Examples:
|
||||
```
|
||||
./dis34 -s 80 -b fff00000 somefile.bin
|
||||
./dis34 -a -E u4.bin u7.bin # single pair
|
||||
./dis34 -a -E u4.bin u7.bin u6.bin u9.bin # multiple pairs
|
||||
./dis34 -a -P points.txt -E u4.bin u7.bin # export entry points
|
||||
./dis34 -a -p points.txt -E u4.bin u7.bin # import entry points
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TMS34010 Instruction Set Architecture
|
||||
|
||||
### Registers
|
||||
|
||||
- **A-file**: a0-a14, accessed when register bit 4 = 0
|
||||
- **B-file**: b0-b14, accessed when register bit 4 = 1
|
||||
- **sp**: register 15 in either file (stack pointer)
|
||||
- Register encoding: 5-bit field, bit 4 = file select, bits 3:0 = register number
|
||||
|
||||
### Instruction Word Format
|
||||
|
||||
All instructions are 16-bit words (little-endian). Multi-word instructions
|
||||
follow with 16-bit or 32-bit immediates/addresses.
|
||||
|
||||
```
|
||||
Bits 15:9 - Primary opcode (op & 0xFE00)
|
||||
Bits 8:5 - Sub-opcode or source register (RS)
|
||||
Bits 4:0 - Destination register (RD), bit 4 = file select
|
||||
Bit 9 - Field selector for MOVE/SETF (0 or 1)
|
||||
```
|
||||
|
||||
### Primary Decode: `op & 0xFE00`
|
||||
|
||||
| Range | Instructions |
|
||||
|-------|-------------|
|
||||
| 0x0000 | rev, emu, exgpc, getpc, jump, getst, putst, popst, pushst |
|
||||
| 0x0200 | nop, clrc, movb @,@, dint, abs, neg, negb, not |
|
||||
| 0x0400-0x0600 | sext, zext, setf, move r/@, move @/r, move @/@, movb r/@, move @/r (with field) |
|
||||
| 0x0800 | trap, call, reti, rets, mmtm, mmfm, moviw, movi (32-bit) |
|
||||
| 0x0A00 | addiw, addi, cmpiw, cmpi, andi, ori, xori, subiw |
|
||||
| 0x0C00 | subi (32-bit), callr, calla, eint, dsj, dsjeq, dsjne, setc |
|
||||
| 0x0E00 | pixblt l/xy/b variants, fill l/xy |
|
||||
| 0x1000-0x1200 | addk / inc (k=1) |
|
||||
| 0x1400-0x1600 | subk / dec (k=1) |
|
||||
| 0x1800-0x1A00 | movk |
|
||||
| 0x1C00-0x1E00 | btst (K-form, 1's complement) |
|
||||
| 0x2000-0x2200 | sla (K-form) |
|
||||
| 0x2400-0x2600 | sll (K-form) |
|
||||
| 0x2800-0x2A00 | sra (K-form, 2's complement: 32-stored) |
|
||||
| 0x2C00-0x2E00 | srl (K-form, 2's complement: 32-stored) |
|
||||
| 0x3000-0x3200 | rl (K-form) |
|
||||
| 0x3800-0x3E00 | dsjs (5-bit displacement, bit 10 = negate) |
|
||||
| 0x4000-0x6E00 | Register-register ALU (add, sub, cmp, and, or, xor, shift, etc.) |
|
||||
| 0x8000-0xBC00 | MOVE/MOVB addressing modes |
|
||||
| 0xC000-0xCE00 | Conditional jumps (JR/JA) |
|
||||
| 0xD000-0xD600 | MOVE *rs(d),*rd+; MOVE @abs,*rd+; exgf |
|
||||
| 0xDE00 | LINE 0 / LINE 1 |
|
||||
| 0xE000-0xEE00 | addxy, subxy, cmpxy, cpw, cvxyl, movx, movy |
|
||||
| 0xF000-0xFC00 | PIXT / drav |
|
||||
|
||||
### Complete Opcode Table
|
||||
|
||||
#### Register-Register (REGREG): `opval | (rs<<5) | rd`
|
||||
|
||||
| Mnemonic | Opval | Notes |
|
||||
|----------|-------|-------|
|
||||
| add | 0x4000 | |
|
||||
| addc | 0x4200 | |
|
||||
| sub | 0x4400 | |
|
||||
| subb | 0x4600 | |
|
||||
| cmp | 0x4800 | |
|
||||
| btst | 0x4A00 | Register form |
|
||||
| and | 0x5000 | |
|
||||
| andn | 0x5200 | |
|
||||
| or | 0x5400 | |
|
||||
| xor | 0x5600 | clr when rs==rd |
|
||||
| divs | 0x5800 | |
|
||||
| divu | 0x5A00 | |
|
||||
| mpys | 0x5C00 | |
|
||||
| mpyu | 0x5E00 | |
|
||||
| sla | 0x6000 | Register form |
|
||||
| sll | 0x6200 | Register form |
|
||||
| sra | 0x6400 | Register form |
|
||||
| srl | 0x6600 | Register form |
|
||||
| rl | 0x6800 | Register form |
|
||||
| lmo | 0x6A00 | |
|
||||
| mods | 0x6C00 | |
|
||||
| modu | 0x6E00 | |
|
||||
| addxy | 0xE000 | |
|
||||
| subxy | 0xE200 | |
|
||||
| cmpxy | 0xE400 | |
|
||||
| cpw | 0xE600 | |
|
||||
| cvxyl | 0xE800 | |
|
||||
| movx | 0xEC00 | |
|
||||
| movy | 0xEE00 | |
|
||||
| drav | 0xF600 | |
|
||||
|
||||
#### One-Register (ONEREG): `opval | rd`
|
||||
|
||||
| Mnemonic | Opval |
|
||||
|----------|-------|
|
||||
| rev | 0x0020 |
|
||||
| exgpc | 0x0120 |
|
||||
| getpc | 0x0140 |
|
||||
| jump | 0x0160 |
|
||||
| getst | 0x0180 |
|
||||
| putst | 0x01A0 |
|
||||
| abs | 0x0380 |
|
||||
| neg | 0x03A0 |
|
||||
| negb | 0x03C0 |
|
||||
| not | 0x03E0 |
|
||||
| call | 0x0920 |
|
||||
| inc | 0x1020 |
|
||||
| dec | 0x1420 |
|
||||
|
||||
#### Implied (no operands)
|
||||
|
||||
| Mnemonic | Opval |
|
||||
|----------|-------|
|
||||
| emu | 0x0100 |
|
||||
| popst | 0x01C0 |
|
||||
| pushst | 0x01E0 |
|
||||
| nop | 0x0300 |
|
||||
| clrc | 0x0320 |
|
||||
| dint | 0x0360 |
|
||||
| reti | 0x0940 |
|
||||
| eint | 0x0D60 |
|
||||
| setc | 0x0DE0 |
|
||||
| pixblt l,l | 0x0F00 |
|
||||
| pixblt l,xy | 0x0F20 |
|
||||
| pixblt xy,l | 0x0F40 |
|
||||
| pixblt xy,xy | 0x0F60 |
|
||||
| pixblt b,l | 0x0F80 |
|
||||
| pixblt b,xy | 0x0FA0 |
|
||||
| fill l | 0x0FC0 |
|
||||
| fill xy | 0x0FE0 |
|
||||
| line 0 | 0xDF1A |
|
||||
| line 1 | 0xDF9A |
|
||||
|
||||
#### Immediate-Long + Register (ILREG): `opval | rd`, then 32-bit immediate
|
||||
|
||||
| Mnemonic | Opval | Notes |
|
||||
|----------|-------|-------|
|
||||
| addi | 0x0B20 | Also word form at 0x0B00 |
|
||||
| movi | 0x09E0 | Also word form at 0x09C0 |
|
||||
| ori | 0x0BA0 | |
|
||||
| xori | 0x0BC0 | |
|
||||
|
||||
#### Negated Immediate-Long + Register (NILREG): stored as ~value
|
||||
|
||||
| Mnemonic | Opval | Notes |
|
||||
|----------|-------|-------|
|
||||
| andi | 0x0B80 | 1's complement |
|
||||
| cmpi | 0x0B60 | 1's complement; also word form at 0x0B40 |
|
||||
| subi | 0x0D00 | 1's complement; also word form at 0x0BE0 |
|
||||
|
||||
#### Constant + Register (KREG): `opval | (k<<5) | rd`, K range 1-32 (0=32)
|
||||
|
||||
| Mnemonic | Opval | Notes |
|
||||
|----------|-------|-------|
|
||||
| addk | 0x1000 | inc when k=1 |
|
||||
| subk | 0x1400 | dec when k=1 |
|
||||
| movk | 0x1800 | |
|
||||
|
||||
#### Constant/Register dual-form (KREGORREGREG)
|
||||
|
||||
| Mnemonic | K-form | R-form | K encoding |
|
||||
|----------|--------|--------|------------|
|
||||
| sla | 0x2000 | 0x6000 | Direct |
|
||||
| sll | 0x2400 | 0x6200 | Direct |
|
||||
| rl | 0x3000 | 0x6800 | Direct |
|
||||
|
||||
#### Negated Constant/Register dual-form (NKREGORREGREG)
|
||||
|
||||
| Mnemonic | K-form | R-form | K encoding |
|
||||
|----------|--------|--------|------------|
|
||||
| btst | 0x1C00 | 0x4A00 | 1's complement: (~op>>5)&0x1F |
|
||||
| sra | 0x2800 | 0x6400 | 2's complement: 32-((op>>5)&0x1F) |
|
||||
| srl | 0x2C00 | 0x6600 | 2's complement: 32-((op>>5)&0x1F) |
|
||||
|
||||
#### Special Instructions
|
||||
|
||||
| Mnemonic | Opval | Format |
|
||||
|----------|-------|--------|
|
||||
| callr | 0x0D3F | Fixed opcode + 16-bit PC-relative displacement |
|
||||
| calla | 0x0D5F | Fixed opcode + 32-bit absolute address |
|
||||
| mmtm | 0x0980 | opval\|rd + 16-bit register bitmask (MSB=reg0) |
|
||||
| mmfm | 0x09A0 | opval\|rd + 16-bit register bitmask (LSB=reg0) |
|
||||
| trap | 0x0900 | opval\|(trap_num & 0x1F) |
|
||||
| rets | 0x0960 | opval\|(pop_count & 0x1F) |
|
||||
| setf | 0x0540/0x0740 | opval\|fs\|(fe<<5), field in bit 9 |
|
||||
| exgf | 0xD500 | opval\|rd, field in bit 9 |
|
||||
| dsj | 0x0D80 | opval\|rd + 16-bit displacement |
|
||||
| dsjeq | 0x0DA0 | opval\|rd + 16-bit displacement |
|
||||
| dsjne | 0x0DC0 | opval\|rd + 16-bit displacement |
|
||||
| dsjs | 0x3800+ | opval\|rd\|(disp<<5), bit 10 = negate |
|
||||
|
||||
### MOVE Addressing Modes (18 variants)
|
||||
|
||||
| Opval | Format | Field |
|
||||
|-------|--------|-------|
|
||||
| 0x4C00 | move rs, rd | No |
|
||||
| 0x4E00 | move rs, rd (cross-file) | No |
|
||||
| 0x8000/0x8200 | move rs, *rd | Yes |
|
||||
| 0x8400/0x8600 | move *rs, rd | Yes |
|
||||
| 0x8800/0x8A00 | move *rs, *rd | Yes |
|
||||
| 0x9000/0x9200 | move rs, *rd+ | Yes |
|
||||
| 0x9400/0x9600 | move *rs+, rd | Yes |
|
||||
| 0x9800/0x9A00 | move *rs+, *rd+ | Yes |
|
||||
| 0xA000/0xA200 | move rs, -*rd | Yes |
|
||||
| 0xA400/0xA600 | move -*rs, rd | Yes |
|
||||
| 0xA800/0xAA00 | move -*rs, -*rd | Yes |
|
||||
| 0xB000/0xB200 | move rs, *rd(disp) | Yes |
|
||||
| 0xB400/0xB600 | move *rs(disp), rd | Yes |
|
||||
| 0xB800/0xBA00 | move *rs(d), *rd(d) | Yes |
|
||||
| 0xD000/0xD200 | move *rs(d), *rd+ | Yes |
|
||||
| 0x0580/0x0780 | move rd, @addr | Yes |
|
||||
| 0x05A0/0x07A0 | move @addr, rd | Yes |
|
||||
| 0x05C0/0x07C0 | move @addr1, @addr2 | Yes |
|
||||
| 0xD400/0xD600 sub0 | move @addr, *rd+ | Yes |
|
||||
|
||||
"Field" column: `Yes` = appends `, 0` or `, 1` to select field register.
|
||||
Paired opcodes differ by bit 9 (field select): low = field 0, high = field 1.
|
||||
|
||||
### MOVB Addressing Modes (9 variants)
|
||||
|
||||
| Opval | Format |
|
||||
|-------|--------|
|
||||
| 0x8C00 | movb rs, *rd |
|
||||
| 0x8E00 | movb *rs, rd |
|
||||
| 0x9C00 | movb *rs, *rd |
|
||||
| 0xAC00 | movb rs, *rd(disp) |
|
||||
| 0xAE00 | movb *rs(disp), rd |
|
||||
| 0xBC00 | movb *rs(d), *rd(d) |
|
||||
| 0x0340 | movb @addr1, @addr2 |
|
||||
| 0x05E0 | movb rd, @addr |
|
||||
| 0x07E0 | movb @addr, rd |
|
||||
|
||||
### PIXT Addressing Modes (6 variants)
|
||||
|
||||
| Opval | Format |
|
||||
|-------|--------|
|
||||
| 0xF000 | pixt rs, *rd, xy |
|
||||
| 0xF200 | pixt *rs, xy, rd |
|
||||
| 0xF400 | pixt *rs, xy, *rd, xy |
|
||||
| 0xF800 | pixt rs, *rd |
|
||||
| 0xFA00 | pixt *rs, rd |
|
||||
| 0xFC00 | pixt *rs, *rd |
|
||||
|
||||
### Conditional Jump Encoding
|
||||
|
||||
Condition code in bits 11:8 of the instruction word:
|
||||
|
||||
| CC | Code | Meaning |
|
||||
|----|------|---------|
|
||||
| 0 | uc | Unconditional |
|
||||
| 1 | p | Plus (positive) |
|
||||
| 2 | ls | Low or same |
|
||||
| 3 | hi | High |
|
||||
| 4 | lt | Less than |
|
||||
| 5 | ge | Greater or equal |
|
||||
| 6 | le | Less or equal |
|
||||
| 7 | gt | Greater than |
|
||||
| 8 | c | Carry |
|
||||
| 9 | nc | No carry |
|
||||
| 10 | eq | Equal (zero) |
|
||||
| 11 | ne | Not equal |
|
||||
| 12 | v | Overflow |
|
||||
| 13 | nv | No overflow |
|
||||
| 14 | n | Negative |
|
||||
| 15 | nn | Not negative |
|
||||
|
||||
Jump forms:
|
||||
- **JR cc** (relative): `0xC000 | (cc<<8) | 0x00` + 16-bit displacement
|
||||
- **JR cc** (short): `0xC000 | (cc<<8) | disp8` (8-bit signed displacement)
|
||||
- **JA cc** (absolute): `0xC000 | (cc<<8) | 0x80` + 32-bit address
|
||||
|
||||
Displacement formula:
|
||||
- 16-bit: `target = pc + 32 + (disp << 4)`
|
||||
- 8-bit: `target = pc + 16 + (disp << 4)`
|
||||
|
||||
### The `issrl` Flag and NKREGORREGREG K-Encoding
|
||||
|
||||
The NKREGORREGREG instruction class (BTST, SRA, SRL) uses negated K constants,
|
||||
but the negation method differs between instructions, controlled by the `issrl`
|
||||
flag in the as34 assembler:
|
||||
|
||||
| Instruction | `issrl` | Negation | Encode formula | Decode formula |
|
||||
|-------------|---------|----------|----------------|----------------|
|
||||
| BTST | 0 | 1's complement | `(~K) & 0x1F` | `(~stored) & 0x1F` |
|
||||
| SRA | 1 | 2's complement | `(-K) & 0x1F` | `32 - stored` |
|
||||
| SRL | 1 | 2's complement | `(-K) & 0x1F` | `32 - stored` |
|
||||
|
||||
The two methods produce different stored values for every K from 1-31:
|
||||
|
||||
```
|
||||
K: 1 2 3 ... 27 28 29 30 31
|
||||
1's: 30 29 28 ... 4 3 2 1 0
|
||||
2's: 31 30 29 ... 5 4 3 2 1
|
||||
```
|
||||
|
||||
Using the wrong decode gives K±1 errors. In `dis34.c`:
|
||||
- BTST (0x1C00): `(~op >> 5) & 0x1f` — 1's complement — correct
|
||||
- SRA (0x2800): `32 - ((op >> 5) & 0x1f)` — 2's complement — correct
|
||||
- SRL (0x2C00): `32 - ((op >> 5) & 0x1f)` — 2's complement — correct
|
||||
|
||||
|
||||
---
|
||||
|
||||
### Round-Trip Design Decisions
|
||||
|
||||
1. **`jacc` for 16-bit displacement JR** -- dis34 outputs `jacc` (not `jrcc`)
|
||||
for the 16-bit displacement form of conditional jumps. This ensures as34
|
||||
emits the same 4-byte encoding. The 8-bit short form uses `jrcc`. The
|
||||
32-bit absolute form uses `jacc`.
|
||||
|
||||
2. **Branch-only labels** -- Only branch/call targets (jr, ja, callr, calla,
|
||||
dsj, dsjeq, dsjne, dsjs) get labels. MOVI and other data-reference
|
||||
instructions keep literal hex addresses to avoid address shifts when
|
||||
code section size changes.
|
||||
|
||||
3. **Raw data section** -- dis2asm.py reads the data section directly from the
|
||||
binary (-i flag) rather than from dis34 output, because the disassembler
|
||||
may misinterpret data words as instructions.
|
||||
|
||||
### Known Formatting Differences from tos.asm
|
||||
|
||||
These are formatting-only differences that do not affect binary output:
|
||||
|
||||
1. **`move a0, a1` vs `move a0, a1, 1`** -- Field specifier not encoded in
|
||||
reg-to-reg MOVE; assembler accepts but ignores it
|
||||
2. **Hex vs decimal** -- dis34 always uses `0x` prefix; tos.asm mixes decimal
|
||||
and hex
|
||||
3. **MMTM/MMFM register order** -- dis34 outputs registers in bit order
|
||||
(ascending); assembler accepts any order
|
||||
4. **Data section** -- Disassembles as `.word` directives and garbage
|
||||
instructions (expected for a linear disassembler)
|
||||
|
||||
---
|
||||
|
||||
## Analyze Mode (`-a`)
|
||||
|
||||
Heuristic code/data separation using two passes:
|
||||
|
||||
1. **Pass 1 (sequential scan)**: Finds blocks of valid instructions terminated
|
||||
by unconditional transfers, validates them (min 4 instructions, has a branch,
|
||||
consistent register file usage, valid branch targets).
|
||||
|
||||
2. **Pass 2 (recursive descent)**: Seeds worklist from pass 1 branch targets
|
||||
and TMS34010 hardware vector table (traps 0-31 at 0xFFFFFFE0 downward).
|
||||
Traces execution paths, validating each block (min 2 instructions, register
|
||||
file consistency) before committing.
|
||||
|
||||
### Unified Address Space in EPROM Mode
|
||||
|
||||
When multiple EPROM pairs are loaded (`-E lo1 hi1 lo2 hi2 ...`), the entire
|
||||
combined ROM is analyzed as a single unified address space. The last pair maps
|
||||
to the top of the 32-bit address space, where the TMS34010 hardware vector
|
||||
table lives (0xFFFFFFE0 downward). Branches between pairs are followed
|
||||
normally, allowing the recursive descent to trace execution across the full ROM.
|
||||
|
||||
### Heuristic Filters
|
||||
|
||||
- **Register file consistency**: Real TMS34010 code uses either A-file or B-file
|
||||
registers consistently. Blocks with >=25% minority register file usage are
|
||||
rejected as likely data.
|
||||
- **Minimum trace length**: Single-instruction traces from recursive descent are
|
||||
rejected to prevent cascading false positives.
|
||||
|
||||
### Entry Point Import/Export (`-p` / `-P`)
|
||||
|
||||
The `-P FILE` option exports all discovered code entry points (branch/call
|
||||
targets and vector table handlers) to a file, one hex address per line. This
|
||||
allows inspection and manual curation of the entry point list.
|
||||
|
||||
The `-p FILE` option imports entry points from a file and uses them as
|
||||
additional seeds for the recursive descent analysis. The file format is one
|
||||
hex address per line; lines starting with `#` are comments, blank lines are
|
||||
ignored.
|
||||
|
||||
Typical workflow:
|
||||
1. Run with `-P points.txt` to export discovered entry points
|
||||
2. Edit `points.txt` to add known code addresses or remove false positives
|
||||
3. Re-run with `-p points.txt` to improve analysis coverage
|
||||
|
||||
---
|
||||
|
||||
## Labels and Vector Table
|
||||
|
||||
### Labels
|
||||
|
||||
Branch and call targets are automatically labelled using the format
|
||||
`L` followed by the uppercase hexadecimal bit-address (e.g., `LFFF00820:`).
|
||||
Only addresses that are actually referenced by a branch, call, or vector table
|
||||
entry receive labels. The corresponding branch/call instructions use the label
|
||||
name instead of a raw hex address.
|
||||
|
||||
### Vector Table Display
|
||||
|
||||
When the ROM maps to the top of the 32-bit address space (as in EPROM modes),
|
||||
the TMS34010 hardware vector table (traps 0–31 at 0xFFFFFFE0 downward) is
|
||||
displayed as labeled `.long` entries with standard vector names:
|
||||
|
||||
```
|
||||
RESET:
|
||||
ffffffe0: .long LFFCEF750
|
||||
INT1:
|
||||
ffffffc0: .long LFFD7D290
|
||||
INT2:
|
||||
ffffffa0: .long LFFD7DDB0
|
||||
ILLOP:
|
||||
fffffe80: .long LFF8632E0
|
||||
```
|
||||
|
||||
The handler addresses are shown as label references, and the corresponding
|
||||
handler code receives matching labels in the disassembly output.
|
||||
|
||||
---
|
||||
|
||||
## TMS34010 I/O Registers
|
||||
|
||||
The TMS34010 has 28 memory-mapped I/O registers in the bit-address range
|
||||
`0xC0000000`–`0xC00001F0`, spaced 0x10 apart (16 bits each). The disassembler
|
||||
automatically annotates instructions that reference these addresses with the
|
||||
register name as a comment.
|
||||
|
||||
| Bit Address | Register | Description |
|
||||
|----------------|----------|---------------------------------|
|
||||
| `0xC0000000` | HESYNC | Horizontal End Sync |
|
||||
| `0xC0000010` | HEBLNK | Horizontal End Blank |
|
||||
| `0xC0000020` | HSBLNK | Horizontal Start Blank |
|
||||
| `0xC0000030` | HTOTAL | Horizontal Total |
|
||||
| `0xC0000040` | VESYNC | Vertical End Sync |
|
||||
| `0xC0000050` | VEBLNK | Vertical End Blank |
|
||||
| `0xC0000060` | VSBLNK | Vertical Start Blank |
|
||||
| `0xC0000070` | VTOTAL | Vertical Total |
|
||||
| `0xC0000080` | DPYCTL | Display Control |
|
||||
| `0xC0000090` | DPYSTRT | Display Start Address |
|
||||
| `0xC00000A0` | DPYINT | Display Interrupt |
|
||||
| `0xC00000B0` | CONTROL | Control Register |
|
||||
| `0xC00000C0` | HSTDATA | Host Data |
|
||||
| `0xC00000D0` | HSTADRL | Host Address Low |
|
||||
| `0xC00000E0` | HSTADRH | Host Address High |
|
||||
| `0xC00000F0` | HSTCTLL | Host Control Low |
|
||||
| `0xC0000100` | HSTCTLH | Host Control High |
|
||||
| `0xC0000110` | INTENB | Interrupt Enable |
|
||||
| `0xC0000120` | INTPEND | Interrupt Pending |
|
||||
| `0xC0000130` | CONVSP | Conversion Source Pitch |
|
||||
| `0xC0000140` | CONVDP | Conversion Destination Pitch |
|
||||
| `0xC0000150` | PSIZE | Pixel Size |
|
||||
| `0xC0000160` | PMASK | Pixel/Plane Mask |
|
||||
| `0xC00001B0` | DPYTAP | Display Tap Point |
|
||||
| `0xC00001C0` | HCOUNT | Horizontal Counter (read-only) |
|
||||
| `0xC00001D0` | VCOUNT | Vertical Counter (read-only) |
|
||||
| `0xC00001E0` | DPYADR | Display Address |
|
||||
| `0xC00001F0` | REFCNT | DRAM Refresh Counter |
|
||||
|
||||
Registers at `0xC0000170`–`0xC00001A0` are reserved/unused.
|
||||
|
||||
References to these registers also serve as a heuristic signal in analyze mode:
|
||||
blocks that access I/O registers are almost certainly real code, so the
|
||||
validation requirements are relaxed for such blocks.
|
||||
Executable
BIN
Binary file not shown.
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Inspect an *address-ordered* VT1200 ROM image.
|
||||
|
||||
Expects the image produced by ``reconstruct.py`` in the default (linear) mode,
|
||||
where file offset == physical byte - 0x07E40000. Reports the reset vector, the
|
||||
interrupt vector page and each bank header checksum.
|
||||
|
||||
Usage:
|
||||
python3 vt1200_map.py vt1200_v22.bin
|
||||
"""
|
||||
import struct
|
||||
import sys
|
||||
|
||||
ROM_BASE = 0x07E40000
|
||||
BANK = 0x40000 # 256 KiB per EPROM pair
|
||||
N_BANKS = 7
|
||||
VECTOR_PAGE = 0x80 # 32 x 32-bit entries at the top of the image
|
||||
|
||||
|
||||
def main(path):
|
||||
d = open(path, "rb").read()
|
||||
n = len(d)
|
||||
print(f"image : {path} ({n} bytes = 0x{n:X})")
|
||||
print(f"load base : byte 0x{ROM_BASE:08X} bit 0x{ROM_BASE << 3:08X}")
|
||||
print(f"top byte : 0x{ROM_BASE + n - 1:08X} bit 0x{(ROM_BASE + n - 1) << 3:08X}")
|
||||
|
||||
# --- reset vector: last entry of the vector page ---
|
||||
rv = struct.unpack_from("<I", d, n - 4)[0]
|
||||
tgt = rv >> 3
|
||||
off = tgt - ROM_BASE
|
||||
where = f"file 0x{off:06X}" if 0 <= off < n else "OUT OF RANGE"
|
||||
print(f"\nreset vector: 0x{rv:08X} -> byte 0x{tgt:08X} ({where})")
|
||||
print(f" [8:16] = {d[8:16].hex(' ')}")
|
||||
|
||||
# --- interrupt vector page (last 128 bytes) ---
|
||||
print(f"\nvector page @ file 0x{n - VECTOR_PAGE:06X} "
|
||||
f"(logical bit 0xFFFFFC00..0xFFFFFFFF):")
|
||||
base = n - VECTOR_PAGE
|
||||
for k in range(VECTOR_PAGE // 4):
|
||||
v = struct.unpack_from("<I", d, base + 4 * k)[0]
|
||||
o = (v >> 3) - ROM_BASE
|
||||
tag = f"-> file 0x{o:06X}" if 0 <= o < n else f"(0x{v:08X})"
|
||||
print(f" vec{k:2d} = 0x{v:08X} {tag}")
|
||||
|
||||
# --- per-bank header: [checksum16][?][0xAAAA][0x5555] ---
|
||||
print("\nbank headers (h0 = 16-bit word sum from the magic word to bank end):")
|
||||
for b in range(N_BANKS):
|
||||
off = b * BANK
|
||||
h0, h1 = struct.unpack_from("<HH", d, off)
|
||||
words = struct.unpack_from(f"<{(BANK - 4) // 2}H", d, off + 4)
|
||||
s = sum(words) & 0xFFFF
|
||||
print(f" bank {b} byte 0x{ROM_BASE + off:08X}"
|
||||
f" hdr=0x{h0:04X} 0x{h1:04X} aaaa 5555"
|
||||
f" sum=0x{s:04X} checksum_ok={s == h0}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
raise SystemExit(__doc__)
|
||||
main(sys.argv[1])
|
||||
Reference in new issue
Block a user