Files
Aurelio Bernal 5e219de441 feat(ghidra): admit soft-float MIPS32r2 ELF with integer-register float storage (#1949)
* 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.
2026-10-12 04:54:38 +08:00

54 lines
2.0 KiB
C

/* Soft-float o32 storage fixture; linked with mips.c, never executed by REA.
* Without an FPU, float/double values travel in integer registers and stack
* slots: pick uses $a0/$a1/$a2 and returns in $v0, wide uses the $a0:$a1 pair,
* and mixed aligns d to $a2:$a3 so f and e spill to the stack at 16 and 24.
* structured returns through a hidden $a0 pointer, moving scale to $a1. The
* typed function pointer makes the caller's indirect call use a prototype that
* belongs to no function. No arithmetic, so no compiler-rt helpers are needed.
*/
volatile unsigned int rea_mips_soft_sink;
__attribute__((noinline)) float rea_mips_soft_pick(float a, float b, int sel)
{
return sel ? a : b;
}
__attribute__((noinline)) double rea_mips_soft_wide(double a, int sel)
{
return sel ? a : 0.5;
}
__attribute__((noinline)) double rea_mips_soft_mixed(int i, double d, float f, double e)
{
rea_mips_soft_sink = *(volatile unsigned int *)&f;
return i ? d : e;
}
struct rea_mips_soft_pair {
double first;
double second;
};
__attribute__((noinline)) struct rea_mips_soft_pair rea_mips_soft_struct(float scale, int sel)
{
struct rea_mips_soft_pair pair = { 0.25, 0.75 };
rea_mips_soft_sink = *(volatile unsigned int *)&scale;
if (sel)
pair.first = 1.25;
return pair;
}
/* Volatile so the indirect call through this typed pointer is not folded. */
float (*volatile rea_mips_soft_callback)(float, float, int) = rea_mips_soft_pick;
__attribute__((noinline)) unsigned int rea_mips_soft_caller(int sel)
{
float picked = rea_mips_soft_pick(1.5f, 2.5f, sel);
double wide = rea_mips_soft_wide(3.25, sel);
double mixed = rea_mips_soft_mixed(sel, 4.75, 5.5f, 6.125);
struct rea_mips_soft_pair pair = rea_mips_soft_struct(7.5f, sel);
float called = rea_mips_soft_callback(8.5f, 9.5f, sel);
return *(unsigned int *)&picked ^ *(unsigned int *)&wide ^ *(unsigned int *)&mixed ^
*(unsigned int *)&pair.first ^ *(unsigned int *)&called;
}