mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
fix(elf): admit Xtensa through canonical target contracts (#11178)
Admit ELF machine 94 as the canonical Xtensa family through Ghidra's header-selected import. Share generic-header exclusions between static target types and live CLI/MCP summaries, preserving ELF-only format constraints and other providers' decline paths. Fixes #10765. Adapted from #10774. Co-authored-by: Josh <joshuakrishna21@gmail.com> Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
12 files changed
+109
-21
No files matched your search
@@ -33,6 +33,11 @@ undefined. Installation `application.properties` has a fixed 1 MiB read allowanc
|
||||
with no runtime override. The failure retains that allowance and directs recovery
|
||||
at the selected installation metadata.
|
||||
|
||||
Xtensa ELF files (`EM_XTENSA = 94`) use the header-selected Ghidra import
|
||||
language and report the canonical `xtensa` architecture in CLI/MCP results.
|
||||
Processor extensions and calling-convention compatibility remain specific to
|
||||
the selected Ghidra language; family recognition alone does not establish them.
|
||||
|
||||
Retained process diagnostics can report termination by `SIGKILL`; the signal alone
|
||||
cannot establish an OOM kill. REA gives neutral recovery guidance and preserves the
|
||||
signal. Confirm resource causes from process output or host/container records;
|
||||
|
||||
@@ -964,7 +964,7 @@
|
||||
},
|
||||
"open_binary": {
|
||||
"input compact": "706f1985ca397688",
|
||||
"output compact": "4cbb79604f9b9e14"
|
||||
"output compact": "52860f8f8ed909d0"
|
||||
},
|
||||
"open_bundle_resources": {
|
||||
"input compact": "18d266261424a97c",
|
||||
|
||||
@@ -23,6 +23,7 @@ describe("executable header parsing", () => {
|
||||
[elf(1, 1, 62), "elf", "x86_64"],
|
||||
[elf(2, 2, 183), "elf", "arm64"],
|
||||
[elf(1, 1, 164), "elf", "hexagon"],
|
||||
[elf(1, 1, 94), "elf", "xtensa"],
|
||||
[thinMach(0xfeedfacf, 0x0100000c), "mach-o", "arm64"],
|
||||
[thinMach(0xcefaedfe, 0x01000007), "mach-o", "x86_64"],
|
||||
[thinMach(0xcefaedfe, 0x0200000c), "mach-o", "arm64_32"],
|
||||
|
||||
@@ -70,6 +70,11 @@ export type ExecutableMetadata =
|
||||
readonly architecture: "hexagon";
|
||||
readonly availableArchitectures: readonly "hexagon"[];
|
||||
}
|
||||
| {
|
||||
readonly format: "elf";
|
||||
readonly architecture: "xtensa";
|
||||
readonly availableArchitectures: readonly "xtensa"[];
|
||||
}
|
||||
| {
|
||||
readonly format: "mach-o";
|
||||
readonly architecture: "arm64_32";
|
||||
@@ -534,6 +539,12 @@ const parseElf = (
|
||||
architecture,
|
||||
availableArchitectures: [architecture],
|
||||
});
|
||||
if (architecture === "xtensa")
|
||||
return ok({
|
||||
format: "elf",
|
||||
architecture,
|
||||
availableArchitectures: [architecture],
|
||||
});
|
||||
return ok({
|
||||
format: "elf",
|
||||
architecture,
|
||||
|
||||
@@ -14,6 +14,7 @@ export const BINARY_ARCHITECTURES = [
|
||||
"powerpc",
|
||||
"m68k",
|
||||
"hexagon",
|
||||
"xtensa",
|
||||
] as const;
|
||||
export type BinaryArchitecture = (typeof BINARY_ARCHITECTURES)[number];
|
||||
|
||||
@@ -62,10 +63,19 @@ export const isElfMetadataArchitecture = (
|
||||
): architecture is ElfMetadataArchitecture =>
|
||||
(ELF_METADATA_ARCHITECTURES as readonly string[]).includes(architecture);
|
||||
|
||||
/** Recognized family with no ELF metadata member; the header alone is the fact. */
|
||||
/** Families that require a specialized target variant instead of generic headers. */
|
||||
export const HEADER_ARCHITECTURE_EXCLUSIONS = [
|
||||
...ELF_METADATA_ARCHITECTURES,
|
||||
...RAW_ONLY_ARCHITECTURES,
|
||||
"hexagon",
|
||||
"xtensa",
|
||||
"arm64_32",
|
||||
] as const satisfies readonly BinaryArchitecture[];
|
||||
|
||||
/** Recognized family with no specialized target member; the header alone is the fact. */
|
||||
export type HeaderArchitecture = Exclude<
|
||||
BinaryArchitecture,
|
||||
ElfMetadataArchitecture | RawOnlyArchitecture | "hexagon" | "arm64_32"
|
||||
(typeof HEADER_ARCHITECTURE_EXCLUSIONS)[number]
|
||||
>;
|
||||
|
||||
/** Raw ELF facts; family recognition does not establish ISA or ABI support. */
|
||||
@@ -212,6 +222,14 @@ export type BinaryTarget =
|
||||
readonly executableRole?: never;
|
||||
readonly managed?: never;
|
||||
})
|
||||
| (BinaryTargetIdentity & {
|
||||
readonly kind: "executable";
|
||||
readonly format: "elf";
|
||||
readonly architecture: "xtensa";
|
||||
readonly availableArchitectures: readonly "xtensa"[];
|
||||
readonly executableRole?: never;
|
||||
readonly managed?: never;
|
||||
})
|
||||
| (BinaryTargetIdentity & {
|
||||
readonly kind: "executable";
|
||||
readonly format: "mach-o";
|
||||
|
||||
@@ -130,6 +130,8 @@ export const elfArchitecture = (
|
||||
return "arm";
|
||||
case 164: // EM_HEXAGON: Qualcomm Hexagon DSP (QDSP6).
|
||||
return "hexagon";
|
||||
case 94: // EM_XTENSA: Tensilica Xtensa.
|
||||
return "xtensa";
|
||||
case 183:
|
||||
return "arm64";
|
||||
case 243: // EM_RISCV: XLEN, byte order and e_flags remain separate ELF facts.
|
||||
|
||||
@@ -2,7 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import {
|
||||
BINARY_ARCHITECTURES,
|
||||
ELF_METADATA_ARCHITECTURES,
|
||||
HEADER_ARCHITECTURE_EXCLUSIONS,
|
||||
RAW_ONLY_ARCHITECTURES,
|
||||
} from "./binaryTargetTypes.js";
|
||||
import { digestSchema } from "./digests.js";
|
||||
@@ -13,12 +13,9 @@ import {
|
||||
import { ZIP_PACKAGE_FORMATS } from "./zipPackageFormat.js";
|
||||
|
||||
const architectureSchema = z.enum(BINARY_ARCHITECTURES);
|
||||
const headerArchitectureSchema = architectureSchema.exclude([
|
||||
...ELF_METADATA_ARCHITECTURES,
|
||||
...RAW_ONLY_ARCHITECTURES,
|
||||
"hexagon",
|
||||
"arm64_32",
|
||||
]);
|
||||
const headerArchitectureSchema = architectureSchema.exclude(
|
||||
HEADER_ARCHITECTURE_EXCLUSIONS,
|
||||
);
|
||||
const identity = { path: z.string(), sha256: digestSchema };
|
||||
const executable = { ...identity, kind: z.literal("executable") };
|
||||
|
||||
|
||||
@@ -617,6 +617,35 @@ describe("Ghidra provider", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Ghidra Xtensa ELF support", () => {
|
||||
it("admits parsed Xtensa targets through profile selection", async () => {
|
||||
const parsed = parseExecutableHeader(elf(1, 1, 94), "x64");
|
||||
if (!parsed.ok) throw new Error("Expected valid Xtensa ELF target");
|
||||
const target: BinaryTarget = {
|
||||
path: "/tmp/esp32s3.elf",
|
||||
sha256: "a".repeat(64),
|
||||
kind: "executable",
|
||||
...parsed.value,
|
||||
};
|
||||
const ghidra = provider();
|
||||
expect(ghidra.inspectTargetSupport(target)).toMatchObject({
|
||||
status: "supported",
|
||||
});
|
||||
const resolved = await ghidra.resolveAnalysisProfile(target);
|
||||
expect(resolved.ok && resolved.value).toMatchObject({
|
||||
profile: {
|
||||
parameters: {
|
||||
target_format: "elf",
|
||||
architecture: "xtensa",
|
||||
loader: "auto-from-header",
|
||||
language_id: "auto-from-header",
|
||||
compiler_spec_id: "auto-default",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Ghidra raw-binary profile", () => {
|
||||
it("admits raw 6502 on Windows and binds its 16-bit language without mutation authority", async () => {
|
||||
const ghidra = provider(installationHost("win32"));
|
||||
@@ -749,6 +778,7 @@ describe("Ghidra Mach-O slice support", () => {
|
||||
"riscv",
|
||||
"powerpc",
|
||||
"hexagon",
|
||||
"xtensa",
|
||||
"6502",
|
||||
"m68k",
|
||||
]),
|
||||
|
||||
@@ -107,6 +107,7 @@ const SUPPORTED_ARCHITECTURES = new Set<BinaryArchitecture>([
|
||||
"powerpc",
|
||||
"m68k",
|
||||
"hexagon",
|
||||
"xtensa",
|
||||
]);
|
||||
|
||||
/** Names every admitted family, so the refusal cannot drift from the set. */
|
||||
|
||||
@@ -207,6 +207,7 @@ const resolveHopperAnalysisProfileInScope = async (
|
||||
export type HopperDeclinedArchitecture =
|
||||
| ElfMetadataArchitecture
|
||||
| "hexagon"
|
||||
| "xtensa"
|
||||
| "arm64_32";
|
||||
|
||||
export const isHopperDeclinedArchitecture = (
|
||||
@@ -214,6 +215,7 @@ export const isHopperDeclinedArchitecture = (
|
||||
): architecture is HopperDeclinedArchitecture =>
|
||||
isElfMetadataArchitecture(architecture) ||
|
||||
architecture === "hexagon" ||
|
||||
architecture === "xtensa" ||
|
||||
architecture === "arm64_32";
|
||||
|
||||
/**
|
||||
|
||||
@@ -194,6 +194,27 @@ describe("Hopper provider target support", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Hopper Xtensa support", () => {
|
||||
it("declines Xtensa with the Ghidra-directed reason", () => {
|
||||
const config = parseConfig({});
|
||||
if (!config.ok) throw new Error("expected valid configuration");
|
||||
const provider = new HopperProvider(config.value, silentLogger, {});
|
||||
const target = {
|
||||
path: "/tmp/esp32s3.elf",
|
||||
sha256: "a".repeat(64),
|
||||
kind: "executable",
|
||||
format: "elf",
|
||||
architecture: "xtensa",
|
||||
availableArchitectures: ["xtensa"],
|
||||
} as const satisfies BinaryTarget;
|
||||
expect(provider.inspectTargetSupport(target)).toMatchObject({
|
||||
status: "unsupported",
|
||||
code: "architecture_unsupported",
|
||||
reason: hopperDeclinedArchitectureReason("xtensa"),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Hopper relationship coverage", () => {
|
||||
it.each([
|
||||
["procedure_callers", ["callers"]],
|
||||
|
||||
@@ -52,6 +52,12 @@ const targets: readonly BinaryTarget[] = [
|
||||
architecture: "hexagon",
|
||||
availableArchitectures: ["hexagon"],
|
||||
},
|
||||
{
|
||||
...executable,
|
||||
format: "elf",
|
||||
architecture: "xtensa",
|
||||
availableArchitectures: ["xtensa"],
|
||||
},
|
||||
{
|
||||
...executable,
|
||||
format: "mach-o",
|
||||
@@ -111,6 +117,9 @@ const targets: readonly BinaryTarget[] = [
|
||||
})),
|
||||
];
|
||||
const impossible = [
|
||||
...(["pe", "mach-o", "mach-o-universal", "dos-mz", "dos-com"] as const).map(
|
||||
(format) => ({ kind: "executable", format, architecture: "xtensa" }),
|
||||
),
|
||||
{ kind: "database", format: "elf", architecture: "x86" },
|
||||
{ kind: "executable", format: "zip", architecture: "arm64" },
|
||||
{ kind: "archive", format: "zip", architecture: "arm64" },
|
||||
@@ -237,16 +246,7 @@ it.each(["draft-2020-12", "draft-07"] as const)(
|
||||
toolContract("binary_session"),
|
||||
)["~standard"].jsonSchema.output({ target });
|
||||
const validate = new AjvJsonSchemaValidator().getValidator(advertised);
|
||||
// One representative per distinct projection shape; the complete target matrix is above.
|
||||
for (const format of [
|
||||
"elf",
|
||||
"raw-binary",
|
||||
"gamecube-dol",
|
||||
"zip",
|
||||
] as const) {
|
||||
const selected = targets.find((candidate) => candidate.format === format);
|
||||
if (selected === undefined)
|
||||
throw new Error(`Missing canonical fixture: ${format}`);
|
||||
for (const selected of targets) {
|
||||
const value = {
|
||||
result: { ...sessionFacts, ...binaryTargetSummary(selected) },
|
||||
};
|
||||
@@ -255,7 +255,7 @@ it.each(["draft-2020-12", "draft-07"] as const)(
|
||||
);
|
||||
expect(validate(value).valid).toBe(true);
|
||||
}
|
||||
for (const classification of impossible.slice(0, 4)) {
|
||||
for (const classification of impossible) {
|
||||
const value = {
|
||||
result: { ...sessionFacts, ...identity, ...classification },
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user