Files
rea/tests/boundary/cli/rawBinaryCli.test.ts
zhugehyukandClaude Opus 5.5 d62d3082c2 feat(ghidra): support raw big-endian m68k analysis (#4018)
* feat(ghidra): support raw big-endian m68k analysis

Admit `m68k` for headerless raw-binary targets. Admission requires a
2-byte aligned entry and a mapping within 0x0..0xffffffff, and Ghidra
imports with BinaryLoader using 68000:BE:32:default, the default compiler
specification, and the `ram` default address space.

`6502` and `m68k` now share one RAW_ONLY_ARCHITECTURES owner, so detected
targets, Evidence subjects, snapshots, and lifecycle output contracts
exclude both instead of repeating a 6502 literal.

Ghidra's default 68000 language decodes the 68040 superset; separate
68020/68030/CPU32/ColdFire variants, Amiga/Atari containers, and reset
vector discovery remain unsupported. Windows Ghidra P0 still admits raw
6502 only.

Verified with Ghidra 12.1.4 on macOS against an 8-byte self-built fixture
(nop; nop; moveq #1,d0; rts at 0x1000): load image verified in `ram`,
entry decompiles to `return 1`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(ghidra): verify raw m68k analysis with real Ghidra

Add npm run verify:ghidra:m68k. The verifier loads a source-owned 22-byte
68000 fixture (annotated hex, no assembler) at 0x1000 and checks, through
the compiled CLI and stdio MCP server: the 68000:BE:32:default BinaryLoader
import, load-image parity, exact mapped bytes, both functions, the bsr.b
call reference, decompilation by address and file offset, rejection of a
misaligned entry and of a mapping past 0xffffffff without replacing the
active session, unchanged source bytes, and runtime cleanup.

Run with Ghidra 12.1.4 and JDK 21 on macOS arm64. Linux has not been
exercised; Windows Ghidra P0 does not admit m68k.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 23:57:34 +08:00

137 lines
3.9 KiB
TypeScript

import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
cliTest(
"rejects a headerless image until architecture and entry are supplied",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-raw-cli-");
const path = join(root, "ecu.bin");
await writeFile(path, Buffer.from("1eff2fe1", "hex"));
const before = await cli.run({
arguments: ["inspect", path, "--json"],
environment: {
HOME: root,
XDG_CONFIG_HOME: root,
XDG_CACHE_HOME: root,
},
});
expect(before.exitCode).not.toBe(0);
expect(JSON.stringify(before.json)).not.toContain("raw-binary");
const missing = await cli.run({
arguments: ["inspect", path, "--target-format", "raw-binary", "--json"],
environment: {
HOME: root,
XDG_CONFIG_HOME: root,
XDG_CACHE_HOME: root,
},
});
expect(missing.exitCode).toBe(1);
expect(missing.json).toMatchObject({
code: "invalid_request",
details: { reason: expect.stringContaining("architecture") },
});
},
);
cliTest(
"rejects a 6502 image crossing the 16-bit boundary before provider startup",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-6502-cli-overflow-");
const path = join(root, "image.bin");
await writeFile(path, Buffer.from("a92a60", "hex"));
const result = await cli.run({
arguments: [
"inspect",
path,
"--target-format",
"raw-binary",
"--architecture",
"6502",
"--entry-point",
"0xffff",
"--provider",
"ghidra",
"--json",
],
environment: { HOME: root, XDG_CONFIG_HOME: root, XDG_CACHE_HOME: root },
});
expect(result.exitCode).toBe(1);
expect(result.json).toMatchObject({
code: "invalid_request",
details: { reason: expect.stringContaining("6502 address width") },
});
},
);
cliTest.for([
["arm64", "c0035fd6", "0x8000001", "must be 4-byte aligned"],
["m68k", "4e75", "0x1001", "must be 2-byte aligned"],
] as const)(
"rejects an unaligned %s entry before provider startup",
async ([architecture, bytes, entryPoint, reason], { cli }) => {
const root = await createTestTempDirectory("rea-raw-cli-alignment-");
const path = join(root, "image.bin");
await writeFile(path, Buffer.from(bytes, "hex"));
const result = await cli.run({
arguments: [
"inspect",
path,
"--target-format",
"raw-binary",
"--architecture",
architecture,
"--entry-point",
entryPoint,
"--provider",
"ghidra",
"--json",
],
environment: { HOME: root, XDG_CONFIG_HOME: root, XDG_CACHE_HOME: root },
});
expect(result.exitCode).toBe(1);
expect(result.json).toMatchObject({
code: "invalid_request",
details: { reason: expect.stringContaining(reason) },
});
},
);
cliTest(
"stops Hopper from claiming a caller-supplied raw-binary image",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-raw-cli-hopper-");
const path = join(root, "ecu.bin");
await writeFile(path, Buffer.from("1eff2fe1", "hex"));
const result = await cli.run({
arguments: [
"inspect",
path,
"--target-format",
"raw-binary",
"--architecture",
"arm",
"--entry-point",
"0x8000000",
"--provider",
"hopper",
"--json",
],
environment: {
HOME: root,
XDG_CONFIG_HOME: root,
XDG_CACHE_HOME: root,
},
});
expect(result.exitCode).toBe(1);
expect(JSON.stringify(result.json)).toMatch(/Hopper|ghidra|Ghidra|hopper/i);
expect(JSON.stringify(result.json)).not.toMatch(
/unsupported binary format/i,
);
},
);