* feat(ghidra): admit soft-float MIPS32r2 ELF with integer-register float storage Soft-float o32 executables (Linux kernel builds, bare-metal firmware) were refused because their ABI flags record declares no CPR1 and FP ABI 3. Ghidra's MIPS32 compiler specifications model only hard-float o32, so typed float/double signatures (DWARF) would be bound to $f12/$f14/$f0 and the decompiler loses the real dataflow: `return sel ? a : b` decompiled to `return in_f0;`, and callers passed phantom FPU inputs. Admit ELF32 ET_EXEC o32 MIPS32r2 targets whose ABI record declares soft-float with no CPR1, and commit `mips_soft_float_storage: o32-integer-registers-v1` in their analysis profile only (hard-float profile digests are unchanged). ReaGhidraMipsSoftFloat.java runs after auto-analysis and, for each function with a float/double formal type, either keeps the types with the storage Ghidra's o32 model assigns to a same-size integer (pair alignment, stack spill), or, for auto-parameters such as hidden struct-return pointers, swaps float types for same-size integers under dynamic storage. No function is left on hard-float storage. The report is written last; the provider client requires it exactly when the profile commits the policy and refuses the session before any operation otherwise. Verification: real Ghidra 12.1.4 lane (verify:ghidra:mips) now builds hard- and soft-float fixtures in both byte orders and checks exact parameter/return storage through inspect_native_api, caller constants, snapshot profile and Evidence outcome; test:fast, verify:package and an installed-tarball soft-float run pass. * fix(ghidra): model soft-float MIPS as a prototype for every typed callable Review feedback on #1949: rewriting storage per Function left external functions and indirect calls through typed function pointers on Ghidra's hard-float o32 prototype, which still read $f12/$f14/$f0 while the session reported success. A negative control confirms it: without the change, `(*rea_mips_soft_callback)(8.5f, 9.5f, sel)` decompiles to `(*rea_mips_soft_callback)(in_f12, in_f14, sel)`. ReaGhidraMipsSoftFloat.java now derives a `__rea_soft_float` prototype from the program's own default o32 model (encoded, float pentries and float-consuming rules removed, so byte-order-specific aggregate rules and the hidden struct-return pointer are kept), installs it as a specification extension, and assigns it with dynamic storage to every non-thunk function, external function and function-pointer definition whose formal types use float or double. Thunks follow their targets. Float types are kept everywhere, so the integer-retype fallback is removed. The script fails without writing its report if the derived prototype or any reassigned function still uses an FPU register. The policy becomes `o32-soft-float-prototype-v1` with a {functions, definitions} report. The soft-float fixture adds an indirect call through a typed pointer; the real-Ghidra lane requires its constants with no FPU inputs and the struct return's `float scale` in $a1. * test(ghidra): pin ODDSPREG acceptance for soft-float MIPS ABI records Review on #1949 suggested requiring flags1 == 0 for soft-float, since MIPS_AFL_FLAGS1_ODDSPREG names CPR1 registers. GCC 5.2 kernel modules, LLVM/Rust firmware and Clang -msoft-float objects all record flags1 = 1 with FP ABI soft-float: it is the MIPS32 o32 toolchain default and selects nothing without CPR1. Keep accepting it, say why at the check, and test that soft-float with ODDSPREG is admitted while any other general flag is refused. * feat(ghidra): admit relocatable MIPS32r2 ELF objects Linux MIPS kernel modules (.ko) and other ELF32 o32 MIPS32r2 ET_REL objects were refused by the Ghidra MIPS lane. Ghidra's ELF loader already places each allocated section and applies REL relocations; this admits ET_REL alongside ET_EXEC (ET_DYN and PRX stay refused) under its own support lane, `elf32-rel-o32-arch32r2-standard-v1`, so executable targets keep theirs. Relocatable sessions carry limitations: addresses are analysis coordinates, undefined symbols live in Ghidra's EXTERNAL block, and HI16 relocations report PARTIAL because Ghidra applies them with their paired LO16. Verification: new `verify:ghidra:mips-relocatable` real-Ghidra lane builds -c -G0 objects in both byte orders plus a soft-float -mlong-calls module variant. GNU readelf supplies sections, symbols and REL entries; every relocation in an allocated section must encode S + A in the loaded word (S from Ghidra placement, A from original bytes), with HI16/LO16 checked as the effective address each LO16 forms, and coverage of a shared HI16, a LO16 carry, external code/data and R_MIPS_32 is asserted. The executable lane, unit suites pass, and on 30 real .ko files 54,972 relocations matched (93 reference empty sections and are not checkable). * fix(ghidra): verify every MIPS HI16 and archive-typed soft-float externals Review feedback on #1950: - The relocation oracle kept one pending HI16, so a second HI16 before the LO16 that resolves both overwrote the first, and unrelated relocations cleared it; the earlier site's high half and carry were never compared with S + A. Every HI16 now waits for the next LO16 of its symbol, which checks all of them; later LO16s reuse those HI16s, and a HI16 left unresolved at a section end fails. On 30 real soft-float kernel modules, 24 contain several HI16s per LO16; all 54,972 placed relocations match. - External callees with float prototypes: the relocatable fixture now calls powf, whose prototype Ghidra's C library archive applies by name. Every variant must decompile `powf(1.5,2.5)` without FPU inputs, and the soft-float module variant must report that __rea_soft_float covered it (with the external step removed it decompiles as `powf(param_1,param_2)`). `-fno-builtin` keeps the call from being folded. Also updates the module variant's expected soft-float policy to `o32-soft-float-prototype-v1`. --------- Co-authored-by: morluto <[email protected]>
11 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, MIPS16/microMIPS,
machine-specific variants, shared objects and PSP PRX are not covered by this
lane. Their family may be identified, but provider selection returns the
specific unsupported constraint rather than guessing a compatible profile.
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.
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. A valid program-header ABI record can still establish
the supported declaration. Available section declarations must agree with it;
their duplicate, conflicting or unreadable records remain errors. 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 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 this lane refuses them. 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.
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 remains PE x86/x86-64 only.
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 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.