1
0

Emulator learnings folded back into decompiler

This commit is contained in:
Aiden
2026-05-25 19:11:22 +10:00
parent 1fabf6587d
commit d2e7609bbf
16 changed files with 1468 additions and 87 deletions

View File

@@ -15,6 +15,12 @@ TX_BUFFER_END = TX_CHECKSUM_ADDRESS
TX_INDEX_ADDRESS = 0xF9C2
TX_FRAME_LENGTH = 6
CHECKSUM_SEED = 0x5A
FRT1_OCIA_VECTOR_ADDRESS = 0x0062
FRT1_OCIA_ISR_ADDRESS = 0xBEEA
FRT1_TCSR_ADDRESS = 0xFE91
POST_TX_REPORT_DELAY_ADDRESS = 0xF9C0
SECONDARY_TX_REPORT_DELAY_ADDRESS = 0xF9C1
PERIODIC_REPORT_COUNTDOWN_ADDRESS = 0xF9C6
RX_FRAME_START = 0xF860
RX_CHECKSUM_ADDRESS = 0xF865
@@ -60,7 +66,7 @@ def analyze_serial_reconstruction(
) -> dict[str, object]:
"""Reconstruct conservative serial-frame candidates from independent evidence."""
ordered = _instruction_sequence(instructions)
evidence = _collect_tx_evidence(ordered) + _collect_rx_evidence(ordered)
evidence = _collect_tx_evidence(ordered) + _collect_rx_evidence(ordered) + _collect_timer_role_evidence(ordered)
candidates = [
candidate
for candidate in (
@@ -69,6 +75,7 @@ def analyze_serial_reconstruction(
)
if candidate is not None
]
ram_roles = _ram_roles_from_evidence(evidence)
annotations: dict[int, list[str]] = {}
instruction_metadata: dict[int, list[dict[str, object]]] = {}
@@ -88,9 +95,25 @@ def analyze_serial_reconstruction(
_instruction_metadata(candidate, item, address, comment),
)
for role in ram_roles:
for item in role["evidence"]:
if not isinstance(item, Mapping):
continue
comment = _comment_for_ram_role(role, item)
for address in item.get("addresses", []):
if not isinstance(address, int):
continue
annotations.setdefault(address, [])
if comment not in annotations[address]:
annotations[address].append(comment)
instruction_metadata.setdefault(address, []).append(
_ram_role_instruction_metadata(role, item, address, comment),
)
return {
"kind": "serial_reconstruction",
"candidates": candidates,
"ram_roles": ram_roles,
"evidence": evidence,
"required_evidence": {
"tx": list(_TX_REQUIRED_EVIDENCE),
@@ -139,6 +162,7 @@ def serial_reconstruction_json_payload(analysis: Mapping[str, object] | None) ->
return {
"kind": "serial_reconstruction",
"candidates": [],
"ram_roles": [],
"evidence": [],
"required_evidence": {
"tx": list(_TX_REQUIRED_EVIDENCE),
@@ -148,6 +172,7 @@ def serial_reconstruction_json_payload(analysis: Mapping[str, object] | None) ->
return {
"kind": analysis.get("kind", "serial_reconstruction"),
"candidates": analysis.get("candidates", []),
"ram_roles": analysis.get("ram_roles", []),
"evidence": analysis.get("evidence", []),
"required_evidence": analysis.get(
"required_evidence",
@@ -408,6 +433,69 @@ def _collect_rx_evidence(ordered: list[Instruction]) -> list[dict[str, object]]:
return evidence
def _collect_timer_role_evidence(ordered: list[Instruction]) -> list[dict[str, object]]:
evidence: list[dict[str, object]] = []
tick_ack = [
ins
for ins in ordered
if ins.address == FRT1_OCIA_ISR_ADDRESS and _is_bclr_bit(ins, FRT1_TCSR_ADDRESS, 5)
]
if tick_ack:
evidence.append(
_evidence(
"frt1_ocia_periodic_tick_isr",
tick_ack,
summary=(
f"candidate periodic tick ISR at {h16(FRT1_OCIA_ISR_ADDRESS)} "
f"for FRT1 OCIA vector {h16(FRT1_OCIA_VECTOR_ADDRESS)} clears OCFA"
),
vector_address=FRT1_OCIA_VECTOR_ADDRESS,
vector_address_hex=h16(FRT1_OCIA_VECTOR_ADDRESS),
isr_address=FRT1_OCIA_ISR_ADDRESS,
isr_address_hex=h16(FRT1_OCIA_ISR_ADDRESS),
),
)
for role_name, address, width_bits, summary in (
(
"post_tx_report_delay",
POST_TX_REPORT_DELAY_ADDRESS,
8,
"candidate post-TX/report delay timer is decremented by the FRT1 OCIA periodic tick ISR",
),
(
"secondary_tx_report_delay",
SECONDARY_TX_REPORT_DELAY_ADDRESS,
8,
"candidate secondary TX/report delay timer is decremented by the FRT1 OCIA periodic tick ISR",
),
(
"periodic_report_countdown",
PERIODIC_REPORT_COUNTDOWN_ADDRESS,
16,
"candidate periodic report countdown is decremented by the FRT1 OCIA periodic tick ISR",
),
):
sequence = _timer_tick_decrement_sequence(ordered, address)
if sequence:
evidence.append(
_evidence(
f"{role_name}_tick_decrement",
sequence,
summary=summary,
role_name=role_name,
ram_address=address,
ram_address_hex=h16(address),
width_bits=width_bits,
isr_address=FRT1_OCIA_ISR_ADDRESS,
isr_address_hex=h16(FRT1_OCIA_ISR_ADDRESS),
),
)
return evidence
def _tx_candidate_from_evidence(evidence: list[dict[str, object]]) -> dict[str, object] | None:
evidence_by_key = {str(item["kind"]): item for item in evidence}
missing = [key for key in _TX_REQUIRED_EVIDENCE if key not in evidence_by_key]
@@ -436,6 +524,42 @@ def _tx_candidate_from_evidence(evidence: list[dict[str, object]]) -> dict[str,
"checksum_seed": CHECKSUM_SEED,
"checksum_seed_hex": h16(CHECKSUM_SEED),
"checksum_formula": "checksum = 0x5A ^ byte0 ^ byte1 ^ byte2 ^ byte3 ^ byte4",
"roles": [
{
"name": "tx_frame",
"address": TX_BUFFER_START,
"address_hex": h16(TX_BUFFER_START),
"end_address": TX_BUFFER_END,
"end_address_hex": h16(TX_BUFFER_END),
"summary": "evidence-supported candidate SCI1 TX frame buffer",
},
{
"name": "tx_checksum",
"address": TX_CHECKSUM_ADDRESS,
"address_hex": h16(TX_CHECKSUM_ADDRESS),
"checksum_seed": CHECKSUM_SEED,
"checksum_seed_hex": h16(CHECKSUM_SEED),
"summary": "evidence-supported candidate SCI1 TX XOR checksum byte",
},
{
"name": "tx_index",
"address": TX_INDEX_ADDRESS,
"address_hex": h16(TX_INDEX_ADDRESS),
"summary": "evidence-supported candidate SCI1 TX frame index",
},
],
"tx_path": {
"kind": "interrupt_driven_txi",
"initial_tdr_write_address": _last_address(evidence_by_key["initial_send_from_buffer_start"]),
"initial_tdr_write_address_hex": h16(_last_address(evidence_by_key["initial_send_from_buffer_start"])),
"txi_indexed_tdr_write_address": _last_address(evidence_by_key["tx_isr_indexed_send"]),
"txi_indexed_tdr_write_address_hex": h16(_last_address(evidence_by_key["tx_isr_indexed_send"])),
"summary": (
"initial byte is written from the TX frame buffer, then subsequent bytes are sent "
"by the TXI path when TDRE is reasserted"
),
"tdre_caveat": "TDRE reassertion is hardware/emulator timing context; static evidence is the indexed TXI send path.",
},
"confidence": "high",
"confidence_score": 0.95,
"confidence_reason": "all required independent evidence groups were observed",
@@ -462,6 +586,46 @@ def _tx_candidate_from_evidence(evidence: list[dict[str, object]]) -> dict[str,
return candidate
def _ram_roles_from_evidence(evidence: list[dict[str, object]]) -> list[dict[str, object]]:
evidence_by_key = {str(item["kind"]): item for item in evidence}
tick = evidence_by_key.get("frt1_ocia_periodic_tick_isr")
roles: list[dict[str, object]] = []
for role_name, address, width_bits in (
("post_tx_report_delay", POST_TX_REPORT_DELAY_ADDRESS, 8),
("secondary_tx_report_delay", SECONDARY_TX_REPORT_DELAY_ADDRESS, 8),
("periodic_report_countdown", PERIODIC_REPORT_COUNTDOWN_ADDRESS, 16),
):
decrement = evidence_by_key.get(f"{role_name}_tick_decrement")
if decrement is None:
continue
role_evidence = [item for item in (tick, decrement) if isinstance(item, Mapping)]
roles.append(
{
"kind": "candidate_ram_role",
"name": role_name,
"address": address,
"address_hex": h16(address),
"width_bits": width_bits,
"confidence": "candidate/evidence-supported",
"summary": (
f"{role_name} at {h16(address)} is a candidate/evidence-supported RAM timer "
f"role; FRT1 OCIA tick ISR {h16(FRT1_OCIA_ISR_ADDRESS)} decrements it"
),
"caveat": "role name is evidence-supported from static references plus emulator-guided timer behavior, not a proved firmware symbol",
"evidence": role_evidence,
"evidence_addresses": {
str(item["kind"]): list(item["addresses"])
for item in role_evidence
},
"evidence_addresses_hex": {
str(item["kind"]): list(item["addresses_hex"])
for item in role_evidence
},
},
)
return roles
def _rx_candidate_from_evidence(evidence: list[dict[str, object]]) -> dict[str, object] | None:
evidence_by_key = {str(item["kind"]): item for item in evidence}
missing = [key for key in _RX_REQUIRED_EVIDENCE if key not in evidence_by_key]
@@ -572,6 +736,35 @@ def _instruction_metadata(
}
def _comment_for_ram_role(role: Mapping[str, object], item: Mapping[str, object]) -> str:
return (
f"candidate/evidence-supported RAM role {role['name']} at {role['address_hex']}; "
f"evidence: {item['summary']}; confidence {role['confidence']}"
)
def _ram_role_instruction_metadata(
role: Mapping[str, object],
item: Mapping[str, object],
address: int,
comment: str,
) -> dict[str, object]:
return {
"address": address,
"action": "serial_reconstruction_ram_role",
"role_name": role["name"],
"role_kind": role["kind"],
"role_address": role["address"],
"role_address_hex": role["address_hex"],
"evidence": item["kind"],
"evidence_summary": item["summary"],
"evidence_addresses": list(item["addresses"]),
"evidence_addresses_hex": list(item["addresses_hex"]),
"confidence": role["confidence"],
"comment": comment,
}
def _buffer_region_references(ordered: list[Instruction]) -> list[Instruction]:
return [ins for ins in ordered if _buffer_refs(ins)]
@@ -761,6 +954,31 @@ def _rx_xor_checksum_validation(ordered: list[Instruction]) -> list[Instruction]
return []
def _timer_tick_decrement_sequence(ordered: list[Instruction], address: int) -> list[Instruction]:
for index, ins in enumerate(ordered):
if not (
ins.address >= FRT1_OCIA_ISR_ADDRESS
and _mnemonic_root(ins.mnemonic) == "TST"
and address in ins.references
):
continue
window = ordered[index + 1 : index + 5]
decrement = next(
(
candidate
for candidate in window
if candidate.address >= FRT1_OCIA_ISR_ADDRESS
and _is_write_to_address(candidate, address)
and _mnemonic_root(candidate.mnemonic) in {"ADD:Q", "ADD:G", "ADDS", "SUB", "SUBS"}
and _immediate_source_value(candidate.operands) in {0xFFFF, 0xFF, 1}
),
None,
)
if decrement is not None and "-1" in decrement.operands:
return [ins, decrement]
return []
def _rx_rdrf_clear_before_rdr_read(ordered: list[Instruction]) -> list[Instruction]:
for index, ins in enumerate(ordered):
if not _is_bclr_bit(ins, SCI1_SSR_ADDRESS, 6):
@@ -950,6 +1168,13 @@ def _dedupe_ints(values: Iterable[int]) -> list[int]:
return output
def _last_address(item: Mapping[str, object]) -> int:
addresses = item.get("addresses", [])
if not isinstance(addresses, list) or not addresses:
return 0
return int(addresses[-1])
def _instruction_sequence(
instructions: Mapping[int, Instruction] | Iterable[Instruction],
) -> list[Instruction]:
@@ -997,6 +1222,7 @@ def _operand_mentions_address(operand: str, address: int) -> bool:
SCI1_TDR_ADDRESS: ("SCI1_TDR",),
SCI1_SSR_ADDRESS: ("SCI1_SSR",),
SCI1_RDR_ADDRESS: ("SCI1_RDR",),
FRT1_TCSR_ADDRESS: ("FRT1_TCSR",),
TX_BUFFER_START: ("TX_BUFFER",),
TX_CHECKSUM_ADDRESS: ("TX_CHECKSUM",),
TX_INDEX_ADDRESS: ("TX_INDEX",),