OpenRemap Docs

0.7.3 — 2026-08-28

The code-reference release — the first third-party integration of the 0.7.x cycle's second goal: capstone-powered disassembly that turns "is this table actually read by code?" into a positive scoring signal.

Added

  • Code-reference (xref) signal — a new engine step between map discovery and scoring. analyze (and scan-maps --xrefs) now disassemble the flash code regions with capstone and collect every byte offset that real instructions statically reference. A table whose data block is referenced by code is almost certainly a genuine calibration map — it receives a score bonus (+0.06, capped at 1.0) and carries evidence ("referenced by code at 0x…").

    • Presence-only by design — a table is never demoted for lacking a reference (most ECU code reaches maps through base-register addressing that is not statically resolvable). The signal can only improve ranking, never introduce false negatives.
    • Arch-gated and conservative — verified mappings only: Bosch EDC17 (Infineon TriCore), Denso/Hitachi SuperH (SH7055/7058/72531/72546), and the C166/ST10 families (Phase 2 — Rust decoder). Everything else (Delphi, Marelli, unverified CPUs) skips cleanly with a recorded reason.
    • Statically resolvable references only — TriCore movh.a aN, #hi
      • lea aN, [aN]disp pairs (the compiler's canonical absolute-address materialisation) and SuperH absolute mov.l/mov.w ADDR, rN operands. No register-state tracking.
    • Data-driven load-base detection — ECU code references physical addresses (EDC17 calibrations live at 0x80000000+) while the file is a linear image; the load base (0, 0x80000000, …) is auto-detected per file from table-data hits — no hardcoded manufacturer knowledge.
    • Surfaced in: analyze (always on; new xrefs report section + ⟶code markers on referenced tables + per-table xref evidence in JSON), scan-maps --xrefs (opt-in flag; markers + JSON evidence), and the recipe maps[] annotation (attach_maps(xrefs=…) — the xref evidence block is advisory metadata, schema stays 4.4/4.5; currently a library surface — no shipped recipe computes xrefs yet). The analyze xref line carries a decoder tag — e.g. [C166 · Rust decoder] or [TriCore · capstone] with a · cascade-detected suffix when the CPU-detection cascade found the arch; the JSON xrefs block adds decoder and arch_source (declared | detected).
    • Measured on the real 4 MB EDC17: hundreds to ~10³ references resolve into the genuine calibration area (load base 0x80000000) and land in high-score tables (14 bonused on the reference bin); pass cost ≈ 4 s on the dev machine (capstone decode of ~2.6 MB of code; analyze total ≈ 18 s). Exact counts are file-dependent.
    • Backed by capstone 5.0.9 (BSD-3, compiled C — universal wheels, same verified platform story as orjson/rapidfuzz in 0.7.2).
  • C166/ST10 decoder for the xref signal (Phase 2 of the arch-domain work) — the reference-bearing subset of the C166 ISA, built directly in Rust (openremap/_rs/src/arch/c166.rs, seeded from the nefmoto 6-form parser, verified against the public ST10/C166 instruction-set manuals). This unlocks the signal for the largest remaining ECU block — the Bosch/Siemens C166 families:

    • Reference-bearing forms only — the direct-memory operands of MOV/MOVB (opcodes 0x84/0x94/0xA4/0xB4/0xF2/0xF3/0xF6/0xF7), not the full ISA (indexed and immediate forms are deliberately excluded).
    • DPP-window translation — C166 16-bit data addresses are windowed at runtime through DPP registers (physical = page << 14 | addr14); the window base W (file = (addr & 0x3FFF) + W) is hit-tested against the table data spans per file, the same empirical approach as the TriCore load-base detection. ME7's flash base (0x800000) and MS43's program base (−0x80000) fall out of the search — no hardcoded manufacturer knowledge.
    • New families covered (arch table): ME7 (+variants), ME9, EDC15, EDC16, MS43, PPD1.x, SID801/803, Simtec56, EMS2000 — extractor coverage grows from 5 to 15 (all C166 direct-memory references validated on the real corpus: ME7 ~3k, EDC15 ~1.7k, MS43 ~3k translated references land in high-score table spans at a single window base).
    • Oracle-verified against Ghidra (dev machine): the size table is generated from the mumbel/Ghidra_C166 SLEIGH spec (the 4-byte set is first-byte-deterministic, zero conflicts) plus the nefmoto/manual forms its ...-abbreviated definitions cover. scripts/verify_c166.py decodes every instruction of real binaries with both engines: 0 length disagreements and 0 ref-mnemonic disagreements on ME7 / EDC15 / MS43 / PPD; MS43 100% — the only unmatched positions are Ghidra spec gaps (e.g. EDC15's 0xC3 erased-fill and SFR encodings), never a decode difference. This caught a real gap in the nefmoto- seeded table (the AND/OR/XOR/CMP-inc-dec and bit-op 4-byte families, which nefmoto never covered).
    • Dev-only parity harnessscripts/verify_c166.py diffs our decoder against Ghidra (pyghidra + mumbel/Ghidra_C166 SLEIGH, Apache-2.0) on the dev machine; skipped cleanly when Ghidra is absent, never a runtime dependency.
  • Siemens MS43 identity extractor — closes the "checksum fires but identify says Unknown" gap: a standard can_handle/extract extractor (manufacturers/siemens/ms43/) identifies MS43 bins by the literal MS43 family string + 5WK9 part prefix at the exact 512 KB size gate, and extracts the program number (430069), Siemens part (5WK90027), and calibration dataset (ca430069.DAT) → match key MS43::430069. identify now reports Siemens/MS43 with HIGH confidence (was Unknown), so the xref signal and checksum coherence run for the whole MS43 family. STRONG evidence-based detection, disambiguated from Simtec56 (shared 5WK9 prefix) by the size gate.

  • CPU-detection cascade (Phase 3) — for families with no declared arch mapping, detect_arch trial-decodes the candidate decoders (c166 → tricore → sh/SH2 → sh/SH2A; x86 excluded) and keeps the first whose references land in the map-table spans (gates: ≥50 instructions, ≥3 span hits). Presence-only and conservative — a wrong arch scatters its references and is rejected, never a false bonus. Each trial runs in a fork-isolated child so a decoder crash (capstone's SH-2A out-of-bounds read, CVE-2026-55894 — reproduced on a real Bosch M1.3 bin) rejects the candidate instead of killing analyze/scan-maps. Measured on the corpus: SIMOS → c166 (~12k instructions, 138 table-span references); GS20 → clean no-code-regions; M1.3 → clean no-arch-detected.

  • Coherence check — a rule-based cross-check that identity / checksum / xref must agree, or explain why. check_coherence compares the identity family against the checksum-detected family and the xref arch, producing agree / stale / gap / conflict verdicts (stale = a tuned file whose checksum wasn't recalculated; gap = a detector never ran; conflict = the only hard red flag). Fed into score_identity as evidence (agree +10, conflict −15 + warning) and surfaced as a consistency line in analyze.

  • a0-resolution for EDC17 (TriCore)arch/tricore.py now finds the global base register a0's boot init (movh.a a0,#hi + lea a0,[a0]#disp) and resolves lea aN,[a0]disp accesses through it (single decode pass, presence-only). Honest scope note: on the reference EDC17 corpus a0 resolves into the RAM window (0xd0000000), not the calibration flash (0x80000000), so the default pipeline's data-driven base detection keeps the flash base and the ~35,944 a0 targets fall out of file range — a latent win that fires on EDC17 variants whose a0 points into calibration flash (measured 12.8× reference growth when the a0 window is applied). The original "~10× map references" premise is corrected: a0 points at runtime data, not the maps.

  • DPP-value resolution for C166arch/c166.py reads the DPP0–DPP3 page registers' boot init (MOV DPPx,#pag, opcode E6 xx lo hi, found by a boot-area byte scan — Python-only, no Rust change) and resolves direct-memory operands to exact 24-bit addresses, preferring this over the empirical window search. On ME7 it recovers the true flash base 0x800000 (the old window search's 0x14000 hit was coincidental); EDC15/MS43 reach maps through EXTP/EXTS page overrides that cannot be followed statically, so the DPP path is cleanly rejected there and falls back to the window search unchanged.

Fixed

  • Cargo crate version skewopenremap/_rs/Cargo.toml + Cargo.lock bumped 0.7.2 → 0.7.3 (Python was already 0.7.3; found by the pre-ship release audit, docs/internal/version-audits/0.7.3-audit.md).
  • attach_maps(xrefs=…) had zero test coverage — two tests now cover the parameter (referenced-by-code evidence embedded; no xref key without it); the changelog wording clarifies it is a dormant library surface (no shipped recipe computes xrefs yet).
  • Coverage-count drift — decoder coverage 5 → 15 extractors (the C166 bullet was one short pre-MS43); AGENTS.md registry count 35 → 36 (Siemens 6 → 7); resume "14 of 35" → "15 of 36".
  • Stale v1-era EDC17 measurement — the "~121 refs / 14 tables / 0.855 / ≈4 s" headline was a v1-era measurement that no longer reproduces exactly; softened to file-dependent wording.
  • Changelog header date — 2026-08-26 → 2026-08-28 (the release now carries 2026-08-27 work).