17 KiB
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, not the whole firmware roadmap. PSP/Allegrex has a separate static-ELF profile tracked in #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): 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:<LE|BE>: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.
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:<LE|BE>: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.
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:
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.
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:
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.