Firmware Reversing - krcenov/RNS510-Map-Viewer GitHub Wiki

Firmware Reversing

This page is about the RNS510 head unit's own firmware (the PowerPC/VxWorks application that reads the map database this wiki otherwise documents), not the map disc format itself. Source: a real Continental/VW factory "Software Loading" (SWL) CD, VwSwIndex 5238, extracted at 5238_ALL, whose APPS\SILVER_1\RNSMIDEC\PROG\FHDD6.FLI (85,658,916 bytes) is the actual application image flashed onto the unit. Reader/analysis code: research/swl_5238_reader.py.

FHDD6.FLI splits into 3 regions: 0-24MB (native PowerPC code + debug strings — this page's focus), 24-76MB (Java classes), 76-85.6MB (VxWorks kernel/BSP — see Theft Protection and the CAN Gateway for TpPvVerifyPin, the one thing this project has directly reached inside it). Compiler: GNU C gcc-2.96 (2.96+ MW/LM) 19990621 AltiVec VxWorks 5.5.

All addresses are file offsets into FHDD6.FLI unless marked VA. VA = file_offset + 0xf688dcf4 (see below).

Architecture

The application processor runs VxWorks 5.5 hosting an embedded Java VM (Insignia Jeode EVM). The touchscreen UI is a Java application (packages vdo.rns.*, vdo.most.*, vdo.nav.*, vdo.bsl.* — a Siemens VDO/Continental in-house codebase). The actual navigation/routing engine is native PowerPC code, internally named "navicore" (codename "e60" — see the real internal build path F:\Entwicklung\e60-tools\CodingTest\cp2\PPC603gnu found in CTEST.OUT's own DWARF debug info, HOST BSP and Vehicle Coding), running on a Freescale/Motorola MPC5200/5200B SoC, built with a Wind River GCC 2.96 cross-toolchain. All 3 layers — native navicore code, the Java class store, and the VxWorks kernel/BSP — are packed into the one FHDD6.FLI file described above.

A real, complete on-disk module inventory for the live system, recovered from a real flashed maintenance script (WA/DEL_OUT.WSH, "Delete out files acc. to CR 54634") that lists every real loadable .out object it tidies up, by its real full path — the clearest single real confirmation of how the running system is actually laid out on /tffs0/: lib/mm/ (multimedia) holds mostgen.out (MOST-bus protocol generation), madMM.out, WMADEC.out (WMA audio decoder), MpegAtaSwitcher.out, mm_drives.out, dab.out, util.out, servicebroker.out, micromedia.out/micromedia_wma.out, mpeg4_celp_lib.out/mpeg4_celp_micromedia.out (MPEG-4 CELP speech codec — likely TTS/voice-prompt audio), recorder.out, msdcontrol.out; lib/pos/pos.out (positioning); lib/arriba/ navcore.out — a SECOND real internal codename for the navigation engine, "arriba", alongside the "e60"/"navicore" name already known from DWARF paths below — this one is the real deployed library path, not a build-tree path; lib/kernel/libDelayImage.out/fileMgr.out; plus data/SysCfgConfig, a real system-config data file sitting alongside the code.

2 satellite MCUs identified (checked against real datasheets — neither has anywhere near enough RAM to run the UI/nav engine itself, but likely handle sub-systems): ST10F276E (STMicroelectronics, 16-bit, 64MHz, 68KB RAM, dual CAN) — matches the size of VUCI\...\GATEWAY.FLI/GWBOOTL.FLI; confirmed the real MOST-bus/CAN gateway controller (fully explored on Theft Protection and the CAN Gateway, including a real disassembler for this exact chip). TMS470MF04207 (TI ARM Cortex-M3 automotive safety MCU, 24KB RAM, dual CAN) — likely tied to the SYS_CODE_SYSTEM_SAFE/immobilizer-style security-code subsystem seen in the menu tree below; unconfirmed.

The full menu tree, recovered from compiled class names: every screen/overlay the UI can show is a vdo/rns/ui/Menu_<NAME>Rules or MenuNet_<NAME>Rules class — 86 unique screens total, spanning Startup/System, Navigation/Map, Radio/Tuner, Media/Audio, Video, Phone, Traffic/TMC, Setup, and Test/Service (TESTMODE, TESTMODE_SYSTEM, TESTMODE_DAB_MONITOR_MAIN, SERVICE — likely technician diagnostic screens, not investigated further). Notably absent: no Climate Control or Parking Sensor screen exists anywhere in the menu tree — those subsystems only have backend status- integration classes (audio ducking, icons), never a navigable screen, in this firmware build. A real destination-entry UI package also exists (vdo/rns/app/nav/std/ctrls/addressentry/ — AECitySuggestionList, AEStreetSuggestionList, AESpellerType, a letter-by-letter spelling control) reached through a generic native search bridge (EdalWrapper*, also used for POI/along-route lookups) — corroborating, not proving, that a real on-unit search screen like this wiki's own road/city name-search catalog could plausibly serve; its exact backing file can't be confirmed (see "romized bytecode" below).

UI customization — a real, tested wall: most Java class bodies use a proprietary "romized"/pre-linked constant-pool format from the embedded JVM's ROM-build toolchain, not standard javac bytecode — only trivial classes (interfaces with no real method bodies) survive in standard, decompilable form. This blocks decompiling/patching the interesting UI logic (ClimateControlAdapter, SystemBundle, etc.) with normal tools. A separate attempt to reach the same logic via the native code's own parsing routines also hit a wall at the time (no load base recoverable for the 0-24MB region) — since resolved for the 1.2MB-9MB sub-range, see "The load base" below, though that doesn't by itself unlock the romized Java layer. Net effect: UI-level modding (new buttons/screens) is not achievable with this project's current tools — this is why the project's own practical focus is map editing and firmware/coding reverse-engineering, not UI modding.

The Extensions Database (EDB) — a real, limited customization API: vdo.nav.api.edb.* is a JDBC-like client to an embedded SQLite engine in the native navicore process (confirmed via literal SQL strings like INSERT OR IGNORE INTO Tiles VALUES (?1,?2,?3,?4,?5)). Clients can only call a fixed catalog of precompiled statement templates, not arbitrary SQL. Confirmed real use: POI databases (.db3 files under EDB/POI/, matching the map disc's own EDB\POI folder — see config/create_cd and the Disc-Root Subsystems), satellite-radio song tagging, weather/traffic tile caching. Schema is point data only (name/lat/lon) — no road geometry or routing fields, so this cannot be used to add roads, only custom POIs ("Personal POI" is an explicit, supported list type).

A real, previously-uncatalogued ADAS subsystem

Found while tracing mv_convert_slot_nr and mv_lefthand_traffic (both call into a then-unknown CArr_AdasMain class) and confirmed at scale by searching the full symbol catalog for related names: this codebase contains a real, substantial Advanced Driver Assistance System integration, previously entirely unmentioned anywhere on this wiki. Real, confirmed class/function names (all under 9MB, several hundred real symbol-table entries, not individually catalogued here):

  • Adas_Most_Dispatcher — a real, dedicated MOST-bus dispatcher class for this subsystem specifically (SendToMOST, SendEvent, DumpMessage, GetLocalDeviceId, GetMostAvailable, GetFBlockAvailable, SetTransmissionMode/GetTransmissionMode, SetDispatcherState, Instance/RemoveInstance — a singleton) — the same general real "FBlock available"/"local device id" vocabulary already confirmed on the real ConfigFblockTest.c protocol documented on RNS510 Serial Console Commands, now shown to be a shared pattern across multiple real MOST FBlocks, not unique to vehicle coding.
  • CArr_AdasMain — the real main ADAS task/controller class: SendToMOST/Send, Update, SetState (takes a real ADAS_MAIN_STATE enum), Deactivate, RetrieveNavSysInfData, UpdateSettingsDueToDVDChange, initializePartlyDatabase/ cleanupPartlyDatabase, CallbackFunction.
  • AdasTools — a real utility class for unpacking datagrams received from the ADAS module: UnPackNavGraphDatagram, UnPackNavSysInfDatagram, UnPackNavGraphResyncDatagram, UnPackNavGraphMatchDatagram, plus angle-transform helpers (ReverseTransformAngle, TransformHeadingAngleRelative) and several Dump* diagnostic functions (one, DumpNavGraph, now traced) — strong evidence the real wire format is a family of named NAV_GRAPH/ NAV_SYS_INF/NAV_GRAPH_RESYNC/NAV_GRAPH_MATCH struct types (seen directly as real parameter types in the symbol table).
  • AdasSpeedTime/AdasEntranceExit/AdasRouteObject/ AdasMessage/AdasDBAccess (already partly known from ResolveUnsetNodeID) — real, separate helper classes; AdasDBAccess::GetStreetTypeFromDatabase and AdasEntranceExit::GetStreetTypeForRamp tie this subsystem directly into the road-network/street-type data this wiki otherwise documents from the map-disc side.
  • A separate, parallel "VAdas" name family — CfcTypeVadasOpOrTimePt/CfcTypeVAdasTimeDuration/ CfcTypeVAdasTimeStart/CfcTypeVAdasSpeedLimit — real classes for time-windowed, direction-aware speed limits (ADAS_SPEED_DIRECTION, PSD_getAdasSpeedLimit, see 0x78834c) and day/week/month-scoped validity windows (IsValidWeek/IsValidYear/ IsValidHourAndMinute/IsValidExactWeekAndWeekDay) — consistent with real variable/time-restricted speed-limit signage (school zones, time-of-day limits), not just static posted limits.
  • ElectronicHorizon — a real, previously-uncatalogued class found via registerBlocking (CArr_DYNA_Main::registerBlocking, confirmed calling ElectronicHorizon::Draw(const MapPoint&)). "Electronic Horizon" is a genuine, well-known real-world ADAS term — predictive road-ahead data (curvature, speed limits, intersections some distance ahead) feeding things like adaptive cruise control and predictive gear shifting on real vehicles. Only this one method found under 9MB; a real, concrete, named lead for a future session, not yet followed further.

Net read, revised: the RNS510 firmware has a real, substantial, previously undocumented integration with an external ADAS-related module communicating over the MOST bus through its own dedicated FBlock dispatcher, feeding speed-limit and navigation-graph data into the existing navigation/TMC pipeline (TIMEx_FlowExplorer, CArr_NavigationMain) confirmed by DumpNavGraph and 0x78834c, with real dynamic road-blocking registration (registerBlocking) tying in genuine floating-point distance math, actual SQLite, and a named ElectronicHorizon concept.

The earlier "plausibly a forward-facing camera unit doing traffic-sign recognition" guess is retracted — it went beyond what the evidence supports, and sits poorly against this head unit generation's known real capabilities: the RNS-510 is a ~2007-2008, pre-MQB-era VW "Silver" platform unit, well-documented as using static NAVTEQ-era map data with no onboard camera/radar sensor fusion and no path to display camera/radar-derived driver-assist data. Two things point away from live camera input specifically: PSD_getAdasSpeedLimit takes a direction parameter (the shape of a direction-dependent map-attribute lookup, not a live sign-detection report), and the complete 1,573-name whole-file reloc-confirmed catalog contains no camera/video-decoder class whatsoever. "Electronic Horizon," as the term was actually used by this era's real automotive suppliers (Continental/Siemens VDO — RNS-510's real navigation supplier), was itself a map-attribute-driven concept (predictive curve/speed data computed from digital map data, not sensor fusion), so finding this code here isn't anachronistic at all, as long as it's read as map-sourced rather than camera-sourced.

Best-supported current model: this is real, genuinely-compiled supplier platform code (shared across a family of related head units/trim levels/model years from the same navigation-software supplier) implementing map-attribute-driven electronic-horizon-style data distribution — most likely the head unit sending its own map-derived predictive data out over MOST to some other vehicle system (e.g. adaptive transmission/headlight controllers on higher-trim platform siblings), with NAV_GRAPH_RESYNC/NAV_GRAPH_MATCH datagrams plausibly being synchronization/acknowledgment traffic in that exchange rather than incoming sensor detections. That the code exists in this binary does not mean this specific RNS-510 build exercises it against real hardware — which, per the above, it most likely doesn't. Still worth a dedicated future session to trace the actual direction of data flow (who initiates UnPackNavGraphDatagram, and what triggers it) — only 3 of the many real functions in this family have been traced so far, and that would settle the sender-vs-receiver question directly rather than by inference.

Function list

Every function this project has named and can say something concrete about, one page each. Status legend:

Symbol Meaning
🟢 Fully traced — complete disassembly (prologue to blr), real understood behavior
🟡 Found, reachable, not yet traced — real address confirmed under 9MB, a real function prologue exists nearby, but not disassembled/analyzed beyond that
🔴 Found, unreachable — real symbol-table record exists, but its address falls in the confirmed 9MB+ dead zone (see below); no code reachable with current technique
⚪ Name only — found via plain debug-string scanning, never resolved to any address (confirmed structurally unreferenced by executing code)
🟡* Partially traced — real disassembly and analysis exist but the page is deliberately not a complete line-by-line trace, either because of a genuine unresolved anomaly (e.g. no locally-visible blr/return) or because the function is too large for a full transcription to be proportionate
Function Real name Status Page
getId CfcMsgStreamedServicePoint::getId 🟢 Firmware-Fn-getId
(unnamed) — 🟢 Firmware-Fn-0x1f95d0
getNodeVidArmMP0 RoutePath::getNodeVidArmMP0 🟢 Firmware-Fn-getNodeVidArmMP0
ResolveUnsetNodeID AdasDBAccess::ResolveUnsetNodeID 🟢 Firmware-Fn-ResolveUnsetNodeID
prepareOutputForMap TIMEx_Explorer::prepareOutputForMap 🟢 Firmware-Fn-prepareOutputForMap
getIndexOfVid RoutePath::getIndexOfVid 🟡* Firmware-Fn-getIndexOfVid
calculateManeuvers ManeuverGeneratorManager::calculateManeuvers 🟡* Firmware-Fn-calculateManeuvers
addSegmentData DYNA_SegmentList::addSegmentData 🟡* Firmware-Fn-addSegmentData
filterToggleSeg (global) filterToggleSeg 🟡* Firmware-Fn-filterToggleSeg
getSegProperty RoutePath::getSegProperty 🟡* Firmware-Fn-getSegProperty
mv_check_turn (global) mv_check_turn 🟡* Firmware-Fn-mv-check-turn
mv_generate_mnvr_name (global) mv_generate_mnvr_name 🟡* Firmware-Fn-mv-generate-mnvr-name
mv_lefthand_traffic (global) mv_lefthand_traffic 🟡* Firmware-Fn-mv-lefthand-traffic
mv_convert_slot_nr (global) mv_convert_slot_nr 🟡* Firmware-Fn-mv-convert-slot-nr
DumpNavGraph AdasTools::DumpNavGraph 🟡* Firmware-Fn-DumpNavGraph
(unnamed) — 🟡* Firmware-Fn-0x78834c
TransformHeadingAngleRelative AdasTools::TransformHeadingAngleRelative 🟡* Firmware-Fn-TransformHeadingAngleRelative
UpdateSettingsDueToDVDChange CArr_AdasMain::UpdateSettingsDueToDVDChange 🟡* Firmware-Fn-UpdateSettingsDueToDVDChange
SetRouteObject AdasRouteObject::SetRouteObject 🟡* Firmware-Fn-SetRouteObject
(unnamed) — 🟢 Firmware-Fn-0x5ba570
(unnamed) — 🟡* Firmware-Fn-0x5ba5d0
getCurrentCarPosition (global) 🟢 Firmware-Fn-getCurrentCarPosition
registerBlocking CArr_DYNA_Main::registerBlocking 🟡* Firmware-Fn-registerBlocking
Draw ElectronicHorizon::Draw 🟡* Firmware-Fn-Draw-ElectronicHorizon
getIso2CountryCode CfcAspectISO2CountryCodeChangedEvent::getIso2CountryCode 🟡* Firmware-Fn-getIso2CountryCode
setRouteSectionDetourId CfcTypeJourneyRouteId::setRouteSectionDetourId 🟡* Firmware-Fn-setRouteSectionDetourId
operator new CfcGuidanceSpeedTableFuture::operator new 🟡* Firmware-Fn-CfcGuidanceSpeedTableFuture-new
(comparison fn + ~18 accessors) plausibly EHTreeNode (name not confirmed) 🟢 Firmware-Fn-EHTreeNode-struct-layout
readNodeMP0 RoutePath::readNodeMP0 🔴 Firmware-Fn-readNodeMP0
RoutePath_readNodeMP0 RoutePath::readNodeMP0 (2nd overload) 🔴 Firmware-Fn-RoutePath-readNodeMP0
RoutePath_readNodeArmMP0 RoutePath::readNodeArmMP0 🔴 Firmware-Fn-RoutePath-readNodeArmMP0
readNodeArmMP0 RoutePath::readNodeArmMP0 (2nd overload) 🔴 Firmware-Fn-readNodeArmMP0
resolveUnsetVNodeId LaneCalculation::resolveUnsetVNodeId 🔴 Firmware-Fn-resolveUnsetVNodeId
db_vid_get_map_id_V000 (accessor, name only) ⚪ Firmware-Fn-db-vid-get-map-id
db_vid_get_pcl_id_V000 (accessor, name only) ⚪ Firmware-Fn-db-vid-get-pcl-id
db_find_node (accessor, name only) ⚪ Firmware-Fn-db-find-node
db_page_pcl_V000 (accessor, name only) ⚪ Firmware-Fn-db-page-pcl

The load base

For years this project could locate real function/string names in the 0-24MB region (via plain string scanning) but could never turn a name into working code — there was no known mapping from a byte offset in the file to the runtime address (VA) the CPU would actually use, so lis/addi absolute-address instructions couldn't be resolved and disassembly was blocked.

Recovered this session: for the region's code-dense 1.2MB-9MB sub-range,

VA = file_offset + 0xf688dcf4        (mod 2^32)

How: disassembled that sub-range instruction-by-instruction (capstone, PPC, big-endian, every 4-byte-aligned offset decoded independently so one bad instruction can't desync the rest), collected every lis rX,HI16 + addi rX,rX,LO16 pair (34,652 of them, from 116,383 lis instructions) as a candidate absolute address, then ran a brute-force sliding-window scan over the sorted target values: find the window of width 24,000,000 containing the most targets. 95.1% (32,966/34,652) fall inside one window — 170x the count pure chance would predict. The window's own left edge gave the base value above.

Verified byte-exact, 3 independent ways: a real runtime log string ("bootMgrLax : Now I'm going to create the image -") landed exactly where a completely unrelated instruction's own computed address said it would; a real, fixed-width UI button-label table ("PLAY \0\0SEARCHBACK\0\0SEARCHFOR \0\0STOP \0\0PAUS...") was hit identically by 4 separate call sites; a broader unbiased spot-check turned up more coherent real content (C++ mangled names, XML config fragments) with zero cherry-picking.

Confirmed limit — do not extrapolate past this without re-verifying: this base only works for code whose real address falls under ~9MB. Real prologue density is ~900-1,235/MB from 1-9MB and essentially zero from 9MB on (confirmed: a ±200,000-byte window search around several 9MB+ targets found zero real function starts, every time it was tried). Code beyond 9MB may use a different base, may not be locally present in this file at all, or may need a fundamentally different technique to reach. This is why every 🔴 function above is real (a confirmed record exists) but not reachable.

Finding real function starts

Real GCC 2.96 PowerPC function prologues in this codebase always begin

stwu r1, -N(r1)      ; bytes: 94 21 <N:2 bytes>
mflr r0               ; bytes: 7c 08 02 a6 (byte-invariant)

swl_5238_reader.find_prologues() scans for this exact 8-byte shape at every 4-byte-aligned offset. Finds 7,757 real, low-false-positive candidates across the whole 24MB native region. Does not find every real function — simple leaf functions can validly skip this exact frame-setup shape.

A recurring pattern: no locally-visible blr — confirmed, via real CPU emulation, to be genuine non-code data

Confirmed on 9 separate functions now (getNodeVidArmMP0, getIndexOfVid, filterToggleSeg, addSegmentData, TransformHeadingAngleRelative, UpdateSettingsDueToDVDChange, SetRouteObject, mv_lefthand_traffic, getIso2CountryCode — notably, this last one is a clean, boundary-unambiguous "gold standard" function, confirming the anomaly is NOT an artifact of uncertain boundaries elsewhere on this list) — this is a real, pervasive architectural pattern across this codebase, not per-function noise or specific to one subsystem (it shows up in both the original route-path functions and the newly-found ADAS family equally). In each case, the function pops its own stack frame (addi r1,r1,N) but the word(s) immediately after do NOT decode as blr or any other valid PowerPC instruction (the exact word count varies per instance, 2 or 3 words seen so far). For getNodeVidArmMP0 specifically a real backward loop branch explained one occurrence of this shape; for the others, no such explanation has been found, and on addSegmentData/UpdateSettingsDueToDVDChange specifically, the code that resumes afterward shows no fresh stwu/mflr prologue either — a real, distinct variant, not previously seen on the first few instances.

UPDATE, resolved (the "is it code or data" half of the question): built a real execution harness using Unicorn Engine (PPC32, big-endian) — not just reading the bytes, actually running them on an emulated CPU core. Loaded UpdateSettingsDueToDVDChange's real bytes at their real VA, set arguments so the one external call is skipped (keeping the test fully self-contained, no other code needed), and single-stepped from entry. Confirmed, unambiguously: every real instruction up to and including the frame-pop (addi r1,r1,0x10) executes correctly; the very next word (00130019, exactly the first word capstone already flagged as undecodable) causes a genuine CPU exception the instant the processor tries to execute it — the same illegal-instruction trap real hardware would raise. This rules out "disassembler gap" definitively — Unicorn/QEMU's PPC decoder is a mature, independent implementation from capstone's, and it agrees the bytes aren't valid code. The data really is data.

Still open: what the data actually encodes (the EH-descriptor-table guess remains a guess, not confirmed). Partially advanced: why some functions show a fresh prologue right after it while addSegmentData/ UpdateSettingsDueToDVDChange don't. Re-ran the emulation with the ENTIRE 24MB native region mapped from the real file (emu_fli_wide.py, same scratchpad) instead of a narrow window, then manually skipped the 8-byte gap and resumed: 5 further real, valid instructions execute cleanly before the trail goes cold on an unmapped read — confirming genuine, well-formed machine code really does resume right after the gap, it just isn't a fresh function prologue (no stwu — it writes into stack offsets BEYOND the small frame the preceding function reserved, i.e. it reuses the just-popped caller frame rather than allocating its own). Reads as a shared trampoline/stub reached by multiple small functions' tails rather than each one's own independent continuation — plausibly where "what the data encodes" and "why no fresh prologue" turn out to be the same answer (the data might be exactly what tells this stub where to dispatch to, keyed by which function fell into it). Not confirmed further; the specific addresses this test computed depend on synthetic register state and aren't trustworthy without the function's real caller context. Given the harness now exists and works, this is a concrete, re-runnable next step rather than a dead end — see emu_fli_func.py/emu_fli_wide.py in this session's scratchpad (not yet promoted into research/).

UPDATE, a bigger reframing — tested on filterToggleSeg, changes how this whole section should be read. filterToggleSeg was previously documented as a clean, complete, standalone 108-byte leaf function ending at its own blob. Emulating past that blob (full 24MB mapped, realistic register values — not synthetic garbage) shows 13 more real instructions execute cleanly, and the operation is independently verified correct: a test value placed in a synthetic source record is correctly copied through to the destination record by the code that "shouldn't" be reachable. Continuing further: a real call to getOffroadDistanceFromRouteToDestination (already known from 2 other functions), a genuine array-copy loop, a mr r3,r31 ("return this") idiom, and what looks like a clean, correctly-matching epilogue — which is ITSELF immediately followed by yet another blob and still more real code (CfcAspectGuidanceStatusChangedEvent construction, also already known from 2 other functions). At least 3 separate blobs, each followed by real, meaningful, on-topic logic, all chained together.

This changes the right interpretation of the whole anomaly. It is very unlikely that 8 unrelated functions all independently "forgot" their own return instruction. Far more likely: this compiler/codebase lays out groups of logically-related functions (or a function plus its exception/call-site metadata) as one contiguous block, where real control flow branches AROUND each inline data blob via jumps this project hasn't mapped yet — and this project's function-at-a-time, linear-disassembly approach has been slicing that contiguous block at each blob and mistaking the pieces for separate, independently-broken functions. Every individual finding on every affected page (the real call targets, the real logic) remains correct and stands — what's revised is the FRAME: these are very likely fragments of a few larger real structures, not N independently-anomalous small functions. Fully mapping this would mean finding the real branch instructions that skip each blob in normal (non-exceptional) execution — a concrete, scoped research question for a future session, now that the execution harness to test hypotheses against exists and works.

UPDATE, immediately following, same session: that specific hypothesis (a direct static branch jumps around each blob) is REFUTED. Scanned the entire 24MB native region for any real b/bc- family branch (direct or conditional, both relative and absolute forms decoded) whose own encoded target lands exactly on the address right after 5 different known blobs. Result: zero real branches target 4 of the 5; the one addressed that did get a hit (0x606f3c, after filterToggleSeg's 3rd blob) turned out to be a branch already known and accounted for as part of that function's own ordinary conditional logic (its beq skipping the "then" half of the record-copy) — not a dedicated "jump around the blob" instruction, just a coincidence of where that already-understood branch happens to land. This rules out the simplest explanation. Whatever actually reaches these post-blob addresses in real, intended execution, it is not a plain direct or conditional branch anywhere in the 24MB region — which leaves either an INDIRECT/computed branch (bctr/bclr off a register or jump table, invisible to this kind of static address scan since the real target is only known at runtime) or something this project hasn't considered yet. Narrows the search, doesn't close it.

UPDATE, same session: also checked whether these post-blob addresses are independently-called real function entry points (the same raw- data/vtable scan used elsewhere on this wiki, plus the direct lis/addi scan) — zero hits, for all of them, anywhere in the whole 85.6MB file. Nothing calls them, nothing branches to them, and yet real, meaningful, independently-verified-correct code sits there.

Current best synthesis, converging 3 independent negative results with the project's own original hypothesis: these addresses are most likely real C++ exception-handling landing pads — real, compiler-generated cleanup/catch code that genuinely exists and genuinely works, but is reached only by the C++ runtime's own stack unwinder during an actual throw, which jumps there by READING the "data blob" as an EH range-table entry (PC-range → handler-address mapping), not by executing it or branching to it in the normal instruction stream. This would cleanly explain every piece of evidence gathered: no static branch or call targets it (the runtime computes the jump from table data, not from a branch instruction anywhere in this function's own bytes); it's genuinely unreachable via ordinary execution (consistent with "function appears to end here" being literally true for the NORMAL, non-throwing path); and it's still real, correct, meaningful compiled code (because it genuinely is real cleanup code, just invoked through an unusual, data-driven path this project's tooling doesn't model). Not proven — confirming it would need either the real EH table format fully decoded, or a from-scratch VxWorks/GCC unwinder reference to compare against — but it is now the best-supported explanation this project has had for this anomaly, strengthened rather than weakened by this session's attempts to find a simpler one.

UPDATE, new session: independently confirmed and quantified at whole- file scale using IDA Professional 9.3's real PowerPC analyzer, not just this project's own hand tracing. Loaded FHDD6.FLI's native 0-24,000,000-byte region into IDA as raw 32-bit big-endian PowerPC at the project's own established load base (0xF688DCF4), ran full autoanalysis, then scripted (IDAPython) a whole-region scan for every word-aligned real stwu r1,-N(r1) function-prologue byte pattern (the same signal IDA's own Patfind prologue matcher uses) and asked IDA to bind each one into a complete function via add_func:

  • 8,015 real prologue candidates found across the 24MB region
  • Only 9.4% (754, combining already-recognized plus newly-bound) could be bound into a complete function
  • 90.6% (7,261) failed — IDA's own flow analysis could not find a function end for them

This is a professional, independently-implemented PowerPC disassembler and cross-reference engine hitting essentially the same wall as this project's manual work, at full-file scale rather than on a handful of examples. For a real compiled 24MB PowerPC binary, a true function density of ~9% (vs. the expected near-100%, since stwu r1,-N(r1) is about as unambiguous a real-function-start signal as PowerPC code produces) is itself strong evidence something systematic — not isolated disassembler confusion on a few hand-picked functions — is going on across the whole native region.

Manually single-stepped IDA's own instruction decoder forward from 15 of the failing prologues (not just checking the end result, but exactly where decoding stops). All 15 stopped at a word whose primary opcode field is 0 — the exact same "universally illegal on real Power ISA" signature independently confirmed via live CPU emulation earlier in this project (see above). 15-for-15 is a strong replication, via a completely different tool and method, of the original emulation finding.

A new structural detail turned up by looking at all 15 stop-words together: the low 16 bits of each undecodable word form a tight arithmetic progression — 0x0019, 0x0019, 0x0021, 0x0029, 0x0021, 0x0029, 0x0021, 0x0029, 0x0021, 0x0021, 0x0029, 0x0019, 0x0021, 0x0019, 0x0031 — every single one is 8n + 1 (25, 33, 41, 49...), across 15 completely unrelated call sites with no other connection to each other. Raw noise or ad-hoc embedded constants would not be expected to share this property. This is new, independent evidence favoring the "structured table" framing over "arbitrary data blob" — consistent with (though still not proof of) the EH range-table hypothesis above: a small, fixed-stride field (plausibly a type-index or table-slot number, scaled by a fixed per-entry byte size) sitting in the low half-word of each entry, with the high half-word carrying a per-entry value that varies freely (0x000c, 0x001a, 0x0094, 0x0009, 0x024f, 0x0008, 0x01f0, 0x0115, 0x002b, 0x001c, 0x0009, 0x0018, 0x0007, 0x0162, 0x02ca — no obvious shared pattern, consistent with a real per-entry payload rather than more index structure).

UPDATE: the "EH landing pad" hypothesis now has real documentary confirmation, not just inference. This firmware's Cfront-style name mangling (__vd__, Pv, Ui, RC9msgheader, etc.) points to Wind River's own compiler (historically branded "Diab", dcc) rather than GNU GCC — confirmed independently via the real VxWorks 6.2 Kernel Programmer's Guide (vxworks_kernel_programmers_guide_6.2.pdf, legitimately obtained, chapter 11 "C++ Development"), which documents the exact compiler invocation (dcc -tPPC604FH:vxworks61 ... -DTOOL_FAMILY=diab -DTOOL=diab -c hello.cpp) and states the Wind River Compiler's C++ front end is the Edison Design Group (EDG) front end, a different lineage from both classic Cfront and GCC. Section 11.5.2 "Exception Handling" of that same manual states, verbatim:

"The Wind River Compiler exception handling model is table driven and requires little run-time overhead if a given exception is not thrown. Exception handling does, however, involve a size increase."

This matches this project's own independently-derived hypothesis on every point: a genuinely table-driven mechanism (not inline branch logic), zero overhead on the non-throwing path (consistent with ordinary execution never reaching or branching to the blob at all), and a size cost attached to functions that use exceptions (consistent with the blob being real, compiler-emitted per-function data). This doesn't yet give us the exact byte-level table layout — that would need the Wind River/Diab Compiler User's Guide itself, or a real Diab install's runtime headers — but it is the first genuine, citable, vendor-authored confirmation that this project's reverse-engineered model of the anomaly (table-driven EH metadata, not broken code) is the right shape, not just the best of several guesses.

UPDATE: the anomaly is now definitively, completely explained — not just supported — via the actual Wind River Compiler User's Guide (wr_compiler_users_guide_mips_5.4.pdf, legitimately obtained and archived in this wiki's own repo for reference; the MIPS-target edition, but this mechanism is described as a general compiler/linker feature, not an architecture-specific appendix, so it applies identically to the PowerPC edition). Section 23.8 ".frame_info sections" states, verbatim:

"The compiler generates .frame_info sections for C++ programs when exception-handling is enabled. A section is created for any function that might appear on the call stack between a try and a throw; the linker concatenates these into a searchable table that is used for stack-unwinding and object clean-up after an exception occurs. For each function, the table contains a small (8- to 24-byte) record that includes pointers to structures in the .data section."

And critically, the manual's own example linker script (sample.dld, 25. Linker Command Language) shows exactly where these records get physically linked:

.text (TEXT) :{
   *(.text) *(.rodata) *(.rdata)
   *(.frame_info) *(.j_class_table)
   *(.init) *(.fini)

.frame_info is placed inside the same .text output section as real code, not segregated into a separate data region — and sorting is OFF by default (-Xsort-frame-info-off), so each function's own record is linked in right where the linker encountered it: immediately after that function's own compiled code. This is a complete, compiler-documented explanation for every single thing this project measured by hand and by emulation: a small (matches our observed multi-word blobs), non-code (matches real CPU-emulation finding primary-opcode-0 illegal words), per-function (matches "appears right after THIS function's own stack-pop, not some other function's"), table-structured (matches the 8n+1 arithmetic-progression field found via IDA) record, sitting physically interleaved with code (matches "no locally-visible blr" — there was never a missing blr; the function genuinely ends there, and what follows is linker-placed metadata, not more of that function's own code) because the linker's own default script puts it there on purpose.

One more real-world detail this explains: -Xframe-info force-generates .frame_info even for plain C functions, specifically "for mixed C/C++ programs in which C++ exceptions may propagate back through C functions." Given this firmware's measured ~90% prevalence of the anomaly across essentially all real function prologues (see the IDA whole-file scan above) — far more than "only functions between a try and a throw" would suggest — it's a reasonable inference that this build used -Xframe-info project-wide, consistent with a large, C-and-C++-mixed embedded navigation codebase needing exceptions to propagate safely through older C-style modules.

This closes the central open question of this entire project. What remains open is only the fine-grained internal layout of the record itself (which fields are which within the 8-24 bytes) — a smaller, well-scoped follow-up, not a fundamental unknown anymore.

Attempted, a later session: 2 leading hypotheses for the record's "word2" field (the small, 8-1576-ish value following the 8n+1-coded first word) — both refuted at scale. Scanned every function in the whole-file-recovered database (8,164 checked, 7,169 containing at least one internal blob) and directly compared each blob's word2 against (a) the containing function's own total byte size, and (b) the blob's own byte offset from the function's start — the two most natural candidates for a small per-function integer in an EH record. Result: word2 matches the function's size in only 4 of 7,169 cases, and the offset-from-start in only 61 of 7,169 — both consistent with coincidental small-integer collisions (plenty of functions and offsets are small numbers already), not a real systematic relationship. No simple ratio or other relationship was visible by eye across the raw samples either. Both hypotheses are refuted, not just unconfirmed — recorded so a future session doesn't re-attempt the same two ideas expecting a different result. Word2's real meaning is more likely a small count/index into some other structure (a catch-type count, an action-table slot, etc.) than anything directly computable from this function's own size or layout. Closing this further would most likely need either the real Diab/Wind River compiler's own runtime headers (not obtained this project — see the compiler-identification work elsewhere on this page) or a fundamentally different analytical angle, not another guess at the same two candidates.

Turning the .frame_info discovery into whole-file function recovery (9.4% → 98.9%)

Knowing exactly what the blobs are (and that the linker interleaves them directly into .text) made it possible to go from "explain the anomaly" to "recover real function boundaries across the whole 24MB region" — not just the ~90 functions this wiki had hand-traced one at a time. Done entirely inside IDA Professional 9.3 (loaded per the setup documented elsewhere on this wiki: PowerPC big-endian 32-bit, generic ppc device, load base 0xF688DCF4, 24,000,000-byte native region), scripted with IDAPython.

Baseline measurement. A whole-region scan for every word-aligned real stwu r1,-N(r1) function-prologue byte pattern found 8,015 real prologue candidates. IDA's own autoanalysis + add_func could only bind 754 of them (9.4%) into complete functions — the rest failed because flow analysis walked straight into a .frame_info blob and gave up, exactly as this page documents above.

Recovery approach, validated as a read-only dry run before any mutation: for each unbound candidate, decode forward from its prologue; on hitting an undecodable (primary-opcode-0) word, treat it as the start of a .frame_info-shaped blob, and search forward (word-aligned) for the point where real code resumes — but strictly bounded by the address of the next already-known real prologue, so the search can never overshoot into a different, already-correct function. This dry run found a validated, in-bounds resume point for 99.9% of all candidates, confirming the approach was sound before touching the live database.

First attempt, 9.4% → 9.7%: barely moved. Patching the first blob per candidate and calling add_func(ea, BADADDR) (asking IDA to auto-detect the end via its own control-flow trace) still failed for nearly everyone. Root cause, found by iterating: a single function can contain many .frame_info blobs, one per call site that might throw — not just one trailing blob. Patching only the first blob left later ones still blocking flow analysis deeper in the same function. Looping blob-detection-and-patch within each candidate (up to 25 blobs, still bounded by the next real prologue) got every byte in range decoding cleanly — but add_func(ea, BADADDR) still refused almost all of them (recovered: 0 new). ida_funcs.find_func_bounds confirmed the refusal (FIND_FUNC_UNDEF) without more detail, but the real cause is BADADDR-mode add_func demands IDA's own control-flow tracer prove the function's extent (follow every branch, resolve any computed jump/bctr, etc.) — a stricter bar than "every byte decodes."

The actual fix: stop asking IDA to infer the end; give it the end we already validated. Switching to add_func(ea, upper_bound) — passing the next known real prologue as an explicit end address, rather than BADADDR — bypasses IDA's conservative flow-closure requirement entirely and just takes our own already-validated boundary. Result: 7,149 additional functions recovered, taking the whole-file bind rate from 9.4% to 98.9% (7,923 of 8,015 candidates), with only 92 unresolved stragglers left for manual follow-up.

Why this matters beyond a bigger number: real IDA function objects across virtually the whole 24MB region means Hex-Rays decompilation is now available broadly — not just for the small set of functions this project traced by hand over many sessions. This changes the project's realistic next phase from "trace one function at a time" to "decompile and read," a fundamentally faster way to cover the rest of this firmware. (Caveat carried over from elsewhere on this page: IDA's auto-assigned sub_XXXXXXXX names carry none of this project's already-confirmed real names — reconciling the two is a separate, future task.)

Found while tracing mv_check_turn: the "scan backward for the nearest real prologue" technique (used to find true function starts once the naive symbol-table address lands mid-body) finds a nearby prologue, but not necessarily the RIGHT one — it doesn't check whether the function's own control flow (backward loop branches especially) actually stays within that boundary. Confirmed concretely: mv_check_turn's disassembly reaches a real blr, but its own loop branches backward past its assumed start, landing before an EARLIER prologue (~900 bytes further back) than the one the naive scan found. This means at least one page on this wiki (mv_check_turn) may document a MIDDLE FRAGMENT of a larger enclosing function, not that symbol's own complete, isolated body — flagged honestly on that page rather than silently treated as complete. Worth checking for on any future page built this same way.

UPDATE, resolved: disassembling from that earlier candidate start confirms it IS mv_check_turn's own real, complete body — a genuine undershoot, now fixed. See mv_check_turn for the real preamble this recovers (a MDEDMDatabase SQL query plus oslibVfs_register, feeding the already-captured switch statement).

A related but DIFFERENT trap, found on mv_generate_mnvr_name: a function using a callee-saved register (r30/r31) before any visible assignment is NOT automatically evidence of a boundary undershoot. Checked directly by disassembling the immediately preceding real function: it ends cleanly and independently, immediately followed by this function's own fresh, genuine prologue — ruling out undershoot here. The real explanation: GCC can pass extra arguments to a static/ internal-linkage helper through callee-saved registers instead of the standard r3-r10 parameter registers, since no external ABI constrains it — the callee still faithfully saves/restores the original value for the caller's benefit, it just also reads the incoming value as a real input first. Net lesson: before concluding a "used before set" register implies wrong boundaries, always check whether the immediately preceding function ends cleanly right before the prologue in question — if it does, the usage is far more likely an extra hidden argument than evidence of undershoot.

First real Hex-Rays decompilation results — a confirmation and a correction

With the whole-file recovery above giving IDA real function objects covering 98.9% of real prologues, Hex-Rays decompilation (PowerPC decompiler, hexppc.dll) is now usable broadly rather than theoretically. Ran it on five already-documented, hand-traced functions/addresses to directly compare against this wiki's own prior work: filterToggleSeg, mv_convert_slot_nr, registerBlocking, and the two EHTreeNode-struct-layout addresses (the "constructor" and the "accessor cluster start").

New confirming evidence that functions in this codebase genuinely escape our forced boundaries. Every single decompiled function contains at least one JUMPOUT(0x...) marker — Hex-Rays' own explicit signal that real control flow branches to an address it could not incorporate into the function (because it lies outside the bounds we gave add_func). For mv_convert_slot_nr, one such target (0xF701F774) is provably past the end address we forced (0xf701f6ec) — not a decompiler quirk, but real, live confirmation (independent of, and agreeing with, the earlier CPU-emulation/static- scan work) that this codebase's functions do not always end where the next textual prologue starts. This is the same "shared trampoline" phenomenon already documented on UpdateSettingsDueToDVDChange, now independently reproduced by Hex-Rays across multiple unrelated functions rather than being a one-off. Net effect: treat every add_func(ea, next_prologue)-bounded function from the whole-file recovery pass as a close, usable approximation for decompilation, not a proven-exact boundary — a JUMPOUT in the output is the decompiler telling you exactly where that approximation broke down for this particular function.

A real correction to the EHTreeNode struct-layout page. The "constructor" address this wiki identified at 0xf6ffc760 is not itself a function entry point — Hex-Rays reports its real containing function spans 0xf6ffc500-0xf6ffc7c4. Likewise the "accessor cluster start" at 0xf6ffc934 belongs to a real function spanning 0xf6ffc7c4-0xf6ffca5c. Both addresses remain real, reachable, correctly-identified code (confirmed via real callers elsewhere) — what was wrong was describing them as independent function entries, rather than call-reachable points inside one larger compiled function. This actually fits, and slightly sharpens, the struct-layout page's own "~20 tiny leaf accessors back-to-back" description: they are back-to- back because the compiler emitted them as one single function, not because the compiler happened to place ~20 separate zero-frame functions adjacent to each other.

Two new, concrete content findings (independent of the boundary question above):

  • filterToggleSeg's real logic is a virtual function call: (*(vtable + 28))(a1 + *(int16*)(vtable + 24)), i.e. a genuine C++ vtable dispatch at a specific, now-known offset — more precise than this wiki's earlier "record-copy operation" framing, and worth folding into that page.
  • registerBlocking uses the classic int-to-double bit-trick conversion idiom: XOR a raw 32-bit value with 0x80000000, pair it with the magic high word 0x43300000 (2^52 in double representation), and subtract 2^52 as a double — the standard software technique for converting an unsigned integer to a double without a hardware int→float instruction. Consistent with the e300 core's real floating-point capabilities, and plausibly how this function turns raw fixed-point position/coordinate data into a real double before the sqlite3-adjacent work already documented on that page.

Standing caveat carried over from setup: this IDA database treats the whole 24MB region as one read-only "ROM" segment, so Hex-Rays inlines every global's ROM-image value as a constant — including ones that are actually mutable at runtime (copied to RAM at startup, per standard embedded .data initialization). A decompiled "constant" global should be read as "this was its value in the flashed image," not necessarily "this value never changes" — relevant context for any future reading of Hex-Rays output from this database, not a defect to fix.

Hex-Rays at scale: 20 more functions decompiled — the JUMPOUT pattern is universal, plus 3 real new findings

Extended the single-function pilot above to 20 more already-documented addresses in one pass. Results:

The JUMPOUT pattern is not occasional — it is universal. All 19 addresses that successfully decompiled (one, mv_check_turn, still isn't bound to any function at all — a follow-up item) show at least one JUMPOUT marker. Several (CfcGuidanceSpeedTableFuture's constructor-or-operator new, Draw, mv_lefthand_traffic, setRouteSectionDetourId) decompile to almost nothing BUT an immediate JUMPOUT — meaning the forced next-known-prologue boundary cut off nearly the entire real function body, not just a trailing portion. This is now the clearest evidence yet that the "shared trampoline"/frame-reuse phenomenon first documented on UpdateSettingsDueToDVDChange is pervasive across this codebase, not an isolated case.

Two more boundary corrections (same pattern as EHTreeNode, now applied to their own pages): Draw/ElectronicHorizon::Draw and getCurrentCarPosition are both real, reachable code, but NOT independent function entries — their real containing functions start earlier (0xf70abaf4 and 0xf702468c respectively). (One extraction false-positive avoided: mv_generate_mnvr_name already correctly documented its own true start — no correction needed there.)

A real, recurring call pattern — UPDATE: checked, and it's not a diagnostic logger, it's almost certainly real EH/throw runtime machinery. The same call shape, unk_F6F24CE8(id, &unk_F7886E04, number, ...), appears in both mv_convert_slot_nr and SetRouteObject, with the exact same second-argument pointer (&unk_F7886E04) at both call sites. The original hypothesis here was a __FILE__-style diagnostic/assert macro. Checked directly by reading the real bytes at unk_F7886E04, and that hypothesis is wrong — it's not a string at all. The raw bytes there (002c0099 00000008, 002c0059 00000008, 002c0019 00000010, 002c0031 00000008, ...) are unmistakably MORE .frame_info-shaped records — the same 8n+1 low-halfword pattern plus a small size field, identical in shape to the per-function EH records documented earlier on this page. This is very likely a direct pointer into the real, concatenated, searchable .frame_info table itself (per the compiler manual's own description — "the linker concatenates these into a searchable table"), not a filename. Being the exact same address across two unrelated functions fits this reading well: a single, stable, shared table/table-entry reference, not a per-function string.

This reframed unk_F6F24CE8 as likely EH/throw runtime machinery rather than a logger. UPDATE, same session, immediately following: that reframing was too hasty — checked 486 total callers (found via DataRefsTo, since every call site loads the address indirectly via lis/addi rather than a direct branch, which is why an earlier xref search using only CodeRefsTo found zero) and decompiled a fresh, unrelated sample of them. Several show unk_F6F24CE8 receiving real, legible C strings as arguments — including two more RTTI-shaped class-name fragments, confirming 2 more real, previously-uncatalogued classes: "...ZP17CfcTypeCoordinate" (17CfcTypeCoordinate → CfcTypeCoordinate) and "...erP8VpTurtle" (P8VpTurtle → VpTurtle, joining VpSimEventSetPosition as a second confirmed Vp-prefixed class — a real naming convention in this codebase, not a one-off). One call site (sub_F6C484C4) fires right after a null-check on a malloc-shaped call, with first argument 118 — the same 118 value also seen returned/passed in 2 of the other new samples, consistent with it being a specific error/category code (plausibly "allocation failure" or "null pointer").

RESOLVED: did the concrete next step (grouped all 486 callers by their first-argument ID, 344 successfully decompiled) — both pieces of evidence are explained by the SAME single mechanism, not two competing ones. Across every ID bucket, the overwhelming majority of second-argument values are non-string pointers (&unk_XXXXXXXX); real "strings" are a small minority, and critically, every single one of those "strings" is a garbled mid-record fragment, not a real message — e.g. "ngsationing" (plainly the tail of something like ...PositioningDiagnosisImpl) recurs byte-identically across 5 unrelated call sites, and "ilFixedArray1ZP17CfcTypeCoordinate" / "...P8VpTurtle" are mangled-name tails, not English sentences a programmer would write for a log message.

These are not diagnostic strings at all — they are the symbol/EH-descriptor table this wiki already extensively documented: a 26,541-record table where each record is [3-byte marker][NUL-terminated mangled C++ name][header fields][ordinal][5 trailing fields]. Hex-Rays' string auto-detector fires on whatever readable ASCII run it finds starting near the given address — which is almost never record-aligned, hence the "garbled fragment" appearance — while the .frame_info-shaped non-string pointers seen at most other call sites are simply addresses elsewhere within the same broader table structure (consistent with one compiler-generated table serving both EH range data and RTTI/name data, exactly as the Wind River compiler manual documents table-driven mechanisms for both).

Conclusion: unk_F6F24CE8(category_id, &descriptor_table_entry, number, ...) is almost certainly the real, single, compiled implementation of this codebase's C++ throw / exception-dispatch runtime entry point — its second argument is a pointer into this project's own already-documented descriptor table, used (plausibly) to identify the thrown type for RTTI-based catch matching. This also opens a genuinely valuable new methodology: these 486 call sites are real, compiler-emitted, direct references into the descriptor table — a potentially more reliable cross-reference source for validating/discovering table entries than the already-known-unreliable "position-4 resolution" heuristic this project has relied on elsewhere, since each one is a live, executing reference rather than a cold catalog lookup.

Tried this immediately, same session, using the project's own research/swl_5238_reader.py marker-scan logic directly against the real FHDD6.FLI bytes (not Hex-Rays' text output): built the full 26,320-record table (by byte range, not the 9MB-limited "field-4 code address" resolution this project uses elsewhere), then checked every distinct call-site address recovered from the Hex-Rays sample against it. Only a small sample was available to check this pass (69 distinct addresses — the earlier classification script only kept up to 3 examples per ID bucket, not the full 486), and only 3 landed inside a real record's byte range — but all 3 are solid, call-site-confirmed, previously-uncatalogued real names:

  • __dl__Q220MDCacheWeatherShapes19ProductInfoReceiverPv — a real operator delete for a brand-new class, MDCacheWeatherShapes:: ProductInfoReceiver — a previously-undocumented weather-data- caching subsystem. Confirmed as the literal argument to the ID-118 throw-site in sub_F6C484C4, the same one that fires right after a failed malloc-shaped call — consistent with 118 meaning something like an allocation-failure category.
  • getMapRenderer2D__C16MapRendererProxy — confirms a new real class, MapRendererProxy, with a getMapRenderer2D() method.
  • __23CfcLocationManagerProxyiP23CfcMsgEndPointInterface — a constructor for a new real class, CfcLocationManagerProxy(int, CfcMsgEndPointInterface*), tying back to the already-documented LocationManagerImpl family.

This directly confirms the cross-reference mechanism works (argument 2 really does resolve to real table entries when checked against actual file bytes, not just Hex-Rays' guesses) and yields 3 solid new real names in the first, small, incomplete sample.

Done immediately, same session: re-ran keeping every call-site address (508 raw rows, 487 resolvable) instead of 3 examples per ID bucket. Yield: 12 hits, 8 distinct real names — a ~2.5% hit rate against the full table, but every hit is solid and several reveal entirely new real subsystems:

  • sqlite3_update_hook — a real SQLite C API function (update- notification callback registration), independently confirming real SQLite usage in this firmware (already suspected from sqlite3_version on registerBlocking).
  • createStatement__Q213SDARSVWDATASV26PredefinedStatementFactoryP22CfcConnectionInterfaceRC9CfcString — reveals a brand-new real subsystem, SDARSVWDATASV:: PredefinedStatementFactory. SDARS is the real automotive industry term for satellite digital audio radio (XM/Sirius) — this is VW's satellite-radio data service, previously entirely undocumented on this wiki.
  • _$_30CfcTypeRoadClassStatisticEntry — a destructor for a new class, sibling to the already-known CfcTypeRoadClassStatistic family (seen via getOffroadDistanceFromRouteToDestination on several pages).
  • getPosition_accuracy__36CfcTypeDeadReckoningDetailed... — a new class tied to dead-reckoning navigation (the real automotive term for GPS-loss position estimation via wheel-speed/heading sensors); trailing byte garbled by a parser edge case, real name is plausibly CfcTypeDeadReckoningDetailed.
  • AU_Preprocessing_StringCompare__FPCvT0 — a new real global function.
  • Plus 3 already-documented above, now further corroborated from additional independent call sites: MDCacheWeatherShapes:: ProductInfoReceiver, MapRendererProxy::getMapRenderer2D, and CfcLocationManagerProxy.

Net assessment: the cross-reference method is real and works, but at this hit rate (~2.5%) it's a low-density prospecting tool, not a bulk solution — most argument-2 addresses point to byte ranges this particular marker-scan doesn't recognize as a clean record (plausibly .frame_info-shaped sub-records, or table regions this parser's span < 20 or span > 4000 heuristic rejects). Still clearly worth running against the FULL set of throw-site references project-wide (not just these 508 from one function's callers) as a future session's project — likely still has many more genuine finds at this same quality level, just sparse.

A real, recurring vtable-dispatch idiom. The exact same compiled shape — (*(vtable + N))(this + *(int16*)(vtable + N-4)) — appears in both filterToggleSeg (N=28) and SetRouteObject (N=76). Seeing the identical pattern at two different vtable offsets, in two independently -traced functions, confirms this is a real, repeated compiler idiom for a specific kind of virtual call — plausibly a virtual accessor that returns a this-relative sub-object pointer — not a one-off coincidence in either page's original trace.

A new, independently-reliable way to find real class names: RTTI type-info strings. getIndexOfVid's decompilation surfaces a literal string reference, "15CfcMapViewProxy" — a classic length-prefixed C++ RTTI type-info name (15 = character count, CfcMapViewProxy = the real class name). This confirms a real, previously-uncatalogued class, CfcMapViewProxy, and more importantly points to a reliable new research method: this firmware was compiled with RTTI enabled (per the VxWorks Kernel Programmer's Guide, section 11.5.3, and confirmed locally since IDA's "Enable RTTI analysis" option is on by default) — scanning .rodata-like regions for this exact <decimal-length><name> byte pattern could surface many more real class names directly from the binary, independent of (and more reliable than) the already-documented-unreliable broader symbol-table catalog. A concrete, scoped idea for a future session, not yet attempted at scale.

getIso2CountryCode's real algorithm is now characterized — see the update on that page: real bounding-box/point-interpolation geometry (point-against-boundary classification), not a generic sorted-table lookup as the callee name alone had suggested.

A third methodological gap: a long scan can OVERSHOOT into a neighboring function

The mirror-image mistake to the undershoot above, confirmed twice while re-examining calculateManeuvers and getSegProperty: scanning a long way past a function's own body to collect lis/addi call targets can silently run through a real blr belonging to a DIFFERENT, textually- adjacent function and keep going, picking up that neighbor's (and even a further neighbor's) own call targets as if they belonged to the function being traced. A blr appearing is NOT sufficient evidence of "this is where my function ends" — the only reliable test is whether the epilogue immediately before it (addi r1,r1,N, or the equivalent frame-chain restore) pops EXACTLY the same frame size N the function's own prologue allocated. On both functions above, a blr was found whose preceding frame-pop size did NOT match, and a genuinely different function's own fresh prologue started right after — the real fix in both cases was scanning the suspect range specifically for ANY stwu at all (not just the expected one) to find exactly where the contamination began, then discarding everything past that point. Both pages were corrected after this check; apply it to any future long scan before trusting its output.

UPDATE, immediately following, same session: built and validated a real call-target cross-reference tool — confirms the "no discoverable caller" finding is systematic, and gives one new, real data point for mv_check_turn. Collected every lis/addi → mtlr → blrl computed-call chain across the full 24MB region (a first attempt had a real bug — too-tight a proximity threshold between mtlr and blrl — caught by testing it against getId's own already-known-real first call, which it wrongly missed; fixed and re-validated against that same known-good target before trusting any of its other output). Confirmed: none of the 13 functions traced/partially-traced on this wiki are themselves a target of any discoverable computed call anywhere in the 24MB region — extending, with a properly-validated tool this time, the same "caller not found" result each fully-traced function's own page already reported individually. For mv_check_turn specifically: one real, confirmed call target (0x587224) exists between the loop's backward- branch target and the originally-assumed start — not itself a function prologue, but genuinely, independently call-reachable code inside the same larger enclosing routine, consistent with a shared case/label reached via computed call from elsewhere (a real GCC pattern for inlined or duplicated code paths). Doesn't fully resolve the boundary, but is real, validated new structural evidence, not a guess.

The symbol/EH-descriptor table

A large, real, previously-undocumented table exists in the file, discovered by tracing a literal-pointer reference from a known function. Record format (empirically derived):

[3-byte-fixed/1-byte-variable marker: 00 10 05 <var>]
[NUL-terminated mangled C++ name]
[variable-length header fields, role not decoded]
[4-byte ordinal counter -- confirmed monotonically increasing across
 real consecutive records, e.g. 0x6fe6 -> 0x6fe7 -> 0x6fe8]
[5 trailing 4-byte fields, ending right before the next record's marker]

Field index 4 (the last of the 5) is the function's own real code address, for functions whose code lives under 9MB — confirmed at scale: of all fields across 26,323 parsed records, position 4 matches a known prologue 35 times vs. 3-6 for positions 0-3 (all far above the ~0.05-hit chance baseline for that sample size). A plain marker search already finds 26,541 occurrences; parse_symbol_table() (in research/swl_5238_reader.py) parses 26,243 named records, of which 8,041 resolve to a plausible in-range (<9MB) address and 35 additionally match a known-good strict prologue exactly. The naive field-4 address often lands a little INSIDE a real function rather than exactly at its start (several pages below show this) — the true start is found by scanning backward for the nearest real prologue.

import swl_5238_reader as swl
with open("FHDD6.FLI", "rb") as f:
    fli = f.read()
prologues = swl.find_prologues(fli[:24_000_000])
catalog = swl.parse_symbol_table(fli, require_prologue=False)      # 8,041 entries, <9MB
full_catalog = swl.parse_symbol_table(fli, max_code_offset=24_000_000)  # includes 9MB+ (unreachable) entries too

Important, evidenced caveat — a function's own string references are NOT its identity. Cross-checking strict symbol-table entries against a separate catalog (functions whose own code computes the address of some readable string) found 4 overlaps, and for every one, the symbol table's real name had nothing to do with the string the function merely referenced (getId, below, is the clearest example — it was earlier mis-called "the findSegIndex-referencing function"). Only the symbol table's own name field is authoritative; "references string X" is not evidence of identity.

Further confirmed, 2-for-2, this session: picked 2 entries from the broader (non-"gold") catalog whose field-4 address happened to land exactly on a real prologue (errorString__9CfcRegExp, parseAtom__15CfcRegExpEngine...) and disassembled both — neither one's real content has anything to do with regular expressions (one is a CfcMdlAspModelObjectChangedMessage-building event handler, the other touches CfcCtrlDestroyCompleteFuture/CfcTypeRoadwayBlockingStatus). Both silently discarded rather than published under the wrong name. Practical lesson: landing on a real prologue is NOT sufficient evidence the resolved address is that symbol's own — always check that the disassembled content is at least thematically plausible for the claimed name before trusting a position-4 resolution outside the already-validated 35-entry "gold" set. A much more reliable source of new targets going forward: addresses confirmed via a real, already-disassembled caller's own lis/addi reference (as used for getCurrentCarPosition and registerBlocking), not a cold symbol-table lookup.

A 3rd bad resolution, same session, via the SAME broader catalog: createLocation__19LocationManagerImplP17CfcTypeCoordinate's resolved address landed on pure readable string/debug data (literal ASCII like "GetD...", "N:/s..." misdecoded as instructions with suspicious immediate values), not code at all — discarded, not published.

4th and 5th bad resolutions, caught a different way — not from a cold catalog lookup this time, but from 2 names that were already published on registerBlocking's own page (cleanupMemory__9MapLoader, __vn__11MapJobQueueUi). Their call ADDRESSES were real (confirmed relocations in already-verified code), but once Draw's own byte range was independently established, both addresses turned out to land mid-body of Draw's own tag-scanning logic — i.e. registerBlocking really does call those 2 addresses, but the NAMES attached to them (from the same broader catalog) were wrong; both were really just registerBlocking jumping into Draw's shared code a 2nd and 3rd time, not 2 separate classes. Corrected on that page rather than left standing.

This project now has 5 confirmed-bad name resolutions from the broader catalog this session, against 2 confirmed-good targets from real caller references (getCurrentCarPosition, Draw) — a stark, now well-evidenced contrast favoring caller-reference-based targets, and a concrete reminder to re-check EVERY call-target name against any newly-established byte ranges, not just trust it once at the moment it's first resolved.

UPDATE, caught and corrected within the same session: an earlier version of this note claimed the 0x81d000-0x820000 neighborhood (33 broader-catalog names in ~36KB) was a specific, unusually dense "low-trust zone." Checked properly against the catalog's own overall density and that claim doesn't hold up: the 8,041-entry broader catalog spans only 0x56ffd8-0x895130 (3.3MB) — an average spacing of just ~410 bytes between names, meaning a RANDOM 36KB window should contain ~88 names on average. 33 in 36KB is, if anything, BELOW average density, not an outlier. There is no evidence this specific neighborhood is worse than anywhere else in the broader catalog — the correct lesson is simply the one already established: the entire broader catalog is this unreliable everywhere, confirmed now 6 times this session (5 bad resolutions plus this density check itself turning out not to isolate anything special). No specific sub-region should be treated as worse (or better) than any other outside the strictly-validated ~35-90 "gold" entries; verify content independently every time, everywhere, full stop. Left this correction in place of the original claim rather than deleting the mistake silently — a real example of exactly the kind of overclaim this project tries to catch.

Final tally, this session, for the "resolve a name from the broader catalog, then find its nearest preceding prologue" method specifically (as opposed to a name confirmed via a real caller reference): attempted on errorString__9CfcRegExp, parseAtom__15CfcRegExpEngine..., createLocation__19LocationManagerImplP17CfcTypeCoordinate, cleanupMemory__9MapLoader, __vn__11MapJobQueueUi, SetDispatcherState__20Adas_Most_DispatcherUc, oslibVfs_register__Fi (as a standalone target, not as evidence inside mv_check_turn's own already-verified body), and CallbackFunction__13CArr_AdasMainP12DispMqStruct — 8 attempts, 8 content mismatches, 0 confirmed hits. Every single one, once disassembled, turned out thematically unrelated to its claimed name (string/token comparison code claiming to be a VFS register call, EDB stack-storage and route-tree math claiming to be a MOST-bus callback, etc.). Conclusion: this specific method should be considered effectively unusable going forward without independent content verification making the name itself irrelevant to trust — at that point the "name" is just a label on whatever was actually found by reading the code, not a confirmed identification. The handful of earlier pages this session built the same way (DumpNavGraph, TransformHeadingAngleRelative, UpdateSettingsDueToDVDChange, SetRouteObject) survive only because their own content was separately, individually argued to match their claimed names at the time — not because the method that found their addresses was reliable. A future session should re-examine even those with this harsher, now better-calibrated standard before leaning on them further.

UPDATE, a later session, tally now 11-for-11 and a real silver lining found: re-checking mv_convert_slot_nr's own previously-published call targets (not a fresh cold lookup — these were ALREADY on the wiki) turned up 3 more confirmed-wrong names: IsValid__10EHTreeNodel, writeProtocolHeader__19CfcNetMemoryChannel, and close__21CfcIoFileOutputStream. The real content at the IsValid/ writeProtocolHeader address, though, turned out to be something genuinely valuable: a complete, unambiguous cluster of ~20 tiny getter/setter leaf functions plus one object-comparison function, together confirming a real, high-confidence in-memory struct layout — see the dedicated page. A correction cascaded to every other page that had cited these same addresses as confirmed: SetRouteObject (same 2 names, same address, same fix) and 0x78834c (whose own CfcNetMemoryChannel claim leaned on mv_convert_slot_nr's now-retracted one — spot-checked independently too, also not confirmed). Practical lesson, sharpened further: a wrong name can still mark a real, valuable discovery — the struct-layout page exists only because the "IsValid" call address was followed up on despite the name being wrong. Don't just discard a bad-name call target; read what's actually there before writing it off.

The "gold standard" catalog: real name + confirmed real caller

The single most valuable capability to come out of this page: cross- referencing the symbol table (real names) against the validated call-target set (real, confirmed callers) directly. 960 functions across the whole 24MB region have BOTH — a real compiler-assigned name AND at least one confirmed real call site referencing them, the strongest evidence this project can currently produce for any given function's identity and liveness. 455 of those 960 are in the reachable <9MB range.

gold = [(name, off) for name, off in catalog.items() if off in call_targets]
reachable_gold = [(n, o) for n, o in gold if o < 9_000_000]

Real, directly relevant examples from this set (all reachable, all doubly-evidenced): IsValid__15EHDatagramGraph, GetFirstSegment__ 10EHTreeNode, Advance__10EHTreeNodeP10EHTreeNodeRP10EHTreeNode, findStageId__C8MapRouteiPi, initAllocMemoryRCResult__9RoutePath P8RCResult, getInstance__14SegmentBlockerPP14SegmentBlockerP13RBP_ JR_Client. Caveat, confirmed concretely: being in this "gold" set does NOT mean the naive symbol-table address is a clean, unambiguous function start — most still land mid-body the same way already documented elsewhere on this page (GetFirstSegment itself: naive address 0x85400c isn't the scanned-backward prologue at 0x853d88, and isn't itself a clean instruction stream either — tried and not pursued further for that reason). Only 4 of the 455 reachable gold functions are ALSO an exact, clean strict-prologue match with no boundary ambiguity at all: getIso2CountryCode (now traced — confirmed bounded, 1,332 bytes, ends in the 9th instance of the central blr anomaly), CfcGuidanceSpeedTableFuture's own operator new (corrected from an earlier claim this 4th slot was a destructor — re-checked directly this session: the real destructor isn't actually a clean match, operator new for the same class is; see its page), setRouteSectionDetourId (partially traced — real early logic confirmed, but no matching epilogue found within 768 bytes, so not fully bounded), and prepareOutputForMap (the 5th fully-traced function on this wiki, added from this list). All 4 now have some real documentation — this specific, highest-confidence list is fully worked through.

Triaged every other directly-relevant reachable gold name (IsValid__15EHDatagramGraph, GetFirstSegment__10EHTreeNode, Advance__10EHTreeNode..., findStageId__C8MapRoute, initAllocMemoryRCResult__9RoutePath, and 10 more graph/tree/route names) — none are boundary-clean. Every one lands mid-body of a larger enclosing routine the same way already documented, confirming this is a pervasive limitation of the current toolset, not something fixable one function at a time. A direct search of the full 455-entry reachable gold set for Vid/VNode/Arm/vseg found zero matches — that terminology genuinely only exists in the confirmed- unreachable 9MB+ region, consistent with everything already established.

Tested and refuted a hypothesis for fixing the boundary problem systematically: the symbol table's other 4 trailing fields (0-3, never used before — only field 4 was) might encode the function's real start/end range or size. Checked directly against the 2 functions with a fully-known exact start AND end (getId, prepareOutputForMap): fields 0-3 do NOT bracket the function — they consistently point to a completely different, shared location (~23.4-23.7MB, near the very end of the 24MB region) for BOTH functions, not anywhere near the function itself. Refuted, not just unconfirmed. The tight clustering is itself a real, separate observation (most likely a shared RTTI/type-info or personality-routine table referenced by many functions' EH records) — a possible lead for future RTTI-focused work, but not for the boundary problem. No obvious size field was found in the record's other header bytes either.

Honest overall assessment: with the current toolset (load base, prologue finder, symbol table, call-target cross-reference, gold- catalog intersection), the boundary-ambiguity problem for functions whose naive/confirmed address lands mid-body appears to be a genuine, structural limitation, not a solvable per-function puzzle. Resolving it systematically would need either new ground truth (a real symbol map, hardware access) or a substantially larger tooling investment (a real control-flow-graph reconstruction pass, not a quick fix) — recorded here so a future session doesn't re-attempt the same already-tried ideas expecting a different result.

UPDATE, a later session: that "substantially larger tooling investment" now exists (the whole-file 98.9% function recovery above), and was used to re-test this at full scale with real ground truth instead of heuristics — with a self-correction along the way, recorded honestly rather than silently fixed.

First attempt, checked immediately after the whole-file recovery pass: only 39 of 8,041 broad-catalog entries landed inside any real function, 8,002 landed inside none at all — seemingly a clean, strong confirmation that the broad catalog's field-4 addresses mostly don't correspond to real code at all, not merely imprecise ones. That number was wrong, caught by re-checking with a fresh, isolated, read-only script: the real, reproducible count is 7,851 of 8,041 (97.6%) DO fall inside a real recognized function — only 190 don't, and of those 7,851, nearly all (7,815) land inside but not exactly at the function's start (only 36 are an exact start match), consistent with this page's own original, long-standing characterization of the problem ("naive symbol-table address often lands mid-body"), not a refutation of it. The most likely explanation for the earlier wrong reading: real function coverage kept growing across this session's many subsequent script runs after the initial 98.9% measurement (each script's own auto_wait() lets IDA's background analysis queue continue converging), so the first check simply ran before coverage had caught up to where it is now — not a sign the method itself was unsound.

Practical result, corrected: applied real names to IDA across several honestly-labeled confidence tiers — 17 wiki-hand-verified + 28 gold-standard (45 total, clean names, no prefix); 202 more via a rebuilt "real name + confirmed real call target" cross-check (lis/addi forming an address actually used in a mtlr+blrl or mtctr+bctrl call, not just a self-described address — marked callconfirmed_); and 1,498 more from the broad catalog itself, attached via containment to whatever real function the address falls inside (marked unverified_, since a containment hit still isn't the same as individually content-verified — this wiki has shown, more than once, that a reloc-confirmed ADDRESS can still carry the WRONG name).

UPDATE, a later session: the callconfirmed_ tier is weaker than its name suggests — downgrade it mentally to the same trust level as unverified_. Deep-tracing GetFirstSegment__10EHTreeNode and Advance__10EHTreeNodeP10EHTreeNodeRP10EHTreeNode — both genuinely in the validated call-target set, not the raw broad catalog — found both resolve to completely unrelated real functions (setErrorCode__23PredefinedStatementImpl... and updateWeatherLocation__Q213SDARSVWDATASV... respectively), the same wrong-name pattern already documented many times elsewhere on this page for the UNVALIDATED catalog. Only a 3rd validated name checked the same way, SetSlope__15EHDatagramGraph, showed plausible content. Proving some real caller references an address is not the same as proving the catalog's name for that address is correct — both are real, independent facts, but they don't imply each other, and this session's own callconfirmed_ IDA labels should be read with exactly the same skepticism as unverified_ ones until individually checked, not treated as meaningfully more trustworthy just because the tier name sounds stronger.

Follow-up, same session: found GetFirstSegment's real callers directly (5 distinct ones) to see if the address's actual BEHAVIOR, independent of any catalog name, supports the "EHTreeNode" theme. It does, modestly: all 5 call it the identical way — (&byte_F70E1C70[144])(), zero arguments, return value immediately used as a context/handle for a following call — a consistent, independently-corroborated behavioral signature across unrelated functions. One caller's real logic (itself mislabeled, same unreliability) shows a classic spatial-grid pattern: walk a linked list (v6 = *v6), check each node's bounding coordinates against a query range, store matches into a grid-indexed lookup array — thematically consistent with tree/segment data, though not proof of the specific name. Advance__10EHTreeNode...'s target address had zero callers found by this same method — inconclusive (the scan has known blind spots, e.g. r2/r13-relative addressing), not evidence it doesn't exist. Net for this specific sub-thread: the real EHTreeNode::GetFirstSegment/Advance tree-traversal algorithm remains unrecovered — getting it would need the same slow, careful, per-function manual verification this wiki's ~20 fully-traced pages already use, not a catalog shortcut.

Follow-up, tried a 2nd class the same way, same session: RouteCalculation's core search-algorithm methods (calculateRoute, successors, penalty_cost, prune — the real shape of a graph-search routing engine: cost function, successor generation, search-space pruning). All 4 field-4 addresses land inside forced-boundary pseudo-functions, 2 of them (penalty_cost, prune) inside the exact same 6.77MB desert already characterized above (descriptor-table-and-Java-content region) — and none of the 4 have any real caller found by this project's current reference-scanning method. This extends the 9MB+-wall finding: it isn't limited to the handful of originally-named targets (readNodeMP0 etc.) — real methods of a completely different, unrelated class are affected too, consistent with this being a systematic problem (plausibly affecting an entire translation unit's worth of symbol-table entries at once) rather than scattered per-symbol corruption.

Pattern across 3 consecutive attempts this session (the original 9MB-wall targets, EHTreeNode's methods, now RouteCalculation's): picking an exciting-sounding catalog name and trying to deep-trace it directly is a low-yield approach for this codebase — it either lands on wrong content or on no reachable content at all, consistently. Getting real algorithmic content for named classes reliably still means the same slow approach already used for this wiki's ~20 fully-traced pages: start from a real, independently-confirmed call site or data structure and work outward, never trust a catalog address in isolation.

Net: IDA's live database went from 45 to roughly 1,745 named functions this session, with the confidence level of each one legible directly from its name prefix (or lack of one) rather than hidden in a separate document.

The long-standing "9MB+ wall" mystery — substantially resolved using the whole-file function recovery

Background (the original mystery, documented in research/ swl_5238_reader.py's own docstring, pre-dating this wiki's shift to wiki-only documentation): several real, named, high-value symbol-table entries — readNodeMP0__9RoutePathUiR5VNodeH, RoutePath_readNodeMP0__FP9RoutePathUiP5VNoded, findNode__14MDCacheTIRTreeRC7MapRectPP18, RoutePath_getNodeVidArmMP0__FP9RoutePathUiPUlPi — have symbol-table field-4 "code addresses" implying file offsets around 11.9-17.3MB. Static prologue scanning (find_prologues()) never found ANY real stwu/mflr prologues anywhere near those addresses, confirmed dead across 5 independent disc builds (EU and North America). Previous sessions characterized this as code living beyond a structural "9MB+ wall," unreachable with the tools available at the time.

Re-tested with this session's whole-file IDA function recovery (98.9% of all real prologues bound into functions across the complete 24MB region) — this gives much stronger ground truth than prologue- pattern heuristics alone. All 4 target addresses land inside the exact same forced-boundary pseudo-function IDA's recovery created: 0xf7383630-0xf79f7c38, a 6.77MB span (file offset 0xaf593c-0x1169f44) — not a real function at all, just the gap between the nearest real prologues before and after a genuine, enormous "prologue desert." This precisely bounds, for the first time, exactly how large that desert is.

Characterized the desert's actual content directly (byte-level sampling + cross-checking against the already-known descriptor table) rather than leaving it as an unexplained void:

  • 97% of it (25,530 of this project's 26,320 total known descriptor- table records) is simply a long, uninterrupted run of the already-documented symbol/EH-descriptor table (file offset 0xaf593c-0xd5e7f4, ~2.9MB) — explaining why no prologues exist there: it's real compiler-emitted metadata, not a code gap at all. 3 of the 4 mystery targets (readNodeMP0, RoutePath_readNodeMP0, RoutePath_getNodeVidArmMP0) land in this sub-range — their field-4 "code addresses" are simply wrong, pointing into the MIDDLE OF A DIFFERENT DESCRIPTOR-TABLE RECORD'S OWN BYTES rather than any real code (directly confirmed: RoutePath_readNodeMP0's own target address decodes as literal ASCII text, "nextAlternative__ 15PositioningImpl..." — unmistakably another record's mangled name, not an instruction).
  • The remaining ~4.24MB (file offset 0xd5e7f4-0x1169f44, where findNode__14MDCacheTIRTree lands) is real embedded Java VM content — independently re-confirming and precisely extending a finding from an earlier NAR-disc session (research/ swl_5238_reader.py: "the 10-15MB strings are Java-class content, not native code"). Direct, unambiguous evidence sampled this session: a plain-ASCII Java classpath string, "classpath /tffs0/lib/vm/li[b]" (a real VxWorks TrueFFS flash path feeding a Java VM's own library path), plus multiple UTF-16LE (wide-character) strings — the standard encoding for Java's internal String representation — spelling recognizable fragments like "...lnk/LinkA...", "...aBroadcast.*", "Listen", "Exceptio[n]", "M_SETTINGS/A...". This extends the previously-measured ~10-15MB Java-content boundary out to at least ~18.3MB. UPDATE, a later session: this was only a narrow slice of something far bigger — see The Embedded Java Application Layer for a real, substantial (2,806+ class) embedded Java application platform spanning tens of megabytes across the whole file, including the real UI, navigation display logic, Bluetooth, climate control, and speech/voice recognition — none of which live in the native PowerPC C++ code this page otherwise documents.

Net resolution: the "9MB+ wall" was never a single, uniform phenomenon — it's two completely different, now-identified real structures sitting back-to-back (a dense run of known descriptor-table metadata, then a large embedded Java VM data/resource region), neither of which is "missing" or "unreachable" native PowerPC code at all. What remains genuinely unresolved (and likely unresolvable without new ground truth, e.g. the actual build/symbol-generation toolchain) is why these 4 specific entries' field-4 values point into this region rather than wherever their real compiled implementation actually lives — consistent with this project's existing, separately-confirmed finding that field-4 resolution is fundamentally unreliable beyond a narrow validated range, now with a concrete, physical explanation for where the "wrong" values actually land instead of just that they're wrong.

A "second load base" investigation: 3 more code regions found, 2 resolved to a different, real subsystem

A full find_prologues() sweep across the ENTIRE 85.7MB file (not just the known 0-9MB window) found 3 dense prologue clusters beyond the already-known native blob: ~25MB (374 prologues), ~30MB (362 prologues), and 76-82MB (6,130 prologues — this last one is the already-known VxWorks kernel/BSP tail, not new). Candidate load bases recovered per region with the same lis/addi clustering technique gave 88.8%/88.2%/99.8% respectively — well above chance, but NONE of the already-known unreachable targets (readNodeMP0, findNode__14MDCacheTIRTree, db_seg_rank_V004, etc.) resolve into any of them — this is a separate set of compilation units, not a hidden continuation of the native map/navicore blob.

Whether 25MB/30MB were real code or find_prologues() false positives inside the "24-76MB Java classes" territory was genuinely ambiguous at first (zero VxWorks-identifying strings nearby, yet a clean 31- instruction blr-terminated function on the first try). The "does it reach a clean blr" test turned out to be UNDISCRIMINATING — even the KNOWN-real 0-9MB region only hits a clean exit in ~10% of sampled prologues within a 4000-byte window, same ballpark as the candidates; don't trust this metric alone for this question again.

The decisive test was mangled C++ symbol names physically adjacent to the code, using the same "name string precedes its function's prologue by ~100-300 bytes" layout as this page's own symbol table. 25MB's names are all Q33vdo3svc*/Q33vdo4util* (a vdo::svc/ vdo::util namespace — Service/Dispatcher/String plumbing, matching DVD/AV-app Java strings found nearby — a genuinely separate multimedia- subsystem module). 30MB's names (only 3 in the entire 85.7MB file, tightly clustered) are GEO_PntPnt_UCangle, GEO_RpntPnt_NCpnt, GON_YXL_Atan — real geo-coordinate math type descriptors — but full disassembly of the adjacent prologue shows a large register-save frame and a repeated lwz->mtlr->blrl virtual-dispatch-through-a-service- object idiom, i.e. a command/event dispatch REGISTRATION routine (the same vdo::svc::dispatcher::Dispatcher role as 25MB), not the geo-math leaf functions themselves — their own bodies remain unidentified (their names appear nowhere else in the file). Confirmed real, newly-identified architecture (a service dispatcher spanning 25-30MB across multiple unrelated subsystems, including telephony/PTT names found in the same table) — but not a solution to this page's own open readNodeMP0/R-tree questions.

A whole-file reloc-confirmed name catalog — 1,573 names, 3 new subsystems

Applying this page's own lis/addi reloc-confirmation technique exhaustively across the whole 24MB region (rather than one call site at a time) surfaced three entire previously-undocumented real subsystems — SDARS satellite radio data services (sports scores, song tagging, fuel prices), real RDS-TMC traffic processing, and satellite weather radar overlay — plus extensive SQLite/zlib confirmation. Full writeup, method, and the archived 1,573-entry catalog: Firmware Reloc-Confirmed Names.

Reproducing this work

import sys
sys.path.insert(0, "research")
import swl_5238_reader as swl
import capstone

with open("5238_ALL/APPS/SILVER_1/RNSMIDEC/PROG/FHDD6.FLI", "rb") as f:
    fli = f.read()

prologues = swl.find_prologues(fli[:24_000_000])            # 7,757 real function starts
catalog = swl.parse_symbol_table(fli)                        # 8,041 name -> file_offset

cs = capstone.Cs(capstone.CS_ARCH_PPC,
                  capstone.CS_MODE_BIG_ENDIAN | capstone.CS_MODE_32)
off = catalog["getId__C26CfcMsgStreamedServicePoint"]
va = off + swl.FHDD6_LOAD_BASE
for insn in cs.disasm(fli[off:off + 400], va):
    print(hex(insn.address - swl.FHDD6_LOAD_BASE), insn.mnemonic, insn.op_str)
    if insn.mnemonic == "blr":
        break

Full narrative, every validation number, and every dead end tried (so they aren't repeated): research/swl_5238_reader.py's own module docstring — search for "load base WAS recovered", "symbol/EH- descriptor table", and the function names above.

⚠️ **GitHub.com Fallback** ⚠️