mirror of
https://github.com/morluto/rea.git
synced 2026-10-11 21:39:55 +00:00
* 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.
95 lines
2.8 KiB
JavaScript
95 lines
2.8 KiB
JavaScript
import { execFile } from "node:child_process";
|
|
import { createHash } from "node:crypto";
|
|
import { readFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { promisify } from "node:util";
|
|
|
|
const execute = promisify(execFile);
|
|
|
|
/** Build a source-owned freestanding ELF; never execute its target code. */
|
|
export async function buildMipsFixture(
|
|
directory,
|
|
byteOrder,
|
|
floatAbi = "hard",
|
|
) {
|
|
if (byteOrder !== "little" && byteOrder !== "big")
|
|
throw new Error("MIPS fixture byte order must be little or big");
|
|
if (floatAbi !== "hard" && floatAbi !== "soft")
|
|
throw new Error("MIPS fixture float ABI must be hard or soft");
|
|
const soft = floatAbi === "soft";
|
|
const compiler = process.env.REA_MIPS_CLANG ?? "clang";
|
|
const target = byteOrder === "little" ? "mipsel-linux-gnu" : "mips-linux-gnu";
|
|
const source = fileURLToPath(new URL("../c/mips.c", import.meta.url));
|
|
const softSource = fileURLToPath(
|
|
new URL("../c/mips-soft-float.c", import.meta.url),
|
|
);
|
|
const probe = fileURLToPath(new URL("./mips-probe.S", import.meta.url));
|
|
const path = join(directory, `${target}${soft ? "-soft-float" : ""}.elf`);
|
|
const args = [
|
|
`--target=${target}`,
|
|
"-march=mips32r2",
|
|
"-mabi=32",
|
|
...(soft ? ["-msoft-float"] : []),
|
|
"-mno-abicalls",
|
|
"-fno-pic",
|
|
"-fuse-ld=lld",
|
|
"-nostdlib",
|
|
"-static",
|
|
"-O0",
|
|
"-g",
|
|
"-Wl,-e,rea_mips_entry",
|
|
"-Wl,--build-id=none",
|
|
source,
|
|
...(soft ? [softSource] : []),
|
|
probe,
|
|
"-o",
|
|
path,
|
|
];
|
|
const version = await execute(compiler, ["--version"], { timeout: 10000 });
|
|
await execute(compiler, args, { timeout: 60000 });
|
|
const bytes = await readFile(path);
|
|
const read16 =
|
|
byteOrder === "little"
|
|
? bytes.readUInt16LE.bind(bytes)
|
|
: bytes.readUInt16BE.bind(bytes);
|
|
const read32 =
|
|
byteOrder === "little"
|
|
? bytes.readUInt32LE.bind(bytes)
|
|
: bytes.readUInt32BE.bind(bytes);
|
|
if (
|
|
bytes.length < 52 ||
|
|
bytes.toString("hex", 0, 4) !== "7f454c46" ||
|
|
bytes[4] !== 1 ||
|
|
bytes[5] !== (byteOrder === "little" ? 1 : 2) ||
|
|
read16(16) !== 2 ||
|
|
read16(18) !== 8 ||
|
|
read32(36) !== 0x70001001
|
|
)
|
|
throw new Error(
|
|
"Compiler did not produce the declared ELF32/o32/MIPS32r2 fixture",
|
|
);
|
|
return {
|
|
path,
|
|
byte_order: byteOrder,
|
|
float_abi: floatAbi,
|
|
flags: read32(36),
|
|
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
source_sha256: createHash("sha256")
|
|
.update(await readFile(source))
|
|
.digest("hex"),
|
|
...(soft
|
|
? {
|
|
soft_source_sha256: createHash("sha256")
|
|
.update(await readFile(softSource))
|
|
.digest("hex"),
|
|
}
|
|
: {}),
|
|
probe_sha256: createHash("sha256")
|
|
.update(await readFile(probe))
|
|
.digest("hex"),
|
|
compiler: version.stdout.trim(),
|
|
arguments: args,
|
|
};
|
|
}
|