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(andscan-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, #hilea aN, [aN]disppairs (the compiler's canonical absolute-address materialisation) and SuperH absolutemov.l/mov.w ADDR, rNoperands. 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; newxrefsreport section +⟶codemarkers on referenced tables + per-tablexrefevidence in JSON),scan-maps --xrefs(opt-in flag; markers + JSON evidence), and the recipemaps[]annotation (attach_maps(xrefs=…)— thexrefevidence block is advisory metadata, schema stays 4.4/4.5; currently a library surface — no shipped recipe computes xrefs yet). Theanalyzexref line carries a decoder tag — e.g.[C166 · Rust decoder]or[TriCore · capstone]with a· cascade-detectedsuffix when the CPU-detection cascade found the arch; the JSONxrefsblock addsdecoderandarch_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;
analyzetotal ≈ 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.pydecodes 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 harness —
scripts/verify_c166.pydiffs 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.
- Reference-bearing forms only — the direct-memory operands of
-
Siemens MS43 identity extractor — closes the "checksum fires but
identifysays Unknown" gap: a standardcan_handle/extractextractor (manufacturers/siemens/ms43/) identifies MS43 bins by the literalMS43family string +5WK9part prefix at the exact 512 KB size gate, and extracts the program number (430069), Siemens part (5WK90027), and calibration dataset (ca430069.DAT) → match keyMS43::430069.identifynow 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 (shared5WK9prefix) by the size gate. -
CPU-detection cascade (Phase 3) — for families with no declared arch mapping,
detect_archtrial-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 killinganalyze/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_coherencecompares 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 intoscore_identityas evidence (agree +10, conflict −15 + warning) and surfaced as a consistency line inanalyze. -
a0-resolution for EDC17 (TriCore) —
arch/tricore.pynow finds the global base registera0's boot init (movh.a a0,#hi+lea a0,[a0]#disp) and resolveslea aN,[a0]dispaccesses through it (single decode pass, presence-only). Honest scope note: on the reference EDC17 corpusa0resolves 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 whosea0points into calibration flash (measured 12.8× reference growth when the a0 window is applied). The original "~10× map references" premise is corrected:a0points at runtime data, not the maps. -
DPP-value resolution for C166 —
arch/c166.pyreads the DPP0–DPP3 page registers' boot init (MOV DPPx,#pag, opcodeE6 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 throughEXTP/EXTSpage 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 skew —
openremap/_rs/Cargo.toml+Cargo.lockbumped 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; noxrefkey 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).