diff --git a/src/native/parsers/otool.ts b/src/native/parsers/otool.ts index b4900fc4f..e03916261 100644 --- a/src/native/parsers/otool.ts +++ b/src/native/parsers/otool.ts @@ -139,7 +139,12 @@ const collectDependency = ( fields: Readonly>, state: ParsedLoadCommands, ): void => { - if (!kind.startsWith("LC_LOAD_") && kind !== "LC_ID_DYLIB") return; + if ( + !kind.startsWith("LC_LOAD_") && + kind !== "LC_ID_DYLIB" && + kind !== "LC_REEXPORT_DYLIB" + ) + return; state.dependencies.push({ path: stripOffsetSuffix(stringField(fields, "name")), kind, diff --git a/tests/boundary/providers/native/nativeReexport.golden.test.ts b/tests/boundary/providers/native/nativeReexport.golden.test.ts new file mode 100644 index 000000000..db9fcab88 --- /dev/null +++ b/tests/boundary/providers/native/nativeReexport.golden.test.ts @@ -0,0 +1,40 @@ +import { expect, it } from "vitest"; + +import { NativeMacOSProvider } from "../../../../src/native/NativeMacOSProvider.js"; +import { + NativeFixtureRunner, + nativeFixture, + nativeMachoTarget, +} from "../../../fixtures/nativeCommands.js"; + +it("retains native re-export dependencies alongside ordinary and identity commands", async () => { + const runner = new NativeFixtureRunner({ + otool: await nativeFixture("native-reexport/otool-load.txt"), + }); + const client = new NativeMacOSProvider(runner, "darwin").createClient( + nativeMachoTarget("/owned/libOuter.dylib"), + ); + const result = await client.execute("inspect_macho", {}); + expect(result.ok).toBe(true); + if (!result.ok) return; + expect(result.value.result).toMatchObject({ + dependencies: { + exhaustive: true, + total: 3, + items: expect.arrayContaining([ + expect.objectContaining({ + kind: "LC_REEXPORT_DYLIB", + path: "@rpath/libLeaf.dylib", + }), + expect.objectContaining({ + kind: "LC_ID_DYLIB", + path: "/owned/libOuter.dylib", + }), + expect.objectContaining({ + kind: "LC_LOAD_DYLIB", + path: "/usr/lib/libSystem.B.dylib", + }), + ]), + }, + }); +}); diff --git a/tests/fixtures/native-macos/native-reexport/README.md b/tests/fixtures/native-macos/native-reexport/README.md new file mode 100644 index 000000000..613f5f7e6 --- /dev/null +++ b/tests/fixtures/native-macos/native-reexport/README.md @@ -0,0 +1,14 @@ +# Source-built re-export load command + +Captured from the adjacent C sources using the macOS arm64 host Xcode tools: + +```sh +xcrun clang -dynamiclib leaf.c -Wl,-install_name,@rpath/libLeaf.dylib -o libLeaf.dylib +xcrun clang -dynamiclib outer.c -Wl,-reexport_library,libLeaf.dylib -o libOuter.dylib +xcrun otool -l libOuter.dylib > otool-load.txt +``` + +The temporary outer-library path was replaced with `/owned/libOuter.dylib`. +The load-command fields are otherwise unmodified. The capture retains the real +re-export, ID, and ordinary libSystem dependency. It proves format parsing, +not Hopper or Ghidra behavior. diff --git a/tests/fixtures/native-macos/native-reexport/leaf.c b/tests/fixtures/native-macos/native-reexport/leaf.c new file mode 100644 index 000000000..9832b7bd6 --- /dev/null +++ b/tests/fixtures/native-macos/native-reexport/leaf.c @@ -0,0 +1 @@ +int owned_leaf(void) { return 9; } diff --git a/tests/fixtures/native-macos/native-reexport/otool-load.txt b/tests/fixtures/native-macos/native-reexport/otool-load.txt new file mode 100644 index 000000000..d20c34486 --- /dev/null +++ b/tests/fixtures/native-macos/native-reexport/otool-load.txt @@ -0,0 +1,140 @@ +/owned/libOuter.dylib: +Load command 0 + cmd LC_SEGMENT_64 + cmdsize 232 + segname __TEXT + vmaddr 0x0000000000000000 + vmsize 0x0000000000004000 + fileoff 0 + filesize 16384 + maxprot 0x00000005 + initprot 0x00000005 + nsects 2 + flags 0x0 +Section + sectname __text + segname __TEXT + addr 0x0000000000000358 + size 0x0000000000000008 + offset 856 + align 2^2 (4) + reloff 0 + nreloc 0 + flags 0x80000400 + reserved1 0 + reserved2 0 +Section + sectname __unwind_info + segname __TEXT + addr 0x0000000000000360 + size 0x0000000000000058 + offset 864 + align 2^2 (4) + reloff 0 + nreloc 0 + flags 0x00000000 + reserved1 0 + reserved2 0 +Load command 1 + cmd LC_SEGMENT_64 + cmdsize 72 + segname __LINKEDIT + vmaddr 0x0000000000004000 + vmsize 0x0000000000004000 + fileoff 16384 + filesize 416 + maxprot 0x00000001 + initprot 0x00000001 + nsects 0 + flags 0x0 +Load command 2 + cmd LC_ID_DYLIB + cmdsize 120 + name /owned/libOuter.dylib (offset 24) + time stamp 1 Wed Dec 31 19:00:01 1969 + current version 0.0.0 +compatibility version 0.0.0 +Load command 3 + cmd LC_DYLD_CHAINED_FIXUPS + cmdsize 16 + dataoff 16384 + datasize 48 +Load command 4 + cmd LC_DYLD_EXPORTS_TRIE + cmdsize 16 + dataoff 16432 + datasize 32 +Load command 5 + cmd LC_SYMTAB + cmdsize 24 + symoff 16472 + nsyms 1 + stroff 16488 + strsize 16 +Load command 6 + cmd LC_DYSYMTAB + cmdsize 80 + ilocalsym 0 + nlocalsym 0 + iextdefsym 0 + nextdefsym 1 + iundefsym 1 + nundefsym 0 + tocoff 0 + ntoc 0 + modtaboff 0 + nmodtab 0 + extrefsymoff 0 + nextrefsyms 0 + indirectsymoff 0 + nindirectsyms 0 + extreloff 0 + nextrel 0 + locreloff 0 + nlocrel 0 +Load command 7 + cmd LC_UUID + cmdsize 24 + uuid 6199BBEB-E87C-38C9-9857-9FAB706B2C3F +Load command 8 + cmd LC_BUILD_VERSION + cmdsize 32 + platform 1 + minos 26.0 + sdk 26.4 + ntools 1 + tool 3 + version 1266.8 +Load command 9 + cmd LC_SOURCE_VERSION + cmdsize 16 + version 0.0 +Load command 10 + cmd LC_REEXPORT_DYLIB + cmdsize 48 + name @rpath/libLeaf.dylib (offset 24) + time stamp 2 Wed Dec 31 19:00:02 1969 + current version 0.0.0 +compatibility version 0.0.0 +Load command 11 + cmd LC_LOAD_DYLIB + cmdsize 56 + name /usr/lib/libSystem.B.dylib (offset 24) + time stamp 2 Wed Dec 31 19:00:02 1969 + current version 1356.0.0 +compatibility version 1.0.0 +Load command 12 + cmd LC_FUNCTION_STARTS + cmdsize 16 + dataoff 16464 + datasize 8 +Load command 13 + cmd LC_DATA_IN_CODE + cmdsize 16 + dataoff 16472 + datasize 0 +Load command 14 + cmd LC_CODE_SIGNATURE + cmdsize 16 + dataoff 16512 + datasize 288 diff --git a/tests/fixtures/native-macos/native-reexport/outer.c b/tests/fixtures/native-macos/native-reexport/outer.c new file mode 100644 index 000000000..be1427b2f --- /dev/null +++ b/tests/fixtures/native-macos/native-reexport/outer.c @@ -0,0 +1 @@ +int owned_outer(void) { return 7; }