diff --git a/src/native/NativeMachoInspection.ts b/src/native/NativeMachoInspection.ts index c6a05ad98f..02c33998fd 100644 --- a/src/native/NativeMachoInspection.ts +++ b/src/native/NativeMachoInspection.ts @@ -237,7 +237,15 @@ const normalizeMacho = ( load.commands, dwarfdump.stdout, artifactPath, - selectedArchitecture.name, + selectedArchitecture.name === null + ? null + : { + name: selectedArchitecture.name, + dwarfdumpName: toolArchitectureName( + "dwarfdump", + selectedArchitecture.name, + ), + }, ); const provenance = captures.map(toInvocation); // A thread-state command carries the entrypoint in architecture-specific diff --git a/src/native/parsers/machoUuid.ts b/src/native/parsers/machoUuid.ts index a555d119d8..c2c9ab1cb4 100644 --- a/src/native/parsers/machoUuid.ts +++ b/src/native/parsers/machoUuid.ts @@ -7,12 +7,17 @@ type UuidObservation = | { readonly kind: "valid"; readonly value: string } | { readonly kind: "invalid" | "conflict"; readonly reason: string }; +interface UuidArchitectureSelection { + readonly name: string; + readonly dwarfdumpName: string; +} + /** Reconcile the selected slice's UUID observations from otool and dwarfdump. */ export const reconcileMachoUuid = ( commands: ReturnType["commands"], dwarfdumpOutput: string, artifactPath: string, - selectedArchitecture: string | null, + selectedArchitecture: UuidArchitectureSelection | null, ): { readonly value: string | null; readonly limitations: readonly string[]; @@ -25,7 +30,7 @@ export const reconcileMachoUuid = ( const parseDwarfdumpUuid = ( output: string, artifactPath: string, - selectedArchitecture: string | null, + selectedArchitecture: UuidArchitectureSelection | null, ): UuidObservation => { const parsed = parseDwarfdumpRows(output, artifactPath); return parsed.kind === "invalid" @@ -94,14 +99,15 @@ const parseDwarfdumpRows = ( const selectDwarfdumpRow = ( rows: readonly DwarfdumpUuidRow[], - selectedArchitecture: string | null, + selectedArchitecture: UuidArchitectureSelection | null, ): UuidObservation => { if (rows.length === 0) return { kind: "absent" }; const selectedRows = selectedArchitecture === null ? rows : rows.filter( - ({ architecture }) => architecture === selectedArchitecture, + ({ architecture }) => + architecture === selectedArchitecture.dwarfdumpName, ); const validRows = selectedRows.filter(({ value }) => MACHO_UUID.test(value)); const distinctValid = new Set( @@ -110,15 +116,15 @@ const selectDwarfdumpRow = ( if (distinctValid.size > 1) return { kind: "conflict", - reason: `dwarfdump --uuid returned conflicting UUIDs for selected architecture ${selectedArchitecture ?? "unknown"}`, + reason: `dwarfdump --uuid returned conflicting UUIDs for selected architecture ${selectedArchitecture?.name ?? "unknown"}`, }; if (selectedRows.length !== 1) return { kind: "invalid", reason: selectedRows.length > 1 - ? `dwarfdump --uuid returned multiple rows for selected architecture ${selectedArchitecture}` - : `dwarfdump --uuid returned no row for selected architecture ${selectedArchitecture}`, + ? `dwarfdump --uuid returned multiple rows for selected architecture ${selectedArchitecture?.name ?? "unknown"}` + : `dwarfdump --uuid returned no row for selected architecture ${selectedArchitecture?.name ?? "unknown"}`, }; const [row] = selectedRows; if (row === undefined) return { kind: "absent" }; diff --git a/tests/boundary/providers/native/nativeSelectedArchitecture.test.ts b/tests/boundary/providers/native/nativeSelectedArchitecture.test.ts index f2cb4561c2..97b37ead52 100644 --- a/tests/boundary/providers/native/nativeSelectedArchitecture.test.ts +++ b/tests/boundary/providers/native/nativeSelectedArchitecture.test.ts @@ -85,31 +85,41 @@ it.each([ architectures: ["x86_64", "arm64e", "arm64e.x1"], selected: "arm64e", dyldArchitecture: "arm64e", + dwarfdumpArchitecture: "arm64e", }, { architectures: ["x86_64", "arm64e.v1"], selected: "arm64e", dyldArchitecture: "arm64e.v1", + dwarfdumpArchitecture: "arm64e", }, { architectures: ["arm64v8", "arm64e.v1"], selected: "arm64e", dyldArchitecture: "arm64e.v1", + dwarfdumpArchitecture: "arm64e", }, { architectures: ["arm64e.v1", "arm64v8"], selected: "arm64e", dyldArchitecture: "arm64e.v1", + dwarfdumpArchitecture: "arm64e", }, // arm64e.x1 is its own subtype: otool and nm cannot reach it as arm64e. { architectures: ["x86_64", "arm64e.x1"], selected: "arm64e.x1", dyldArchitecture: "arm64e.x1", + dwarfdumpArchitecture: "arm64e.x1", }, ] as const)( "selects the $dyldArchitecture slice of a universal arm64e target", - async ({ architectures, selected, dyldArchitecture }) => { + async ({ + architectures, + selected, + dyldArchitecture, + dwarfdumpArchitecture, + }) => { const calls: { readonly tool: string; readonly arguments_: readonly string[]; @@ -133,7 +143,7 @@ it.each([ .join("\n"); const fixtures = new NativeFixtureRunner({ lipo, - dwarfdump: `UUID: 01234567-89AB-CDEF-0123-456789ABCDEF (${dyldArchitecture}) /owned/universal\n`, + dwarfdump: `UUID: 01234567-89AB-CDEF-0123-456789ABCDEF (${dwarfdumpArchitecture}) /owned/universal\n`, }); const client = new NativeMacOSProvider( {}, @@ -160,6 +170,14 @@ it.each([ .filter(({ tool }) => tool === "dyld_info") .every(({ arguments_ }) => arguments_.includes(dyldArchitecture)), ).toBe(true); + expect( + calls.find(({ tool }) => tool === "dwarfdump")?.arguments_, + ).toContain(`--arch=${dwarfdumpArchitecture}`); + expect( + result.value.limitations.some((limitation) => + limitation.includes("dwarfdump"), + ), + ).toBe(false); // vtool has no spelling for a variant subtype, so it is skipped rather // than pointed at another slice. const variant = selected.includes("."); @@ -172,6 +190,61 @@ it.each([ }, ); +it.each([ + { + description: "a row explicitly naming another ABI version", + rows: "UUID: FEDCBA98-7654-3210-FEDC-BA9876543210 (arm64e.v2) /owned/universal\n", + uuid: "01234567-89AB-CDEF-0123-456789ABCDEF", + limitations: [ + "dwarfdump --uuid returned no row for selected architecture arm64e.v1; using the unambiguous otool LC_UUID.", + ], + }, + { + description: "a matching family row alongside another ABI version", + rows: "UUID: 01234567-89ab-cdef-0123-456789abcdef (arm64e) /owned/universal\nUUID: FEDCBA98-7654-3210-FEDC-BA9876543210 (arm64e.v2) /owned/universal\n", + uuid: "01234567-89ab-cdef-0123-456789abcdef", + limitations: [], + }, +])( + "preserves selected slice identity with $description", + async ({ rows, uuid, limitations }) => { + const calls: { + readonly tool: string; + readonly arguments_: readonly string[]; + }[] = []; + const fixtures = new NativeFixtureRunner({ + lipo: `architecture arm64e.v1 + cputype ${CPU.arm64.type} + cpusubtype CPU_SUBTYPE_ARM64E + capabilities PTR_AUTH_VERSION USERSPACE 1 + offset 16384 + size 8192 + align 2^14 (16384)`, + dwarfdump: rows, + }); + const client = new NativeMacOSProvider( + {}, + sliceSelectingRunner(fixtures, "arm64e", "arm64e.v1", calls), + "darwin", + ).createClient(nativeMachoTarget("/owned/universal")); + const result = await client.execute("inspect_macho", {}); + if (!result.ok) throw result.error; + expect(result.value.result).toMatchObject({ + uuid, + selected_architecture: { + name: "arm64e.v1", + container_file_offset: 16384, + }, + }); + expect( + result.value.limitations.filter((value) => value.includes("dwarfdump")), + ).toEqual(limitations); + expect( + calls.find(({ tool }) => tool === "dwarfdump")?.arguments_, + ).toContain("--arch=arm64e"); + }, +); + it.each([ ["ordinary", "/owned/universal", "\n"], ["LF Non-fat text", "/owned/\nNon-fat file: forged", "\n"],