mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(native): unify legacy Mach-O platform interpretation
This commit is contained in:
1 parent
5f4bd6ba34
commit
2b8e388bbe
5 files changed
+109
-84
No files matched your search
@@ -108,6 +108,7 @@ it.each([
|
||||
{ command: LC.VERSION_MIN_IPHONEOS, cpu: CPU.i386, platform: 7 },
|
||||
{ command: LC.VERSION_MIN_IPHONEOS, cpu: CPU.x86_64, platform: 7 },
|
||||
{ command: LC.VERSION_MIN_IPHONEOS, cpu: CPU.arm64, platform: 2 },
|
||||
{ command: LC.VERSION_MIN_TVOS, cpu: CPU.i386, platform: 3 },
|
||||
{ command: LC.VERSION_MIN_TVOS, cpu: CPU.x86_64, platform: 8 },
|
||||
{ command: LC.VERSION_MIN_TVOS, cpu: CPU.arm64, platform: 3 },
|
||||
{ command: LC.VERSION_MIN_WATCHOS, cpu: CPU.i386, platform: 9 },
|
||||
|
||||
@@ -4,6 +4,10 @@ import type {
|
||||
MachoSlice,
|
||||
} from "../../domain/apple/dylibResolution.js";
|
||||
import { machoCpuSubtypeBase } from "../../domain/apple/machoCpuIdentity.js";
|
||||
import {
|
||||
legacyMachoPlatform,
|
||||
type MachoVersionMinPlatform,
|
||||
} from "../../domain/apple/machoPlatform.js";
|
||||
import {
|
||||
machoLoadCommandAlignment,
|
||||
classifyFatContainerIdentity,
|
||||
@@ -53,53 +57,18 @@ const LC_LOAD_UPWARD_DYLIB = 0x80000023;
|
||||
const LC_RPATH = 0x8000001c;
|
||||
const LC_DYLD_ENVIRONMENT = 0x27;
|
||||
const LC_CODE_SIGNATURE = 0x1d;
|
||||
const LC_VERSION_MIN_MACOSX = 0x24;
|
||||
const LC_VERSION_MIN_IPHONEOS = 0x25;
|
||||
const LC_VERSION_MIN_TVOS = 0x2f;
|
||||
const LC_VERSION_MIN_WATCHOS = 0x30;
|
||||
const LC_BUILD_VERSION = 0x32;
|
||||
|
||||
// `mach-o/loader.h` `PLATFORM_*` numbers observed via Apple linker output.
|
||||
const PLATFORM_MACOS = 1;
|
||||
const PLATFORM_IOS = 2;
|
||||
const PLATFORM_TVOS = 3;
|
||||
const PLATFORM_WATCHOS = 4;
|
||||
const PLATFORM_IOSSIMULATOR = 7;
|
||||
const PLATFORM_TVOSSIMULATOR = 8;
|
||||
const PLATFORM_WATCHOSSIMULATOR = 9;
|
||||
|
||||
const CPU_TYPE_I386 = 7;
|
||||
const CPU_TYPE_X86_64 = 0x01000007;
|
||||
const CPU_TYPE_ARM64 = 0x0100000c;
|
||||
|
||||
/**
|
||||
* Legacy `LC_VERSION_MIN_*` commands predate explicit simulator platform
|
||||
* IDs. Per Apple's dyld rules the image CPU type distinguishes simulator
|
||||
* from device: Intel images are simulator, ARM images are device
|
||||
* (tvOS simulator applies to x86_64 only). `LC_BUILD_VERSION` platform
|
||||
* numbers are preserved verbatim; unknown future numbers stay unknown
|
||||
* downstream, never mapped to nearest.
|
||||
*/
|
||||
const legacyVersionMinPlatform = (
|
||||
command: number,
|
||||
cpuType: number,
|
||||
): number | undefined => {
|
||||
const isIntel = cpuType === CPU_TYPE_I386 || cpuType === CPU_TYPE_X86_64;
|
||||
switch (command) {
|
||||
case LC_VERSION_MIN_MACOSX:
|
||||
return PLATFORM_MACOS;
|
||||
case LC_VERSION_MIN_IPHONEOS:
|
||||
return isIntel ? PLATFORM_IOSSIMULATOR : PLATFORM_IOS;
|
||||
case LC_VERSION_MIN_TVOS:
|
||||
return cpuType === CPU_TYPE_X86_64
|
||||
? PLATFORM_TVOSSIMULATOR
|
||||
: PLATFORM_TVOS;
|
||||
case LC_VERSION_MIN_WATCHOS:
|
||||
return isIntel ? PLATFORM_WATCHOSSIMULATOR : PLATFORM_WATCHOS;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
/** loader.h command codes and their device platform identities. */
|
||||
const VERSION_MIN_PLATFORMS: ReadonlyMap<number, MachoVersionMinPlatform> =
|
||||
new Map([
|
||||
[0x24, 1], // LC_VERSION_MIN_MACOSX
|
||||
[0x25, 2], // LC_VERSION_MIN_IPHONEOS
|
||||
[0x2f, 3], // LC_VERSION_MIN_TVOS
|
||||
[0x30, 4], // LC_VERSION_MIN_WATCHOS
|
||||
]);
|
||||
|
||||
/** `mach-o/loader.h` `dylib_use_command`: `nameoff == 28` and this marker. */
|
||||
const DYLIB_USE_MARKER = 0x1a741800;
|
||||
@@ -691,14 +660,14 @@ const decodeCommand = (
|
||||
slice.platforms.push(platform);
|
||||
return;
|
||||
}
|
||||
const legacyPlatform = legacyVersionMinPlatform(command, slice.cpu_type);
|
||||
const legacyPlatform = VERSION_MIN_PLATFORMS.get(command);
|
||||
if (legacyPlatform !== undefined) {
|
||||
if (body.byteLength < 16)
|
||||
throw new MachoFormatIssue(
|
||||
"malformed",
|
||||
`load command ${index} is too short for a version-min command`,
|
||||
);
|
||||
slice.platforms.push(legacyPlatform);
|
||||
slice.platforms.push(legacyMachoPlatform(legacyPlatform, slice.cpu_type));
|
||||
return;
|
||||
}
|
||||
if (command === LC_LAZY_LOAD_DYLIB_INFO)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/** Device platform encoded by a legacy LC_VERSION_MIN_* command. */
|
||||
export type MachoVersionMinPlatform = 1 | 2 | 3 | 4;
|
||||
|
||||
/**
|
||||
* dyld derives legacy simulator platforms from the CPU type. tvOS uses
|
||||
* x86_64 only; iOS and watchOS also recognize i386. Explicit LC_BUILD_VERSION
|
||||
* platforms do not use this rule.
|
||||
*/
|
||||
export const legacyMachoPlatform = (
|
||||
platform: MachoVersionMinPlatform,
|
||||
cpuType: number | null,
|
||||
): number => {
|
||||
if (platform === 3) return cpuType === 0x01000007 ? 8 : 3;
|
||||
if (cpuType === 7 || cpuType === 0x01000007) {
|
||||
if (platform === 2) return 7;
|
||||
if (platform === 4) return 9;
|
||||
}
|
||||
return platform;
|
||||
};
|
||||
+22
-19
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
legacyMachoPlatform,
|
||||
type MachoVersionMinPlatform,
|
||||
} from "../../domain/apple/machoPlatform.js";
|
||||
|
||||
interface ParsedSection {
|
||||
readonly segment: string;
|
||||
readonly name: string;
|
||||
@@ -70,6 +75,14 @@ const DYLIB_FIELDS = [
|
||||
const DYLIB_ENCODING_FIELDS = ["time stamp", "options"];
|
||||
const EMPTY_VALUE_FIELDS = new Set(["options", "segname", "sectname"]);
|
||||
|
||||
const VERSION_MIN_PLATFORMS: ReadonlyMap<string, MachoVersionMinPlatform> =
|
||||
new Map([
|
||||
["LC_VERSION_MIN_MACOSX", 1],
|
||||
["LC_VERSION_MIN_IPHONEOS", 2],
|
||||
["LC_VERSION_MIN_TVOS", 3],
|
||||
["LC_VERSION_MIN_WATCHOS", 4],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Non-verbose otool prints LC_BUILD_VERSION platform and tool codes as
|
||||
* numbers. Name the codes declared in the SDK's <mach-o/loader.h>, without
|
||||
@@ -408,8 +421,16 @@ const collectBuild = (
|
||||
}
|
||||
if (kind.startsWith("LC_VERSION_MIN_")) {
|
||||
const platform = kind.slice("LC_VERSION_MIN_".length) || null;
|
||||
const devicePlatform = VERSION_MIN_PLATFORMS.get(kind);
|
||||
const derivedPlatform =
|
||||
devicePlatform === undefined
|
||||
? undefined
|
||||
: legacyMachoPlatform(devicePlatform, state.cpuType);
|
||||
state.builds.push({
|
||||
platform: legacySimulatorPlatform(platform, state.cpuType),
|
||||
platform:
|
||||
derivedPlatform === undefined || derivedPlatform === devicePlatform
|
||||
? platform
|
||||
: buildCodeName(BUILD_PLATFORM_NAMES, String(derivedPlatform)),
|
||||
minimum_os: stringField(fields, "version"),
|
||||
sdk: stringField(fields, "sdk"),
|
||||
tools: [],
|
||||
@@ -417,24 +438,6 @@ const collectBuild = (
|
||||
}
|
||||
};
|
||||
|
||||
// Apple dyld interprets legacy iOS/tvOS/watchOS minimum-version commands on
|
||||
// Intel slices as simulator platforms. Newer commands encode this directly.
|
||||
const legacySimulatorPlatform = (
|
||||
platform: string | null,
|
||||
cpuType: number | null,
|
||||
): string | null => {
|
||||
if (![7, 0x01000007].includes(cpuType ?? -1)) return platform;
|
||||
return (
|
||||
(
|
||||
{
|
||||
IPHONEOS: "IOSSIMULATOR",
|
||||
TVOS: "TVOSSIMULATOR",
|
||||
WATCHOS: "WATCHOSSIMULATOR",
|
||||
} as const
|
||||
)[platform as "IPHONEOS" | "TVOS" | "WATCHOS"] ?? platform
|
||||
);
|
||||
};
|
||||
|
||||
const collectDependency = (
|
||||
kind: string,
|
||||
fields: Readonly<Record<string, string | number | null>>,
|
||||
|
||||
@@ -269,33 +269,66 @@ Section
|
||||
});
|
||||
|
||||
it.each([
|
||||
["LC_VERSION_MIN_IPHONEOS", "IOSSIMULATOR"],
|
||||
["LC_VERSION_MIN_TVOS", "TVOSSIMULATOR"],
|
||||
["LC_VERSION_MIN_WATCHOS", "WATCHOSSIMULATOR"],
|
||||
])("classifies legacy Intel simulator command %s", (command, platform) => {
|
||||
["LC_VERSION_MIN_IPHONEOS", 7, "IOSSIMULATOR"],
|
||||
["LC_VERSION_MIN_IPHONEOS", 16777223, "IOSSIMULATOR"],
|
||||
["LC_VERSION_MIN_IPHONEOS", 16777228, "IPHONEOS"],
|
||||
["LC_VERSION_MIN_TVOS", 7, "TVOS"],
|
||||
["LC_VERSION_MIN_TVOS", 16777223, "TVOSSIMULATOR"],
|
||||
["LC_VERSION_MIN_TVOS", 16777228, "TVOS"],
|
||||
["LC_VERSION_MIN_WATCHOS", 7, "WATCHOSSIMULATOR"],
|
||||
["LC_VERSION_MIN_WATCHOS", 16777223, "WATCHOSSIMULATOR"],
|
||||
["LC_VERSION_MIN_WATCHOS", 16777228, "WATCHOS"],
|
||||
["LC_VERSION_MIN_MACOSX", 7, "MACOSX"],
|
||||
["LC_VERSION_MIN_MACOSX", 16777223, "MACOSX"],
|
||||
["LC_VERSION_MIN_MACOSX", 16777228, "MACOSX"],
|
||||
])(
|
||||
"classifies legacy command %s on CPU %s as %s",
|
||||
(command, cpuType, platform) => {
|
||||
const text = [
|
||||
"Mach header",
|
||||
" magic cputype cpusubtype caps filetype ncmds sizeofcmds flags",
|
||||
` ${cpuType === 7 ? "0xfeedface" : "0xfeedfacf"} ${cpuType} 3 0x00 2 1 16 0x0`,
|
||||
"Load command 0",
|
||||
` cmd ${command}`,
|
||||
" cmdsize 16",
|
||||
" version 12.0",
|
||||
" sdk 12.1",
|
||||
].join("\n");
|
||||
expect(parseOtoolLoadCommands(text).builds[0]?.platform).toBe(platform);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["3", "TVOS"],
|
||||
["4294967295", "4294967295"],
|
||||
])("preserves explicit build platform %s on Intel", (code, platform) => {
|
||||
const text = [
|
||||
"Mach header",
|
||||
" magic cputype cpusubtype caps filetype ncmds sizeofcmds flags",
|
||||
" 0xfeedfacf 16777223 3 0x00 2 1 24 0x0",
|
||||
"Load command 0",
|
||||
" cmd LC_BUILD_VERSION",
|
||||
" cmdsize 24",
|
||||
` platform ${code}`,
|
||||
" minos 12.0",
|
||||
" sdk 12.1",
|
||||
" ntools 0",
|
||||
].join("\n");
|
||||
expect(parseOtoolLoadCommands(text).builds[0]?.platform).toBe(platform);
|
||||
});
|
||||
|
||||
it("preserves an unknown legacy platform spelling on Intel", () => {
|
||||
const text = [
|
||||
"Mach header",
|
||||
" magic cputype cpusubtype caps filetype ncmds sizeofcmds flags",
|
||||
" 0xfeedfacf 16777223 3 0x00 2 1 16 0x0",
|
||||
"Load command 0",
|
||||
` cmd ${command}`,
|
||||
" cmd LC_VERSION_MIN_FUTURE",
|
||||
" cmdsize 16",
|
||||
" version 12.0",
|
||||
" sdk 12.1",
|
||||
].join("\n");
|
||||
expect(parseOtoolLoadCommands(text).builds[0]?.platform).toBe(platform);
|
||||
});
|
||||
|
||||
it("keeps legacy device platforms on arm64 slices", () => {
|
||||
const text = [
|
||||
"Mach header",
|
||||
" magic cputype cpusubtype caps filetype ncmds sizeofcmds flags",
|
||||
" 0xfeedfacf 16777228 0 0x00 2 1 16 0x0",
|
||||
"Load command 0",
|
||||
" cmd LC_VERSION_MIN_IPHONEOS",
|
||||
" cmdsize 16",
|
||||
" version 12.0",
|
||||
" sdk 12.1",
|
||||
].join("\n");
|
||||
expect(parseOtoolLoadCommands(text).builds[0]?.platform).toBe("IPHONEOS");
|
||||
const result = parseOtoolLoadCommands(text);
|
||||
expect(result.builds[0]?.platform).toBe("FUTURE");
|
||||
expect(result.commands[0]?.kind).toBe("LC_VERSION_MIN_FUTURE");
|
||||
});
|
||||
Reference in new issue
Block a user