diff --git a/src/ghidra/GhidraMipsProfile.ts b/src/ghidra/GhidraMipsProfile.ts index 52debfd750..83eee37ff0 100644 --- a/src/ghidra/GhidraMipsProfile.ts +++ b/src/ghidra/GhidraMipsProfile.ts @@ -69,6 +69,20 @@ const hasLegacyMipsHeaderShape = (target: BinaryTarget): boolean => { }; /** Return a refusal for GNU ABI attributes this existing MIPS lane cannot model. */ +/** + * Every MIPS lane, including PSP, needs every ABI declaration source examined: + * headers outside the captured file may hold declarations that would refuse it. + */ +export const ghidraMipsIncompleteDeclarationsReason = ( + metadata: MipsElfMetadata, +): string | null => + (metadata.limitations?.length ?? 0) > 0 + ? [ + "MIPS ABI declaration inspection is incomplete; unsupported attributes or conflicting declaration sources may remain unknown.", + ...(metadata.limitations ?? []), + ].join(" ") + : null; + export const ghidraMipsGnuAttributesUnsupportedReason = ( metadata: MipsElfMetadata, ): string | null => { @@ -164,11 +178,8 @@ export const ghidraMipsUnsupportedReason = ( if (load.symbolRelativeCount !== 0) return "Symbol-bearing R_MIPS_REL32 is unsupported: Ghidra normalizes MIPS16 function addresses and can lose the source function-pointer mode tag. Symbol-index-zero REL32 is verified separately."; } - if ((metadata.limitations?.length ?? 0) > 0) - return [ - "MIPS ABI declaration inspection is incomplete; unsupported attributes or conflicting declaration sources may remain unknown.", - ...(metadata.limitations ?? []), - ].join(" "); + const incomplete = ghidraMipsIncompleteDeclarationsReason(metadata); + if (incomplete !== null) return incomplete; const abi = metadata.abiFlags; if (shared && abi === null && (metadata.limitations?.length ?? 0) === 0) return null; // Inspected absence leaves FP semantics unknown, never inferred. diff --git a/src/ghidra/GhidraPspProfile.ts b/src/ghidra/GhidraPspProfile.ts index 8e87073f83..e119ec9a56 100644 --- a/src/ghidra/GhidraPspProfile.ts +++ b/src/ghidra/GhidraPspProfile.ts @@ -1,6 +1,7 @@ import type { BinaryTarget } from "../domain/binaryTargetTypes.js"; import { ghidraMipsGnuAttributesUnsupportedReason, + ghidraMipsIncompleteDeclarationsReason, ghidraMipsUnsupportedReason, } from "./GhidraMipsProfile.js"; import { ghidraPowerPcUnsupportedReason } from "./GhidraPowerPcProfile.js"; @@ -33,6 +34,8 @@ export const ghidraProcessorUnsupportedReason = ( const gnuAttributesReason = ghidraMipsGnuAttributesUnsupportedReason(metadata); if (gnuAttributesReason !== null) return gnuAttributesReason; + const incomplete = ghidraMipsIncompleteDeclarationsReason(metadata); + if (incomplete !== null) return incomplete; if (metadata.elfClass !== 32 || metadata.byteOrder !== "little") return "The PSP Allegrex profile requires little-endian ELF32."; if (metadata.type !== 2) diff --git a/tests/boundary/filesystem/ghidraPspExtension.test.ts b/tests/boundary/filesystem/ghidraPspExtension.test.ts index 8872e4a7ed..33261d96e2 100644 --- a/tests/boundary/filesystem/ghidraPspExtension.test.ts +++ b/tests/boundary/filesystem/ghidraPspExtension.test.ts @@ -326,31 +326,72 @@ describe.skipIf(process.platform === "win32")( }, ); +/** + * A PSPSDK-shaped ELF32 whose PT_MIPS_ABIFLAGS record matches the PSP profile. + * Optionally declare a section-header table beyond the captured file. + */ +const pspElfBytes = ({ sectionHeadersBeyondEof = false } = {}): Buffer => { + const abiOffset = 8192; + const bytes = Buffer.alloc(abiOffset + 24); + bytes.set([0x7f, 0x45, 0x4c, 0x46, 1, 1, 1]); + bytes.writeUInt16LE(2, 16); + bytes.writeUInt16LE(8, 18); + bytes.writeUInt32LE(1, 20); + bytes.writeUInt32LE(52, 28); + bytes.writeUInt32LE(0x10a23001, 36); + bytes.writeUInt16LE(52, 40); + bytes.writeUInt16LE(32, 42); + bytes.writeUInt16LE(1, 44); + if (sectionHeadersBeyondEof) { + bytes.writeUInt32LE(bytes.length + 64, 32); // e_shoff + bytes.writeUInt16LE(40, 46); // e_shentsize + bytes.writeUInt16LE(2, 48); // e_shnum + } + bytes.writeUInt32LE(0x70000003, 52); + bytes.writeUInt32LE(abiOffset, 56); + bytes.writeUInt32LE(24, 68); + bytes.writeUInt32LE(24, 72); + // GNU readelf -x .MIPS.abiflags, not bytes generated by REA. + bytes.set( + Buffer.from("000002000101000200000000000000000100000000000000", "hex"), + abiOffset, + ); + return bytes; +}; + describe.skipIf(process.platform === "win32")( "PSP inspected ABI declaration boundary", () => { + it("refuses a PSP image whose section headers lie beyond the captured file", async () => { + const { directory, provider } = await installation(); + const path = join(directory, "psp-truncated-sections.elf"); + await writeFile(path, pspElfBytes({ sectionHeadersBeyondEof: true })); + const parsed = await parseBinaryTarget(path); + if (!parsed.ok) throw parsed.error; + if ( + parsed.value.kind !== "executable" || + parsed.value.architecture !== "mips" + ) + throw new Error("PSP ELF was not recognized as MIPS"); + expect(parsed.value.mips.abiFlags).toEqual(pspAbiFlags); + expect(parsed.value.mips.limitations).toContainEqual( + expect.stringContaining("ABI declarations in unavailable headers"), + ); + expect(provider.inspectTargetSupport(parsed.value).status).toBe( + "unsupported", + ); + const result = await provider.resolveAnalysisProfile(parsed.value); + expect(result.ok).toBe(false); + if (result.ok) throw new Error("Incomplete ABI inspection was admitted"); + expect(result.error._tag).toBe("AnalysisUnsupportedTargetError"); + expect(result.error.message).toContain( + "MIPS ABI declaration inspection is incomplete", + ); + }); + it("retains independently inspected PSP ABI bytes through the real target reader", async () => { const { directory, provider } = await installation(); - const abiOffset = 8192; - const bytes = Buffer.alloc(abiOffset + 24); - bytes.set([0x7f, 0x45, 0x4c, 0x46, 1, 1, 1]); - bytes.writeUInt16LE(2, 16); - bytes.writeUInt16LE(8, 18); - bytes.writeUInt32LE(1, 20); - bytes.writeUInt32LE(52, 28); - bytes.writeUInt32LE(0x10a23001, 36); - bytes.writeUInt16LE(52, 40); - bytes.writeUInt16LE(32, 42); - bytes.writeUInt16LE(1, 44); - bytes.writeUInt32LE(0x70000003, 52); - bytes.writeUInt32LE(abiOffset, 56); - bytes.writeUInt32LE(24, 68); - bytes.writeUInt32LE(24, 72); - // GNU readelf -x .MIPS.abiflags, not bytes generated by REA. - bytes.set( - Buffer.from("000002000101000200000000000000000100000000000000", "hex"), - abiOffset, - ); + const bytes = pspElfBytes(); const path = join(directory, "psp-with-abi.elf"); await writeFile(path, bytes); const parsed = await parseBinaryTarget(path);