diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp --- a/lld/MachO/Writer.cpp +++ b/lld/MachO/Writer.cpp @@ -678,6 +678,7 @@ in.header->addLoadCommand(make(config->platformInfo)); int64_t dylibOrdinal = 1; + DenseMap ordinalForInstallName; for (InputFile *file : inputFiles) { if (auto *dylibFile = dyn_cast(file)) { if (dylibFile->isBundleLoader) { @@ -701,7 +702,29 @@ config->deadStripDylibs)) continue; - dylibFile->ordinal = dylibOrdinal++; + // Several DylibFiles can have the same installName. Only emit a single + // load command for that installName and give all these DylibFiles the + // same ordinal. + // This can happen if: + // - a new framework could change its installName to an older + // framework name via an $ld$ symbol depending on platform_version + // - symlink (eg libpthread.tbd is a symlink to libSystem.tbd) + // - a framework can be linked both explicitly on the linker + // command line and implicitly as a reexport from a different + // framework. The re-export will usually point to the tbd file + // in Foo.framework/Versions/A/Foo.tbd, while the explicit link will + // usually find Foo.framwork/Foo.tbd. These are usually two identical + // but distinct files (concrete example: CFNetwork.framework, reexported + // from CoreServices.framework). + // In the first case, *semantically distinct* DylibFiles will have the + // same installName. + int64_t &ordinal = ordinalForInstallName[dylibFile->dylibName]; + if (ordinal) { + dylibFile->ordinal = ordinal; + continue; + } + + ordinal = dylibFile->ordinal = dylibOrdinal++; LoadCommandType lcType = dylibFile->forceWeakImport || dylibFile->refState == RefState::Weak ? LC_LOAD_WEAK_DYLIB diff --git a/lld/test/MachO/dylink-ordinal.s b/lld/test/MachO/dylink-ordinal.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/dylink-ordinal.s @@ -0,0 +1,65 @@ +# REQUIRES: x86 + +## --no-leading-lines needed for .tbd files. +# RUN: rm -rf %t; split-file --no-leading-lines %s %t + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/main.s -o %t/main.o +# RUN: %lld -o %t/main -L%t -lFoo -lBar -lSystem %t/main.o +# RUN: llvm-objdump --lazy-bind -d --no-show-raw-insn %t/main | FileCheck %s + +# CHECK: callq 0x[[#%x,FOO_OFF:]] +# CHECK-NEXT: callq 0x[[#%x,BAR_OFF:]] + +# CHECK: [[#%x,BAR_OFF]]: jmpq {{.*}} # [[#%x,BAR_BIND:]] +# CHECK: [[#%x,FOO_OFF]]: jmpq {{.*}} # [[#%x,FOO_BIND:]] + +# CHECK-LABEL: Lazy bind table: +# CHECK-DAG: __DATA __la_symbol_ptr 0x[[#%x,FOO_BIND]] Foo _foo +# CHECK-DAG: __DATA __la_symbol_ptr 0x[[#%x,BAR_BIND]] Foo _bar + +# RUN: llvm-nm -m %t/main | FileCheck --check-prefix=NM %s + +# NM-DAG: _bar (from Foo) +# NM-DAG: _foo (from Foo) + +# RUN: llvm-otool -L %t/main | FileCheck %s --check-prefix=LOAD + +# LOAD: Foo.dylib +# LOAD-NOT: Foo.dylib + +#--- libFoo.tbd +--- !tapi-tbd +tbd-version: 4 +targets: [ x86_64-macos ] +uuids: + - target: x86_64-macos + value: 00000000-0000-0000-0000-000000000000 +install-name: 'Foo.dylib' +current-version: 0001.001.1 +exports: + - targets: [ x86_64-macos ] + symbols: [ _foo ] + +#--- libBar.tbd +--- !tapi-tbd +tbd-version: 4 +targets: [ x86_64-macos ] +uuids: + - target: x86_64-macos + value: 00000000-0000-0000-0000-000000000000 +## Also uses Foo.dylib as install-name! +## Normally, this would happen conditionally via an $ld$ symbol. +install-name: 'Foo.dylib' +current-version: 0001.001.1 +exports: + - targets: [ x86_64-macos ] + symbols: [ _bar ] + +#--- main.s +.section __TEXT,__text +.globl _main, _foo, _bar + +_main: + callq _foo + callq _bar + ret