162 lines
5.4 KiB
Python
162 lines
5.4 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Iterable
|
|
|
|
from .decoder import H8536Decoder
|
|
from .formatting import label_for
|
|
from .model import Instruction
|
|
from .tables import FLOW_STOP
|
|
|
|
|
|
def trace(decoder: H8536Decoder, starts: Iterable[int], start_limit: int, end_limit: int) -> dict[int, Instruction]:
|
|
decoded: dict[int, Instruction] = {}
|
|
queue = [(addr, decoder.br) for addr in starts if start_limit <= addr < end_limit]
|
|
seen_starts: set[tuple[int, int | None]] = set()
|
|
|
|
while queue:
|
|
pc, br = queue.pop(0)
|
|
if (pc, br) in seen_starts:
|
|
continue
|
|
seen_starts.add((pc, br))
|
|
|
|
while start_limit <= pc < end_limit:
|
|
decoder.br = br
|
|
if pc in decoded:
|
|
break
|
|
ins = decoder.decode(pc)
|
|
decoded[pc] = ins
|
|
next_br = ins.br_value if ins.writes_br else br
|
|
for target in ins.targets:
|
|
if start_limit <= target < end_limit and (target, next_br) not in seen_starts:
|
|
queue.append((target, next_br))
|
|
if ins.kind in FLOW_STOP or not ins.fallthrough:
|
|
break
|
|
pc = (pc + max(ins.size, 1)) & 0xFFFF
|
|
br = next_br
|
|
|
|
return decoded
|
|
|
|
|
|
def linear_sweep(decoder: H8536Decoder, start: int, end: int) -> dict[int, Instruction]:
|
|
decoded: dict[int, Instruction] = {}
|
|
pc = start
|
|
br = decoder.br
|
|
while pc < end:
|
|
decoder.br = br
|
|
ins = decoder.decode(pc)
|
|
decoded[pc] = ins
|
|
if ins.writes_br:
|
|
br = ins.br_value
|
|
pc += max(ins.size, 1)
|
|
return decoded
|
|
|
|
|
|
def collect_labels(instructions: Iterable[Instruction], vectors: dict[int, tuple[str, int]]) -> dict[int, str]:
|
|
labels: dict[int, str] = {}
|
|
for _vector_addr, (name, target) in vectors.items():
|
|
labels.setdefault(target, f"vec_{name}_{target:04X}")
|
|
for ins in instructions:
|
|
for target in ins.targets:
|
|
labels.setdefault(target, label_for(target))
|
|
return labels
|
|
|
|
|
|
def collect_function_entries(
|
|
instructions: Iterable[Instruction],
|
|
vectors: dict[int, tuple[str, int]],
|
|
) -> set[int]:
|
|
entries = {target for _name, target in vectors.values()}
|
|
for ins in instructions:
|
|
if ins.kind == "call":
|
|
entries.update(ins.targets)
|
|
return entries
|
|
|
|
|
|
def assign_functions(instructions: dict[int, Instruction], entries: set[int]) -> dict[int, int]:
|
|
owners: dict[int, int] = {}
|
|
current: int | None = None
|
|
for address in sorted(instructions):
|
|
if address in entries:
|
|
current = address
|
|
if current is not None:
|
|
owners[address] = current
|
|
if instructions[address].kind in {"return", "rte", "sleep"}:
|
|
current = None
|
|
return owners
|
|
|
|
|
|
def build_functions(
|
|
instructions: dict[int, Instruction],
|
|
vectors: dict[int, tuple[str, int]],
|
|
labels: dict[int, str],
|
|
) -> list[dict[str, object]]:
|
|
entries = collect_function_entries(instructions.values(), vectors)
|
|
owners = assign_functions(instructions, entries)
|
|
vector_sources: dict[int, list[str]] = {}
|
|
for _vector_addr, (name, target) in vectors.items():
|
|
vector_sources.setdefault(target, []).append(name)
|
|
|
|
functions: dict[int, dict[str, object]] = {}
|
|
for address, owner in owners.items():
|
|
ins = instructions[address]
|
|
function = functions.setdefault(
|
|
owner,
|
|
{
|
|
"start": owner,
|
|
"label": labels.get(owner, label_for(owner)),
|
|
"sources": vector_sources.get(owner, []),
|
|
"instruction_count": 0,
|
|
"end": owner,
|
|
"calls": [],
|
|
"unresolved_calls": 0,
|
|
},
|
|
)
|
|
function["instruction_count"] = int(function["instruction_count"]) + 1
|
|
function["end"] = max(int(function["end"]), ins.address + max(ins.size, 1) - 1)
|
|
if ins.kind == "call":
|
|
if ins.targets:
|
|
calls = function["calls"]
|
|
assert isinstance(calls, list)
|
|
for target in ins.targets:
|
|
if target not in calls:
|
|
calls.append(target)
|
|
else:
|
|
function["unresolved_calls"] = int(function["unresolved_calls"]) + 1
|
|
|
|
return [functions[start] for start in sorted(functions)]
|
|
|
|
|
|
def build_call_graph(
|
|
instructions: dict[int, Instruction],
|
|
vectors: dict[int, tuple[str, int]],
|
|
labels: dict[int, str],
|
|
) -> dict[str, object]:
|
|
entries = collect_function_entries(instructions.values(), vectors)
|
|
owners = assign_functions(instructions, entries)
|
|
nodes = build_functions(instructions, vectors, labels)
|
|
edges: list[dict[str, object]] = []
|
|
seen: set[tuple[int, int]] = set()
|
|
|
|
for ins in instructions.values():
|
|
if ins.kind != "call" or not ins.targets:
|
|
continue
|
|
source = owners.get(ins.address)
|
|
if source is None:
|
|
continue
|
|
for target in ins.targets:
|
|
key = (source, target)
|
|
if key in seen:
|
|
continue
|
|
seen.add(key)
|
|
edges.append(
|
|
{
|
|
"from": source,
|
|
"from_label": labels.get(source, label_for(source)),
|
|
"to": target,
|
|
"to_label": labels.get(target, label_for(target)),
|
|
"call_site": ins.address,
|
|
},
|
|
)
|
|
|
|
return {"nodes": nodes, "edges": sorted(edges, key=lambda edge: (edge["from"], edge["to"]))}
|