mirror of
https://github.com/morluto/rea
synced 2026-10-27 22:45:19 -04:00
c86b09a109
* feat(ghidra): admit 32-bit big-endian PowerPC ELF Recognize EM_PPC as a provider-neutral `powerpc` family that keeps its ELF class, byte order, type and flags, and admit ELF32 big-endian ET_EXEC through a bounded Ghidra lane. ELF64, little-endian, relocatable/shared objects and EF_PPC_EMB are refused with a specific reason; Hopper and IDA do not gain PowerPC support, and Windows Ghidra P0 is unchanged. Ghidra's ELF loader selects PowerPC:BE:32:e500:default for a plain EM_PPC header, which misreads 750-family code. The lane binds PowerPC:BE:32:default/default in the profile (language_selection "admitted-lane-v1"), imports with it and checks the reported language. A language set through REA_GHIDRA_LANGUAGE_ID still replaces the lane default. Add a source-owned fixture, fixed-encoding probe, GNU readelf cross-check and the optional `verify:ghidra:ppc` real-provider lane, plus docs/ghidra-ppc.md. Refs #1741 * style(ghidra): format shared PowerPC profile integration * fix(ghidra): preserve caller-selected PowerPC languages --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
188 lines
5.6 KiB
TypeScript
188 lines
5.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
assertPowerPcGlobal,
|
|
assertPowerPcLoadedImage,
|
|
assertPowerPcProbe,
|
|
parsePowerPcReadelf,
|
|
} from "./ppc-checks.mjs";
|
|
|
|
// GNU readelf 2.47 output for the source-owned Clang 23 / LLD fixture.
|
|
// This tests the reader boundary, not Ghidra; the real lane rebuilds it.
|
|
const readelf = `ELF Header:
|
|
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
|
|
Class: ELF32
|
|
Data: 2's complement, big endian
|
|
Version: 1 (current)
|
|
OS/ABI: UNIX - System V
|
|
ABI Version: 0
|
|
Type: EXEC (Executable file)
|
|
Machine: PowerPC
|
|
Version: 0x1
|
|
Entry point address: 0x100101b0
|
|
Start of program headers: 52 (bytes into file)
|
|
Start of section headers: 2188 (bytes into file)
|
|
Flags: 0x0
|
|
Size of this header: 52 (bytes)
|
|
Size of program headers: 32 (bytes)
|
|
Number of program headers: 6
|
|
Size of section headers: 40 (bytes)
|
|
Number of section headers: 18
|
|
Section header string table index: 16
|
|
|
|
Symbol table '.symtab' contains 7 entries:
|
|
Num: Value Size Type Bind Vis Ndx Name
|
|
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
|
|
1: 00000000 0 FILE LOCAL DEFAULT ABS ppc.c
|
|
2: 10010180 48 FUNC GLOBAL DEFAULT 4 rea_ppc_leaf
|
|
3: 1002023c 4 OBJECT GLOBAL DEFAULT 5 rea_ppc_global
|
|
4: 100101b0 116 FUNC GLOBAL DEFAULT 4 rea_ppc_entry
|
|
5: 100000f4 29 OBJECT GLOBAL DEFAULT 1 rea_ppc_marker
|
|
6: 10010224 24 FUNC GLOBAL DEFAULT 4 rea_ppc_probe
|
|
`;
|
|
const sha256 = "a".repeat(64);
|
|
const image = {
|
|
language_id: "PowerPC:BE:32:default",
|
|
compiler_spec_id: "default",
|
|
source_files: [{ original_sha256: sha256, modified_sha256: sha256 }],
|
|
};
|
|
const move = {
|
|
address: "0x10010224",
|
|
status: "decoded",
|
|
architecture: "PowerPC:BE:32:default",
|
|
length: 4,
|
|
bytes: "38641234",
|
|
mnemonic: "addi",
|
|
operands: [
|
|
{ index: 2, components: [{ kind: "immediate", value: "0x1234" }] },
|
|
],
|
|
};
|
|
const branch = {
|
|
address: "0x1001022c",
|
|
status: "decoded",
|
|
architecture: "PowerPC:BE:32:default",
|
|
length: 4,
|
|
bytes: "41820008",
|
|
mnemonic: "beq",
|
|
flow: {
|
|
kind: "jump",
|
|
conditional: true,
|
|
computed: false,
|
|
direct_destinations: ["0x10010234"],
|
|
},
|
|
};
|
|
|
|
describe("PowerPC real-verifier independent expectations", () => {
|
|
it("reads GNU header and defined symbols without a Ghidra-derived oracle", () => {
|
|
const parsed = parsePowerPcReadelf(readelf);
|
|
expect(parsed.powerpc).toEqual({
|
|
elfClass: 32,
|
|
byteOrder: "big",
|
|
type: 2,
|
|
flags: 0,
|
|
});
|
|
expect(parsed.symbols.rea_ppc_probe).toBe("0x10010224");
|
|
});
|
|
|
|
it.each([
|
|
["wrong class", "ELF32", "ELF64"],
|
|
["wrong byte order", "big endian", "little endian"],
|
|
[
|
|
"wrong machine",
|
|
"Machine: PowerPC",
|
|
"Machine: PowerPC64",
|
|
],
|
|
["wrong object type", "EXEC (Executable file)", "DYN (Shared object file)"],
|
|
[
|
|
"wrong flags",
|
|
"Flags: 0x0",
|
|
"Flags: 0x80000000",
|
|
],
|
|
["missing symbol", "rea_ppc_probe", "unrelated_probe"],
|
|
])(
|
|
"rejects %s instead of returning a guessed fixture interpretation",
|
|
(_name, from, to) => {
|
|
expect(() => parsePowerPcReadelf(readelf.replace(from, to))).toThrow();
|
|
},
|
|
);
|
|
|
|
it("rejects ambiguous producer fields", () => {
|
|
expect(() => parsePowerPcReadelf(`${readelf}\nClass: ELF32\n`)).toThrow(
|
|
/ambiguous/u,
|
|
);
|
|
});
|
|
|
|
it("checks the effective loaded image, including Ghidra's e500 default", () => {
|
|
assertPowerPcLoadedImage(image, sha256);
|
|
expect(() =>
|
|
assertPowerPcLoadedImage(
|
|
{ ...image, language_id: "PowerPC:BE:32:e500" },
|
|
sha256,
|
|
),
|
|
).toThrow();
|
|
expect(() =>
|
|
assertPowerPcLoadedImage(
|
|
{ ...image, compiler_spec_id: "macosx" },
|
|
sha256,
|
|
),
|
|
).toThrow();
|
|
expect(() =>
|
|
assertPowerPcLoadedImage(
|
|
{
|
|
...image,
|
|
source_files: [
|
|
{ original_sha256: sha256, modified_sha256: "b".repeat(64) },
|
|
],
|
|
},
|
|
sha256,
|
|
),
|
|
).toThrow();
|
|
});
|
|
|
|
it("checks exact probe bytes, immediate and conditional destination", () => {
|
|
assertPowerPcProbe(move, branch, "0x10010224");
|
|
});
|
|
|
|
it("rejects plausible nonempty but incorrect instruction observations", () => {
|
|
expect(() =>
|
|
assertPowerPcProbe({ ...move, bytes: "38641235" }, branch, "0x10010224"),
|
|
).toThrow();
|
|
expect(() =>
|
|
assertPowerPcProbe({ ...move, length: 2 }, branch, "0x10010224"),
|
|
).toThrow();
|
|
expect(() =>
|
|
assertPowerPcProbe(
|
|
{
|
|
...move,
|
|
operands: [
|
|
{ index: 2, components: [{ kind: "immediate", value: "0x1235" }] },
|
|
],
|
|
},
|
|
branch,
|
|
"0x10010224",
|
|
),
|
|
).toThrow();
|
|
expect(() =>
|
|
assertPowerPcProbe(
|
|
move,
|
|
{
|
|
...branch,
|
|
flow: { ...branch.flow, direct_destinations: ["0x10010230"] },
|
|
},
|
|
"0x10010224",
|
|
),
|
|
).toThrow();
|
|
expect(() =>
|
|
assertPowerPcProbe(
|
|
move,
|
|
{ ...branch, flow: { ...branch.flow, conditional: false } },
|
|
"0x10010224",
|
|
),
|
|
).toThrow();
|
|
});
|
|
|
|
it("checks the big-endian global word", () => {
|
|
assertPowerPcGlobal("00000007");
|
|
expect(() => assertPowerPcGlobal("07000000")).toThrow();
|
|
});
|
|
});
|