# MIPS ELF analysis through Ghidra This lane extends native ELF analysis; it does not execute MIPS code or rehost firmware. It advances the generic-MIPS portion of [#718](https://github.com/morluto/rea/issues/718), not the whole firmware roadmap. PSP/Allegrex has a [separate static-ELF profile](ghidra-psp.md) tracked in [#1330](https://github.com/morluto/rea/issues/1330). ## Admission boundary ELF `EM_MIPS` is retained as the provider-neutral `mips` family. The target also retains class, byte order, `e_type`, raw `e_flags`, and inspected ABI declarations. Recognition does not mean every provider or MIPS variant is supported. Existing PE, managed and Mach-O architecture sets are unchanged. The Ghidra lane admits ELF32 `ET_EXEC` and `ET_REL`, little or big endian, with explicit o32 and `EF_MIPS_ARCH_32R2` flags and no machine-specific or ASE encoding flags. These are header declarations, not proof that every instruction conforms to that ISA. ELF64, n32/n64, unspecified ABI, microMIPS, machine-specific variants and PSP PRX are not covered by this standard-instruction lane. MIPS16 shared images have the separate bounded profile below; other shared or compressed images remain unsupported. Their family may be identified, but provider selection returns the specific unsupported constraint rather than guessing a compatible profile. A second admission covers recordless legacy firmware images ([#1620](https://github.com/morluto/rea/issues/1620)): ELF32 `ET_EXEC` headers declaring `EF_MIPS_ARCH_32` or `EF_MIPS_ARCH_32R2` with an unspecified ABI field, `EF_MIPS_ABI2` clear, and an _inspected_ absence of any ABI flags record (a target whose record was merely not observed stays refused). For these images o32 is an analysis assumption derived from the ELF32/ET_EXEC structure, recorded as `mips_abi_basis` in the profile and stated as a limitation in Evidence, never a declared fact; floating-point ABI semantics stay unverified. Ghidra's own header auto-selection picks a 64-bit language for exactly these files, so the lane pins `-processor MIPS::32:default` through a `provider-pinned-v1` profile selection and checks the bridge-reported `language_id` after import. A caller-selected `REA_GHIDRA_LANGUAGE_ID`/`REA_GHIDRA_COMPILER_SPEC_ID` still wins over the pin; combining the pinned profile with an environment override is a typed refusal. A header that declares an ABI field but carries no record is still refused. Two further boundaries apply: an inspection that could not examine every ABI declaration source (for example a carved ELF whose section table extends past the captured bytes without a program-header record) does not establish the recordless admission, and an `EF_MIPS_ARCH_32` header that _declares_ an ABI is refused rather than relabeled, because the declared-ABI lane is verified for `EF_MIPS_ARCH_32R2` headers only. Explicit `EF_MIPS_FP64` and `EF_MIPS_NAN2008` declarations are refused. Their absence is insufficient: Clang can emit identical `e_flags` for FP32, FP64 and soft-float targets. Target resolution also reads the 24-byte ABI flags record identified by `PT_MIPS_ABIFLAGS` and/or `SHT_MIPS_ABIFLAGS`, without relying on section names or the record being inside the initial 4 KiB header probe. Program and section declarations must agree; duplicate, conflicting, truncated or out-of-file records are rejected. Stripped section tables and section-only records are supported. Extended table numbering is explicitly unsupported. GNU `SHT_GNU_ATTRIBUTES` sections are refused by numeric section type until REA parses and reconciles their declarations; section names and payload sizes do not control detection or allocation. For carved ELFs whose section table extends beyond the captured file, REA examines every complete captured section header and records the unavailable headers as a limitation. The provider refuses an incomplete ABI declaration inspection even when a program-header ABI record is valid, because an unavailable section header could carry an unsupported or conflicting declaration. A missing program-header record does not make ABI declarations in uncaptured sections absent or supply a default ABI. The field layout follows [`Elf_MIPS_ABIFlags_v0`](https://github.com/bminor/glibc/blob/master/elf/elf.h). Provider admission on the declared-ABI lane above requires record version 0, MIPS32r2, 32-bit GPRs, no CPR2, no ISA extension, ASE or unknown general flags, and one of two floating-point declarations: 32-bit CPR1 with a double-precision or FPXX ABI, or no CPR1 with the soft-float ABI. Other combinations, including single-float, FP64 and unspecified ABIs, are refused. Missing records remain unknown rather than defaulting to a compatible ABI, and the declared-ABI lane refuses them; the recordless legacy admission above is the only path that admits a recordless image. Unknown record values are retained for diagnosis but do not gain provider support. These checks bind the declared interpretation; the fixtures do not establish full floating-point behavioral conformance. ### MIPS16 shared images A separate profile admits ELF32 `ET_DYN`, either byte order, declaring explicit o32, MIPS32r2 and exactly the MIPS16 ASE. Machine variants, n32, FP64 and NAN2008 remain refused. Source inspection must examine the complete dynamic relocation declarations through uniquely mapped file-backed `PT_LOAD` ranges. This profile verifies `R_MIPS_NONE` and symbol-index-zero `R_MIPS_REL32`; section declarations must describe that same dynamic table. Unavailable section headers and additional section relocation tables (including `--emit-relocs` output) remain unsupported. Symbol-bearing REL32, other relocation types, RELA and PLT declarations remain unsupported. A real negative control shows that Ghidra can normalize a symbol-bearing MIPS16 function pointer to an even address, losing the source mode tag. REA refuses that shape rather than rewriting the source or silently repairing the analysis result. The profile pins the endian-matched `MIPS::32:default` processor and default compiler specification, and checks the bridge-reported language after import. The generic processor preserves mixed MIPS32/MIPS16 code through per-address `ISA_MODE` context; a global `16e` processor would impose one mode across the image. A caller-selected incompatible processor or compiler specification is explicitly refused without replacing the requested choice. An observed ABI flags record must agree with the header and MIPS16 ASE, and must declare the same verified floating-point combinations as the standard lane. With no observed record, every ABI declaration table must have been completely examined. The header still declares o32, but floating-point ABI semantics remain unknown and are stated in Evidence; selecting the default compiler specification does not establish floating-point storage. Partial section tables do not prove record absence. The existing soft-float prototype policy applies when the source explicitly declares soft-float. Returned addresses are analysis coordinates, not runtime load addresses. `inspect_native_load_image` preserves original relocation bytes, current loaded memory bytes and provider status. External GOT bindings remain unverified without their defining images. The provider's `modified_sha256` describes its ephemeral relocated image and can differ from `original_sha256`; it does not mean that the caller's artifact was written. `inspect_native_instruction` retains the program language variant in `mode` and reports actual per-address `ISA_MODE` separately in `decoder_context`, including null when unavailable. Artifact bytes and digests remain unchanged; this workflow does not execute the shared library. The optional real-provider check builds eight source-owned GNU controls, covering both byte orders, mixed MIPS32/MIPS16 code, declared and absent ABI records, and the rejected symbol-bearing and additional section relocations. It compares source instruction bytes, function symbols and the local function-pointer tag with actual CLI/MCP results, checks context through a snapshot round trip, and verifies process cleanup. It requires caller-supplied GNU MIPS cross-binutils and Ghidra; no fixture target is executed or patched after linking. Floating-point behavior of compressed functions is not established by these integer controls. ```bash REA_MIPS_AS=/absolute/path/to/mips-linux-gnu-as \ REA_MIPS_LD=/absolute/path/to/mips-linux-gnu-ld \ GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra \ npm run verify:ghidra:mips-shared ``` MIPS header and ABI record facts are committed in the analysis profile so interpretation changes invalidate profile-bound snapshots. The support-lane identity is versioned independently of file identity. Evidence, lifecycle outputs and saved snapshots preserve the `mips` family. Hopper and IDA adapters do not implicitly gain MIPS support. Windows Ghidra P0 does not admit MIPS targets. ### Soft-float prototype Ghidra's MIPS32 compiler specifications model hard-float o32 only: a typed `float` or `double` parameter is bound to `$f12`/`$f14` and returned in `$f0`. Soft-float o32 passes those values in integer registers and stack slots, so a typed prototype (for example from DWARF) would make the decompiler read FPU registers the code never writes and drop the real dataflow. That applies to functions, external functions and indirect calls through typed function pointers alike. For soft-float targets REA commits `mips_soft_float_storage: o32-soft-float-prototype-v1` in the analysis profile and runs `ReaGhidraMipsSoftFloat.java` after auto-analysis. The script encodes the program's own default o32 prototype, removes its floating-point parameter and return entries and the rules that consume floating-point storage, and installs the result as the program-specific `__rea_soft_float` prototype through a Ghidra specification extension. Deriving it keeps the byte-order-specific aggregate rules and the hidden struct-return pointer. The script then assigns that prototype, with dynamic storage, to every non-thunk function and external function, and to every function-pointer definition, whose declared types use `float` or `double`. Thunks follow their targets. Types are kept. The script fails, and writes no report, if the derived prototype still places a float in an FPU register or any reassigned function does. It records the function and definition counts last; REA requires that report exactly when the profile commits the policy and refuses the session otherwise, before serving any operation. Session Evidence carries the policy and counts as limitations. Callables without typed float signatures keep Ghidra's default prototype, under which Ghidra already infers integer-register parameters. Hard-float profiles do not gain the policy, so their digests are unchanged. ### Relocatable objects `ET_REL` objects, such as Linux kernel modules (`.ko`), are imported by Ghidra's ELF loader, which assigns each allocated section an analysis address and applies the object's REL relocations there. Undefined symbols are placed in Ghidra's `EXTERNAL` block: a thunk for code, a label for data. These addresses are analysis coordinates, not the addresses a kernel or linker would choose, and session Evidence says so. `inspect_native_load_image` reports each relocation with Ghidra's status; HI16 entries report `PARTIAL` because Ghidra applies them together with their paired LO16. Relocatable targets commit `mips_support_lane: elf32-rel-o32-arch32r2-standard-v1`, separate from the executable lane, whose identity executable targets keep. Kernel modules are usually soft-float and therefore also use the soft-float storage policy. ## Use With a caller-installed Ghidra and compatible JDK on a supported Linux/macOS host: ```bash rea analyze /absolute/path/to/mips.elf --provider ghidra --json rea function /absolute/path/to/mips.elf function_name --provider ghidra --json ``` MCP uses the existing tools, including `open_binary` with an absolute path and `provider_id: "ghidra"`. No MIPS-specific tool catalog or emulator is introduced. Inspect instructions, caller relationships and data alongside pseudocode. In particular, delay-slot semantics are supplied by Ghidra, not reconstructed by REA's ELF parser. ## Verification ### Executables The source-owned freestanding fixture is `tests/conformance/c/mips.c`; the soft-float variant also links `tests/conformance/c/mips-soft-float.c`. The optional cross-target lane requires a caller-supplied Clang with MIPS targets, LLD, GNU `readelf`, Ghidra and its compatible JDK; ordinary host-native checks do not acquire these tools. `REA_MIPS_CLANG` selects Clang and `REA_MIPS_READELF` selects GNU readelf (for example `greadelf` on macOS). These are test-time prerequisites, not REA runtime providers. ```bash GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra npm run verify:ghidra:mips ``` The lane compiles both byte orders with hard-float and soft-float ABIs and never executes the targets. GNU readelf independently supplies header/ABI facts and symbol addresses, compared with the production REA target resolver before provider startup. Its version, bounded command output and selected tool path are retained in the verification report. The parser deliberately recognizes only this fixture's GNU output, rejecting missing, ambiguous or unexpected fields rather than substituting defaults. Existing public MCP operations check Ghidra's **observed** language/compiler specification and loaded-file hashes. The source-owned `mips-probe.S` supplies fixed ADDIU and conditional-branch encodings: verification checks bytes, length, immediate and direct destination, independent of decompiler variable names. Global and string bytes are checked at independently reported symbol addresses. ELF load-image results retain observations even though REA's independent load-image comparison currently covers DOS, not ELF; this lane checks those observations and does not relabel the upstream result as verified. CLI/MCP discovery and decompilation, direct calls, target/profile/Evidence identity, snapshot round-trip and final process cleanup remain covered. For both byte orders, the lane also carves away the section table while retaining all program-header payloads. It checks the actual Ghidra language, entry-address decompilation, distinct artifact digest and missing-section limitations through CLI Evidence and an MCP-produced snapshot. Caller-selected Ghidra heap limits are passed to these child workflows. For soft-float fixtures the lane also checks, through `inspect_native_api`, the exact parameter and return types and storage of float, double, mixed (pair alignment and stack spill) and struct-returning functions; that a caller's decompilation receives every source float constant without FPU inputs, including an indirect call through a typed function pointer; and that the snapshot profile and Evidence retain the policy and outcome counts. Pseudocode presence is a liveness check, not proof of semantic equivalence. Instruction decoding and reader agreement likewise do not establish runtime, delay-slot execution or complete floating-point semantics. Assertion regressions reject incorrect byte order, constants, destinations and loaded identities. Parser/profile tests are narrower checks, not substitutes for this real-provider run. Report the actual lanes run and leave unexecuted verification pending. ### Relocatable objects This lane has its own command and fixture: ```bash GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra npm run verify:ghidra:mips-relocatable ``` It compiles `tests/conformance/c/mips-relocatable.c` with `-c -G0` in both byte orders, plus a little-endian soft-float `-mlong-calls` variant matching Linux module options. GNU readelf independently supplies sections, symbols and REL entries. For every relocation in an allocated section, the lane reads the loaded word through `read_bytes` and requires it to encode `S + A`: `S` from Ghidra's own section placement (`list_segments`) or `EXTERNAL` placement, `A` from the original file bytes. HI16/LO16 are checked as the effective address each LO16 forms with each HI16 it resolves, including several HI16s before one LO16, so a missing signed-low carry fails and a HI16 without a following LO16 fails. The lane also requires matching Ghidra relocation entries and statuses, section file offsets and sizes, coverage of a shared HI16, a LO16 resolving several pending HI16s (from an assembly block, since Clang does not emit that from C), a LO16 carry, external code and data and `R_MIPS_32` words, direct calls from the entry function, the relocatable support lane in the snapshot profile, the relocatable limitation in Evidence and an unchanged source object. The fixture also calls `powf`, an external whose float prototype comes from Ghidra's C library archive; every variant must decompile `powf(1.5,2.5)` without FPU inputs, and the soft-float variant must report that the soft-float prototype covered it.