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.
177 lines
8.5 KiB
Java
177 lines
8.5 KiB
Java
import ghidra.app.script.GhidraScript;
|
|
import ghidra.program.database.SpecExtension;
|
|
import ghidra.program.model.data.AbstractFloatDataType;
|
|
import ghidra.program.model.data.DataType;
|
|
import ghidra.program.model.data.DoubleDataType;
|
|
import ghidra.program.model.data.FloatDataType;
|
|
import ghidra.program.model.data.FunctionDefinition;
|
|
import ghidra.program.model.data.IntegerDataType;
|
|
import ghidra.program.model.data.ParameterDefinition;
|
|
import ghidra.program.model.data.TypeDef;
|
|
import ghidra.program.model.lang.CompilerSpec;
|
|
import ghidra.program.model.lang.PrototypeModel;
|
|
import ghidra.program.model.lang.Register;
|
|
import ghidra.program.model.listing.Function;
|
|
import ghidra.program.model.listing.Parameter;
|
|
import ghidra.program.model.listing.Program;
|
|
import ghidra.program.model.listing.VariableStorage;
|
|
import ghidra.program.model.pcode.XmlEncode;
|
|
import java.io.StringReader;
|
|
import java.io.StringWriter;
|
|
import java.util.ArrayList;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.regex.Pattern;
|
|
import javax.xml.parsers.DocumentBuilderFactory;
|
|
import javax.xml.transform.OutputKeys;
|
|
import javax.xml.transform.TransformerFactory;
|
|
import javax.xml.transform.dom.DOMSource;
|
|
import javax.xml.transform.stream.StreamResult;
|
|
import org.w3c.dom.Document;
|
|
import org.w3c.dom.Element;
|
|
import org.w3c.dom.Node;
|
|
import org.w3c.dom.NodeList;
|
|
import org.xml.sax.InputSource;
|
|
|
|
/**
|
|
* Soft-float o32 passes and returns float/double values exactly where same-size integers go.
|
|
* Ghidra's MIPS32 compiler specifications model only hard-float o32, so every typed float
|
|
* prototype (functions, externals, thunks via their targets, and function-pointer definitions)
|
|
* would otherwise read $f12/$f14/$f0. Derive a program-specific prototype model from the
|
|
* program's own default o32 model with its floating-point entries and rules removed, install it
|
|
* as a specification extension, and assign it to every callable whose formal types use floats.
|
|
*/
|
|
public final class ReaGhidraMipsSoftFloat extends GhidraScript {
|
|
static final String REPORT_KEY = "REA MIPS soft-float storage";
|
|
static final String POLICY = "o32-soft-float-prototype-v1";
|
|
static final String PROTOTYPE = "__rea_soft_float";
|
|
private static final Pattern FPU_REGISTER = Pattern.compile("f\\d+(_\\d+)?");
|
|
|
|
@Override
|
|
public void run() throws Exception {
|
|
String processor = currentProgram.getLanguage().getProcessor().toString();
|
|
if (!processor.equals("MIPS") || currentProgram.getDefaultPointerSize() != 4)
|
|
throw new IllegalStateException(
|
|
"REA soft-float storage requires a 32-bit MIPS program, not " + processor);
|
|
CompilerSpec spec = currentProgram.getCompilerSpec();
|
|
PrototypeModel base = spec.getDefaultCallingConvention();
|
|
new SpecExtension(currentProgram)
|
|
.addReplaceCompilerSpecExtension(softFloatPrototype(base, spec), monitor);
|
|
PrototypeModel soft = currentProgram.getCompilerSpec().getCallingConvention(PROTOTYPE);
|
|
if (soft == null)
|
|
throw new IllegalStateException("REA soft-float prototype was not installed");
|
|
requireIntegerStorage(
|
|
soft.getStorageLocations(
|
|
currentProgram,
|
|
new DataType[] {
|
|
FloatDataType.dataType, IntegerDataType.dataType, DoubleDataType.dataType,
|
|
FloatDataType.dataType, DoubleDataType.dataType
|
|
},
|
|
false),
|
|
"soft-float prototype self-check");
|
|
int functions = 0;
|
|
List<Function> callables = new ArrayList<>();
|
|
currentProgram.getFunctionManager().getFunctions(true).forEach(callables::add);
|
|
currentProgram.getFunctionManager().getExternalFunctions().forEach(callables::add);
|
|
for (Function function : callables) {
|
|
// A thunk's signature is its target's; the target is in this list.
|
|
if (function.isThunk() || !usesFloat(function)) continue;
|
|
if (function.hasCustomVariableStorage()) function.setCustomVariableStorage(false);
|
|
function.setCallingConvention(PROTOTYPE);
|
|
List<VariableStorage> storage = new ArrayList<>();
|
|
storage.add(function.getReturn().getVariableStorage());
|
|
for (Parameter parameter : function.getParameters()) storage.add(parameter.getVariableStorage());
|
|
requireIntegerStorage(storage.toArray(VariableStorage[]::new), function.getName(true));
|
|
functions += 1;
|
|
}
|
|
int definitions = 0;
|
|
Iterator<DataType> types = currentProgram.getDataTypeManager().getAllDataTypes();
|
|
while (types.hasNext()) {
|
|
if (!(types.next() instanceof FunctionDefinition definition) || !usesFloat(definition))
|
|
continue;
|
|
definition.setCallingConvention(PROTOTYPE);
|
|
definitions += 1;
|
|
}
|
|
// Written last: any failure above leaves no report, and REA refuses the session.
|
|
currentProgram.getOptions(Program.PROGRAM_INFO).setString(
|
|
REPORT_KEY,
|
|
"{\"policy\":\"" + POLICY + "\",\"functions\":" + functions +
|
|
",\"definitions\":" + definitions + "}");
|
|
println("REA MIPS soft-float prototype: functions=" + functions + " definitions=" + definitions);
|
|
}
|
|
|
|
/** The default o32 model without floating-point registers or float-consuming rules. */
|
|
private static String softFloatPrototype(PrototypeModel base, CompilerSpec spec) throws Exception {
|
|
XmlEncode encoder = new XmlEncode();
|
|
base.encode(encoder, spec.getPcodeInjectLibrary());
|
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
|
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
|
Document document =
|
|
factory.newDocumentBuilder().parse(new InputSource(new StringReader(encoder.toString())));
|
|
Element root = document.getDocumentElement();
|
|
if (!root.getTagName().equals("prototype"))
|
|
throw new IllegalStateException("Unexpected default prototype encoding " + root.getTagName());
|
|
root.setAttribute("name", PROTOTYPE);
|
|
List<Node> removed = new ArrayList<>();
|
|
collect(root.getElementsByTagName("pentry"), "metatype", "float", null, removed);
|
|
collect(root.getElementsByTagName("consume"), "storage", "float", "rule", removed);
|
|
if (removed.isEmpty())
|
|
throw new IllegalStateException("Default prototype has no floating-point entries to remove");
|
|
for (Node node : removed) node.getParentNode().removeChild(node);
|
|
StringWriter xml = new StringWriter();
|
|
var transformer = TransformerFactory.newInstance().newTransformer();
|
|
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
|
transformer.transform(new DOMSource(document), new StreamResult(xml));
|
|
return xml.toString();
|
|
}
|
|
|
|
/** Select matching elements, or their nearest `ancestor` element when one is named. */
|
|
private static void collect(
|
|
NodeList nodes, String attribute, String value, String ancestor, List<Node> selected) {
|
|
for (int index = 0; index < nodes.getLength(); index += 1) {
|
|
Element element = (Element) nodes.item(index);
|
|
if (!value.equals(element.getAttribute(attribute))) continue;
|
|
Node target = element;
|
|
while (ancestor != null && target != null && !ancestor.equals(target.getNodeName()))
|
|
target = target.getParentNode();
|
|
if (target != null && !selected.contains(target)) selected.add(target);
|
|
}
|
|
}
|
|
|
|
private static void requireIntegerStorage(VariableStorage[] storage, String owner) {
|
|
for (VariableStorage item : storage) {
|
|
if (item.isBadStorage() || item.isUnassignedStorage())
|
|
throw new IllegalStateException("REA soft-float storage unresolved for " + owner);
|
|
List<Register> registers = item.getRegisters();
|
|
if (registers == null) continue; // stack-only storage
|
|
for (Register register : registers) {
|
|
if (FPU_REGISTER.matcher(register.getName()).matches())
|
|
throw new IllegalStateException(
|
|
"REA soft-float storage still uses " + register.getName() + " for " + owner);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static boolean usesFloat(Function function) {
|
|
if (isFloat(function.getReturn().getFormalDataType())) return true;
|
|
for (Parameter parameter : function.getParameters()) {
|
|
if (isFloat(parameter.getFormalDataType())) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static boolean usesFloat(FunctionDefinition definition) {
|
|
if (isFloat(definition.getReturnType())) return true;
|
|
for (ParameterDefinition argument : definition.getArguments()) {
|
|
if (isFloat(argument.getDataType())) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static boolean isFloat(DataType type) {
|
|
DataType current = type;
|
|
while (current instanceof TypeDef typeDef) current = typeDef.getBaseDataType();
|
|
return current instanceof AbstractFloatDataType;
|
|
}
|
|
}
|