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:
authored and GitHub committed 2026-11-15 22:09:41 +08:00
1 parent 104b1a35b4
commit 1995ab7fce
12 files changed
+109 -21

No files matched your search

+5
View File
@@ -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",
+1
View File
@@ -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"],
+11
View File
@@ -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,
+20 -2
View File
@@ -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";
+2
View File
@@ -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.
+4 -7
View File
@@ -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",
]),
+1
View File
@@ -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. */
+2
View File
@@ -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";
/**
+21
View File
@@ -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 },
};