mirror of
https://github.com/morluto/rea.git
synced 2026-11-14 18:52:48 +00:00
fix(apple): match IPS ARM32 joins by CPU subtype
Fixes #10201 Squashed from #10270 and rebased onto main; the ARM32 branch runs before the ARM64 family matching from #9971. Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
1 parent
d80339df1d
commit
ff64ed5142
3 files changed
+152
No files matched your search
@@ -38,6 +38,32 @@ export const machoHostSubtypeRank = (
|
||||
export const machoCpuSubtypeBase = (cpuSubtype: number): number =>
|
||||
cpuSubtype & ~CPU_SUBTYPE_CAPABILITY_MASK;
|
||||
|
||||
// XNU osfmk/mach/machine.h CPU_SUBTYPE_ARM_* identities. Keep the generic
|
||||
// display fallback separate: an unlisted subtype does not establish agreement.
|
||||
const ARM32_SUBTYPES: ReadonlyMap<number, string> = new Map([
|
||||
[0, "arm"],
|
||||
[5, "armv4t"],
|
||||
[6, "armv6"],
|
||||
[7, "armv5tej"],
|
||||
[8, "xscale"],
|
||||
[9, "armv7"],
|
||||
[10, "armv7f"],
|
||||
[11, "armv7s"],
|
||||
[12, "armv7k"],
|
||||
[13, "armv8"],
|
||||
[14, "armv6m"],
|
||||
[15, "armv7m"],
|
||||
[16, "armv7em"],
|
||||
[17, "armv8m.main"],
|
||||
[18, "armv8m.base"],
|
||||
[19, "armv8.1m.main"],
|
||||
]);
|
||||
|
||||
/** Known ARM32 identity without changing the family name used for display. */
|
||||
export const machoKnownArm32Subtype = (
|
||||
cpuSubtype: number,
|
||||
): string | undefined => ARM32_SUBTYPES.get(machoCpuSubtypeBase(cpuSubtype));
|
||||
|
||||
/** Known ARM64 subtype identity, distinct from the family display fallback. */
|
||||
export const machoKnownArm64Subtype = (
|
||||
cpuSubtype: number,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { FieldReader } from "./AppleIpsFieldReader.js";
|
||||
import {
|
||||
machoArchitectureFamily,
|
||||
machoArchitectureName,
|
||||
machoKnownArm32Subtype,
|
||||
machoKnownArm64Subtype,
|
||||
machoKnownX86_64Subtype,
|
||||
} from "../../domain/apple/machoCpuIdentity.js";
|
||||
@@ -204,6 +205,15 @@ const architectureRelation = (
|
||||
): "match" | "mismatch" | "unknown" => {
|
||||
const left = REPORTED_FAMILIES.get(comparableArch(recorded));
|
||||
if (left === undefined) return "unknown";
|
||||
if (slice.cpuType === 12) {
|
||||
const right = machoKnownArm32Subtype(slice.cpuSubtype);
|
||||
const arm32 = ["arm", "armv7", "armv7s"].includes(left);
|
||||
if (!arm32) return "mismatch";
|
||||
if (right === undefined) return "unknown";
|
||||
// A generic ARM report keeps its family meaning; explicit subtypes must
|
||||
// agree with byte-header identity rather than the shared "arm" display.
|
||||
return left === "arm" || left === right ? "match" : "mismatch";
|
||||
}
|
||||
const leftFamily = machoArchitectureFamily(left);
|
||||
const exact = left === leftFamily ? undefined : left;
|
||||
if (
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { expect } from "vitest";
|
||||
import {
|
||||
fatImage,
|
||||
machoImage,
|
||||
} from "../../src/artifacts/apple/MachoImage.fixture.js";
|
||||
import { toolContract } from "../../src/contracts/toolContracts.js";
|
||||
import { appleIpsBytes } from "../fixtures/binaryDiagnostics/appleIps.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, runFileCli } from "../support/cli/cliFixture.js";
|
||||
|
||||
/** Actual 32-bit framing; the displayed family alone cannot identify its subtype. */
|
||||
const armImage = (subtype: number, withUuid = true): Uint8Array => {
|
||||
const text = Buffer.alloc(56);
|
||||
text.writeUInt32LE(1, 0); // LC_SEGMENT, not LC_SEGMENT_64.
|
||||
text.writeUInt32LE(text.length, 4);
|
||||
text.write("__TEXT", 8);
|
||||
text.writeUInt32LE(0x1000, 24);
|
||||
text.writeUInt32LE(0x4000, 28);
|
||||
const uuid = Buffer.alloc(24, 0xaa);
|
||||
uuid.writeUInt32LE(0x1b, 0);
|
||||
uuid.writeUInt32LE(uuid.length, 4);
|
||||
return machoImage({
|
||||
cpu: { type: 12, subtype },
|
||||
wide: false,
|
||||
commands: withUuid ? [text, uuid] : [text],
|
||||
});
|
||||
};
|
||||
|
||||
cliTest.for([
|
||||
{ arch: "armv7", subtype: 9, outcome: "joined" },
|
||||
{ arch: "armv7s", subtype: 11, outcome: "joined" },
|
||||
{ arch: "armv7", subtype: 0x80000009, outcome: "joined" },
|
||||
{ arch: "armv7s", subtype: 0x8000000b, outcome: "joined" },
|
||||
{ arch: "arm", subtype: 0, outcome: "joined" },
|
||||
{ arch: "armv7", subtype: 11, outcome: "mismatch" },
|
||||
{ arch: "armv7s", subtype: 9, outcome: "mismatch" },
|
||||
{ arch: "arm", subtype: 9, outcome: "joined" },
|
||||
{ arch: "arm", subtype: 6, outcome: "joined" },
|
||||
{ arch: "arm", subtype: 99, outcome: "unknown" },
|
||||
{ arch: "armv7", subtype: 99, outcome: "unknown" },
|
||||
{ arch: "x86_64", subtype: 99, outcome: "mismatch" },
|
||||
])(
|
||||
"joins IPS $arch with ARM subtype $subtype only on established identity ($outcome)",
|
||||
async ({ arch, subtype, outcome }, { cli }) => {
|
||||
const root = await createTestTempDirectory("rea-ips-arm-identity-");
|
||||
const path = join(root, "report.ips");
|
||||
const image = join(root, "image.bin");
|
||||
await writeFile(path, appleIpsBytes({ arch, base: 0x8000 }));
|
||||
await writeFile(image, armImage(subtype));
|
||||
const result = await runFileCli(cli, {
|
||||
arguments: ["inspect-apple-ips", path, "--join-path", image, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
const report = toolContract("inspect_apple_ips").outputSchema.parse(
|
||||
result.json,
|
||||
).normalized_result;
|
||||
expect(report.images[0]?.arch).toBe(arch);
|
||||
expect(report.join?.status).toBe("selected");
|
||||
if (report.join?.status !== "selected") throw new Error("Missing join");
|
||||
expect(report.join.slices[0]).toMatchObject({
|
||||
arch: "arm",
|
||||
cpu_type: "0xc",
|
||||
cpu_subtype: `0x${subtype.toString(16)}`,
|
||||
});
|
||||
expect(report.join.image_matches).toHaveLength(
|
||||
outcome === "joined" ? 1 : 0,
|
||||
);
|
||||
expect(report.join.frames[0]).toMatchObject(
|
||||
outcome === "joined"
|
||||
? {
|
||||
status: "joined",
|
||||
static_address: { decimal: "4920", hex: "0x1338" },
|
||||
}
|
||||
: { status: "unjoined", static_address: null },
|
||||
);
|
||||
if (outcome !== "joined")
|
||||
expect(report.join.limitations.join(" ")).toContain(
|
||||
outcome === "unknown" ? "architecture is unknown" : "does not match",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
cliTest.for([11, 99])(
|
||||
"keeps UUID-less ARM subtype %s relevant unless its identity rules out a match",
|
||||
async (subtype, { cli }) => {
|
||||
const root = await createTestTempDirectory("rea-ips-arm-uuid-");
|
||||
const path = join(root, "report.ips");
|
||||
const image = join(root, "universal.bin");
|
||||
await writeFile(path, appleIpsBytes({ arch: "armv7", base: 0x8000 }));
|
||||
await writeFile(
|
||||
image,
|
||||
fatImage([
|
||||
{ cpu: { type: 12, subtype: 9 }, bytes: armImage(9) },
|
||||
{ cpu: { type: 12, subtype }, bytes: armImage(subtype, false) },
|
||||
]),
|
||||
);
|
||||
const result = await runFileCli(cli, {
|
||||
arguments: ["inspect-apple-ips", path, "--join-path", image, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
const report = toolContract("inspect_apple_ips").outputSchema.parse(
|
||||
result.json,
|
||||
).normalized_result;
|
||||
if (report.join?.status !== "selected") throw new Error("Missing join");
|
||||
expect(report.join.slices).toHaveLength(2);
|
||||
expect(report.join.slices[1]?.uuid).toBeNull();
|
||||
expect(report.join.image_matches).toHaveLength(subtype === 11 ? 1 : 0);
|
||||
expect(report.join.frames[0]?.status).toBe(
|
||||
subtype === 11 ? "joined" : "unjoined",
|
||||
);
|
||||
if (subtype === 99)
|
||||
expect(report.join.limitations.join(" ")).toContain("UUID is unknown");
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user