* 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>
137 lines
3.9 KiB
TypeScript
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,
|
|
);
|
|
},
|
|
);
|