fix(native): retain lazy dylibs, normalize version-min builds, limit thread entrypoints (#532)

This commit is contained in:
morluto authored and GitHub committed 2026-10-05 15:23:08 +08:00
1 parent 9e30d93af3
commit 0f77ada55e
3 files changed
+112 -8

No files matched your search

+9
View File
@@ -114,6 +114,15 @@ const normalizeMacho = (
]
: []),
...additionalLimitations,
...(load.entrypoints.length === 0 &&
load.commands.some(
(command) =>
command.kind === "LC_UNIXTHREAD" || command.kind === "LC_THREAD",
)
? [
"Thread-state entrypoints (LC_UNIXTHREAD/LC_THREAD) are retained as raw load commands; file offsets are not derived.",
]
: []),
...(imageBase === null &&
/^\s*offset\s+symbol\s*$/mu.test(byTool("dyld_info", 1).stdout)
? [
+18 -8
View File
@@ -125,13 +125,22 @@ const collectBuild = (
fields: Readonly<Record<string, string | number | null>>,
state: ParsedLoadCommands,
): void => {
if (kind !== "LC_BUILD_VERSION") return;
state.builds.push({
platform: stringField(fields, "platform"),
minimum_os: stringField(fields, "minos"),
sdk: stringField(fields, "sdk"),
tools: parseBuildTools(block),
});
if (kind === "LC_BUILD_VERSION") {
state.builds.push({
platform: stringField(fields, "platform"),
minimum_os: stringField(fields, "minos"),
sdk: stringField(fields, "sdk"),
tools: parseBuildTools(block),
});
return;
}
if (kind.startsWith("LC_VERSION_MIN_"))
state.builds.push({
platform: kind.slice("LC_VERSION_MIN_".length) || null,
minimum_os: stringField(fields, "version"),
sdk: stringField(fields, "sdk"),
tools: [],
});
};
const collectDependency = (
@@ -142,7 +151,8 @@ const collectDependency = (
if (
!kind.startsWith("LC_LOAD_") &&
kind !== "LC_ID_DYLIB" &&
kind !== "LC_REEXPORT_DYLIB"
kind !== "LC_REEXPORT_DYLIB" &&
kind !== "LC_LAZY_LOAD_DYLIB"
)
return;
state.dependencies.push({
@@ -0,0 +1,85 @@
import { expect, it } from "vitest";
import { NativeMacOSProvider } from "../../../../src/native/NativeMacOSProvider.js";
import { parseOtoolLoadCommands } from "../../../../src/native/parsers/otool.js";
import {
NativeFixtureRunner,
nativeMachoTarget,
} from "../../../fixtures/nativeCommands.js";
it("retains lazily loaded library dependencies", () => {
const load = parseOtoolLoadCommands(
[
"Load command 9",
" cmd LC_LAZY_LOAD_DYLIB",
" name /usr/lib/libLazy.dylib (offset 24)",
" time stamp 2 Thu Jan 1 00:00:02 1970",
" current version 1.0.0",
"compatibility version 1.0.0",
"",
].join("\n"),
);
expect(load.dependencies).toContainEqual({
path: "/usr/lib/libLazy.dylib",
kind: "LC_LAZY_LOAD_DYLIB",
current_version: "1.0.0",
compatibility_version: "1.0.0",
});
});
it("normalizes legacy version-minimum commands into build metadata", () => {
const load = parseOtoolLoadCommands(
[
"Load command 3",
" cmd LC_VERSION_MIN_MACOSX",
" version 10.13",
" sdk 10.15",
"Load command 4",
" cmd LC_VERSION_MIN_IPHONEOS",
" version 12.0",
" sdk 12.1",
"Load command 5",
" cmd LC_SOURCE_VERSION",
" version 1.2.3",
"",
].join("\n"),
);
expect(load.builds).toEqual([
{
platform: "MACOSX",
minimum_os: "10.13",
sdk: "10.15",
tools: [],
},
{
platform: "IPHONEOS",
minimum_os: "12.0",
sdk: "12.1",
tools: [],
},
]);
});
it("limits thread-state entrypoints instead of reporting none silently", async () => {
const runner = new NativeFixtureRunner({
"otool:-h": [
"Load command 0",
" cmd LC_UNIXTHREAD",
" flavor 1",
" count 1",
"",
].join("\n"),
});
const client = new NativeMacOSProvider(runner, "darwin").createClient(
nativeMachoTarget("/owned/legacy-fixture"),
);
const result = await client.execute("inspect_macho", {});
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.result).toMatchObject({
entrypoints: { items: [] },
});
expect(result.value.limitations).toContain(
"Thread-state entrypoints (LC_UNIXTHREAD/LC_THREAD) are retained as raw load commands; file offsets are not derived.",
);
});