[lld] 7394460 - [lld-macho] Handle TAPI and regular re-exports uniformly

Jez Ng via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 26 19:26:56 PDT 2020


Author: Jez Ng
Date: 2020-08-26T19:20:48-07:00
New Revision: 7394460d8759be5afc3322a2b8cab5e6865e431a

URL: https://github.com/llvm/llvm-project/commit/7394460d8759be5afc3322a2b8cab5e6865e431a
DIFF: https://github.com/llvm/llvm-project/commit/7394460d8759be5afc3322a2b8cab5e6865e431a.diff

LOG: [lld-macho] Handle TAPI and regular re-exports uniformly

The re-exports list in a TAPI document can either refer to other inlined
TAPI documents, or to on-disk files (which may themselves be TBD or
regular files.) Similarly, the re-exports of a regular dylib can refer
to a TBD file.

Differential Revision: https://reviews.llvm.org/D85404

Added: 
    lld/MachO/DriverUtils.cpp
    lld/MachO/DriverUtils.h
    lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libc++abi.tbd

Modified: 
    lld/MachO/CMakeLists.txt
    lld/MachO/Config.h
    lld/MachO/Driver.cpp
    lld/MachO/InputFiles.cpp
    lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libc++.tbd
    lld/test/MachO/Inputs/iPhoneSimulator.sdk/usr/lib/libSystem.tbd
    lld/test/MachO/invalid/stub-link.s
    lld/test/MachO/reexport-stub.s
    lld/test/MachO/stub-link.s
    lld/test/MachO/sub-library.s

Removed: 
    


################################################################################
diff  --git a/lld/MachO/CMakeLists.txt b/lld/MachO/CMakeLists.txt
index 6fe356f51589..985ad7d8b7df 100644
--- a/lld/MachO/CMakeLists.txt
+++ b/lld/MachO/CMakeLists.txt
@@ -5,6 +5,7 @@ add_public_tablegen_target(MachOOptionsTableGen)
 add_lld_library(lldMachO2
   Arch/X86_64.cpp
   Driver.cpp
+  DriverUtils.cpp
   ExportTrie.cpp
   InputFiles.cpp
   InputSection.cpp

diff  --git a/lld/MachO/Config.h b/lld/MachO/Config.h
index 362069ea8040..0c6644041dba 100644
--- a/lld/MachO/Config.h
+++ b/lld/MachO/Config.h
@@ -39,6 +39,7 @@ struct Configuration {
   llvm::MachO::Architecture arch;
   PlatformInfo platform;
   llvm::MachO::HeaderFileType outputType;
+  std::vector<llvm::StringRef> systemLibraryRoots;
   std::vector<llvm::StringRef> librarySearchPaths;
   std::vector<llvm::StringRef> frameworkSearchPaths;
   std::vector<llvm::StringRef> runtimePaths;

diff  --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index d6a3bb91e1a0..d24494f752c5 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -8,6 +8,7 @@
 
 #include "Driver.h"
 #include "Config.h"
+#include "DriverUtils.h"
 #include "InputFiles.h"
 #include "OutputSection.h"
 #include "OutputSegment.h"
@@ -129,7 +130,7 @@ static Optional<std::string> findFramework(StringRef name) {
       // Suffix lookup failed, fall through to the no-suffix case.
     }
 
-    if (Optional<std::string> path = findWithExtension(symlink, {".tbd", ""}))
+    if (Optional<std::string> path = resolveDylibPath(symlink))
       return path;
   }
   return {};
@@ -233,13 +234,10 @@ static void addFile(StringRef path) {
     inputFiles.push_back(make<DylibFile>(mbref));
     break;
   case file_magic::tapi_file: {
-    Expected<std::unique_ptr<InterfaceFile>> result = TextAPIReader::get(mbref);
-    if (!result) {
-      error("could not load TAPI file at " + mbref.getBufferIdentifier() +
-            ": " + toString(result.takeError()));
+    Optional<DylibFile *> dylibFile = makeDylibFromTAPI(mbref);
+    if (!dylibFile)
       return;
-    }
-    inputFiles.push_back(make<DylibFile>(**result));
+    inputFiles.push_back(*dylibFile);
     break;
   }
   default:
@@ -506,7 +504,7 @@ bool macho::link(llvm::ArrayRef<const char *> argsArr, bool canExitEarly,
   config->outputType = args.hasArg(OPT_dylib) ? MH_DYLIB : MH_EXECUTE;
   config->runtimePaths = args::getStrings(args, OPT_rpath);
 
-  std::vector<StringRef> roots;
+  std::vector<StringRef> &roots = config->systemLibraryRoots;
   for (const Arg *arg : args.filtered(OPT_syslibroot))
     roots.push_back(arg->getValue());
   // NOTE: the final `-syslibroot` being `/` will ignore all roots

diff  --git a/lld/MachO/DriverUtils.cpp b/lld/MachO/DriverUtils.cpp
new file mode 100644
index 000000000000..fa0b62e11c49
--- /dev/null
+++ b/lld/MachO/DriverUtils.cpp
@@ -0,0 +1,46 @@
+//===- DriverUtils.cpp ----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DriverUtils.h"
+#include "InputFiles.h"
+
+#include "lld/Common/ErrorHandler.h"
+#include "lld/Common/Memory.h"
+#include "llvm/Support/Path.h"
+#include "llvm/TextAPI/MachO/TextAPIReader.h"
+
+using namespace llvm;
+using namespace llvm::MachO;
+using namespace llvm::sys;
+using namespace lld;
+using namespace lld::macho;
+
+Optional<std::string> macho::resolveDylibPath(StringRef path) {
+  // TODO: if a tbd and dylib are both present, we should check to make sure
+  // they are consistent.
+  if (fs::exists(path))
+    return std::string(path);
+
+  SmallString<261> location = path;
+  path::replace_extension(location, ".tbd");
+  if (fs::exists(location))
+    return std::string(location);
+
+  return {};
+}
+
+Optional<DylibFile *> macho::makeDylibFromTAPI(MemoryBufferRef mbref,
+                                               DylibFile *umbrella) {
+  Expected<std::unique_ptr<InterfaceFile>> result = TextAPIReader::get(mbref);
+  if (!result) {
+    error("could not load TAPI file at " + mbref.getBufferIdentifier() + ": " +
+          toString(result.takeError()));
+    return {};
+  }
+  return make<DylibFile>(**result, umbrella);
+}

diff  --git a/lld/MachO/DriverUtils.h b/lld/MachO/DriverUtils.h
new file mode 100644
index 000000000000..d3d3670ab246
--- /dev/null
+++ b/lld/MachO/DriverUtils.h
@@ -0,0 +1,31 @@
+//===- DriverUtils.h --------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLD_MACHO_DRIVER_UTILS_H
+#define LLD_MACHO_DRIVER_UTILS_H
+
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
+
+namespace lld {
+namespace macho {
+
+class DylibFile;
+
+// Check for both libfoo.dylib and libfoo.tbd (in that order).
+llvm::Optional<std::string> resolveDylibPath(llvm::StringRef path);
+
+llvm::Optional<DylibFile *> makeDylibFromTAPI(llvm::MemoryBufferRef mbref,
+                                              DylibFile *umbrella = nullptr);
+
+} // namespace macho
+} // namespace lld
+
+#endif

diff  --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp
index 8b24eac652b2..0a3e3f6558a7 100644
--- a/lld/MachO/InputFiles.cpp
+++ b/lld/MachO/InputFiles.cpp
@@ -43,6 +43,7 @@
 
 #include "InputFiles.h"
 #include "Config.h"
+#include "DriverUtils.h"
 #include "ExportTrie.h"
 #include "InputSection.h"
 #include "MachOStructs.h"
@@ -53,6 +54,7 @@
 
 #include "lld/Common/ErrorHandler.h"
 #include "lld/Common/Memory.h"
+#include "llvm/ADT/iterator.h"
 #include "llvm/BinaryFormat/MachO.h"
 #include "llvm/Support/Endian.h"
 #include "llvm/Support/MemoryBuffer.h"
@@ -340,6 +342,60 @@ ObjFile::ObjFile(MemoryBufferRef mb) : InputFile(ObjKind, mb) {
     parseRelocations(sectionHeaders[i], subsections[i]);
 }
 
+// The path can point to either a dylib or a .tbd file.
+static Optional<DylibFile *> loadDylib(StringRef path, DylibFile *umbrella) {
+  Optional<MemoryBufferRef> mbref = readFile(path);
+  if (!mbref) {
+    error("could not read dylib file at " + path);
+    return {};
+  }
+
+  file_magic magic = identify_magic(mbref->getBuffer());
+  if (magic == file_magic::tapi_file)
+    return makeDylibFromTAPI(*mbref, umbrella);
+  assert(magic == file_magic::macho_dynamically_linked_shared_lib);
+  return make<DylibFile>(*mbref, umbrella);
+}
+
+// TBD files are parsed into a series of TAPI documents (InterfaceFiles), with
+// the first document storing child pointers to the rest of them. When we are
+// processing a given TBD file, we store that top-level document here. When
+// processing re-exports, we search its children for potentially matching
+// documents in the same TBD file. Note that the children themselves don't
+// point to further documents, i.e. this is a two-level tree.
+//
+// ld64 allows a TAPI re-export to reference documents nested within other TBD
+// files, but that seems like a strange design, so this is an intentional
+// deviation.
+const InterfaceFile *currentTopLevelTapi = nullptr;
+
+// Re-exports can either refer to on-disk files, or to documents within .tbd
+// files.
+static Optional<DylibFile *> loadReexport(StringRef path, DylibFile *umbrella) {
+  if (path::is_absolute(path, path::Style::posix))
+    for (StringRef root : config->systemLibraryRoots)
+      if (Optional<std::string> dylibPath =
+              resolveDylibPath((root + path).str()))
+        return loadDylib(*dylibPath, umbrella);
+
+  // TODO: Expand @loader_path, @executable_path etc
+
+  if (currentTopLevelTapi != nullptr) {
+    for (InterfaceFile &child :
+         make_pointee_range(currentTopLevelTapi->documents())) {
+      if (path == child.getInstallName())
+        return make<DylibFile>(child, umbrella);
+      assert(child.documents().empty());
+    }
+  }
+
+  if (Optional<std::string> dylibPath = resolveDylibPath(path))
+    return loadDylib(*dylibPath, umbrella);
+
+  error("unable to locate re-export with install name " + path);
+  return {};
+}
+
 DylibFile::DylibFile(MemoryBufferRef mb, DylibFile *umbrella)
     : InputFile(DylibKind, mb) {
   if (umbrella == nullptr)
@@ -358,6 +414,9 @@ DylibFile::DylibFile(MemoryBufferRef mb, DylibFile *umbrella)
   }
 
   // Initialize symbols.
+  // TODO: if a re-exported dylib is public (lives in /usr/lib or
+  // /System/Library/Frameworks), we should bind to its symbols directly
+  // instead of the re-exporting umbrella library.
   if (const load_command *cmd = findCommand(hdr, LC_DYLD_INFO_ONLY)) {
     auto *c = reinterpret_cast<const dyld_info_command *>(cmd);
     parseTrie(buf + c->export_off, c->export_size,
@@ -386,13 +445,8 @@ DylibFile::DylibFile(MemoryBufferRef mb, DylibFile *umbrella)
     auto *c = reinterpret_cast<const dylib_command *>(cmd);
     StringRef reexportPath =
         reinterpret_cast<const char *>(c) + read32le(&c->dylib.name);
-    // TODO: Expand @loader_path, @executable_path etc in reexportPath
-    Optional<MemoryBufferRef> buffer = readFile(reexportPath);
-    if (!buffer) {
-      error("unable to read re-exported dylib at " + reexportPath);
-      return;
-    }
-    reexported.push_back(make<DylibFile>(*buffer, umbrella));
+    if (Optional<DylibFile *> reexport = loadReexport(reexportPath, umbrella))
+      reexported.push_back(*reexport);
   }
 }
 
@@ -431,11 +485,20 @@ DylibFile::DylibFile(const InterfaceFile &interface, DylibFile *umbrella)
       break;
     }
   }
-  // TODO(compnerd) properly represent the hierarchy of the documents as it is
-  // in theory possible to have re-exported dylibs from re-exported dylibs which
-  // should be parent'ed to the child.
-  for (const std::shared_ptr<InterfaceFile> &intf : interface.documents())
-    reexported.push_back(make<DylibFile>(*intf, umbrella));
+
+  bool isTopLevelTapi = false;
+  if (currentTopLevelTapi == nullptr) {
+    currentTopLevelTapi = &interface;
+    isTopLevelTapi = true;
+  }
+
+  for (InterfaceFileRef intfRef : interface.reexportedLibraries())
+    if (Optional<DylibFile *> reexport =
+            loadReexport(intfRef.getInstallName(), umbrella))
+      reexported.push_back(*reexport);
+
+  if (isTopLevelTapi)
+    currentTopLevelTapi = nullptr;
 }
 
 ArchiveFile::ArchiveFile(std::unique_ptr<llvm::object::Archive> &&f)

diff  --git a/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libc++.tbd b/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libc++.tbd
index c71c9b0eb009..f7c70b20666c 100644
--- a/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libc++.tbd
+++ b/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libc++.tbd
@@ -1,10 +1,10 @@
 --- !tapi-tbd-v3
 archs:            [ i386, x86_64 ]
-uuids:            [ 'i386: 00000000-0000-0000-0000-000000000000', 'x86_64: 00000000-0000-0000-0000-0
-0000000001' ]
+uuids:            [ 'i386: 00000000-0000-0000-0000-000000000000', 'x86_64: 00000000-0000-0000-0000-000000000001' ]
 platform:         macosx
 install-name:     '/usr/lib/libc++.dylib'
 current-version:  1281
 exports:
   - archs:        [ i386, x86_64 ]
+    re-exports:   [ '/usr/lib/libc++abi.dylib' ]
 ...

diff  --git a/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libc++abi.tbd b/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libc++abi.tbd
new file mode 100644
index 000000000000..47b7456484b0
--- /dev/null
+++ b/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libc++abi.tbd
@@ -0,0 +1,10 @@
+--- !tapi-tbd-v3
+archs:            [ i386, x86_64 ]
+uuids:            [ 'i386: 00000000-0000-0000-0000-000000000000', 'x86_64: 00000000-0000-0000-0000-000000000001' ]
+platform:         macosx
+install-name:     '/usr/lib/libc++abi.dylib'
+current-version:  1281
+exports:
+  - archs:        [ i386, x86_64 ]
+    symbols:      [ ___gxx_personality_v0 ]
+...

diff  --git a/lld/test/MachO/Inputs/iPhoneSimulator.sdk/usr/lib/libSystem.tbd b/lld/test/MachO/Inputs/iPhoneSimulator.sdk/usr/lib/libSystem.tbd
index 3e62c2ee711b..56246f480d99 100644
--- a/lld/test/MachO/Inputs/iPhoneSimulator.sdk/usr/lib/libSystem.tbd
+++ b/lld/test/MachO/Inputs/iPhoneSimulator.sdk/usr/lib/libSystem.tbd
@@ -20,4 +20,15 @@ exports:
     symbols:    [ __cache_handle_memory_pressure_event ]
   - archs:      [ i386, x86_64 ]
     symbols:    [ _cache_create, _cache_destroy, _cache_get ]
+
+# The following TAPI document is not re-exported by any other document in this
+# TBD file, and should therefore be inaccessible.
+--- !tapi-tbd-v3
+archs:            [ i386, x86_64 ]
+uuids:            [ 'i386: 00000000-0000-0000-0000-000000000003', 'x86_64: 00000000-0000-0000-0000-000000000004' ]
+platform:         ios
+install-name:     '/usr/lib/libnotreexported.dylib'
+exports:
+  - archs:      [ i386, x86_64 ]
+    symbols:    [ _from_non_reexported_tapi_dylib ]
 ...

diff  --git a/lld/test/MachO/invalid/stub-link.s b/lld/test/MachO/invalid/stub-link.s
index f1c159037024..8160ff69e071 100644
--- a/lld/test/MachO/invalid/stub-link.s
+++ b/lld/test/MachO/invalid/stub-link.s
@@ -5,11 +5,13 @@
 # RUN: llvm-mc -filetype obj -triple x86_64-apple-ios %s -o %t/test.o
 # RUN: not lld -flavor darwinnew -o %t/test -Z -L%S/../Inputs/iPhoneSimulator.sdk/usr/lib -lSystem %t/test.o 2>&1 | FileCheck %s
 
-# CHECK: error: undefined symbol __cache_handle_memory_pressure_event
+# CHECK-DAG: error: undefined symbol __cache_handle_memory_pressure_event
+# CHECK-DAG: error: undefined symbol _from_non_reexported_tapi_dylib
 
 .section __TEXT,__text
 .global _main
 
 _main:
   movq __cache_handle_memory_pressure_event at GOTPCREL(%rip), %rax
+  movq _from_non_reexported_tapi_dylib at GOTPCREL(%rip), %rax
   ret

diff  --git a/lld/test/MachO/reexport-stub.s b/lld/test/MachO/reexport-stub.s
index 3d477b1d1c6f..d2139c805d37 100644
--- a/lld/test/MachO/reexport-stub.s
+++ b/lld/test/MachO/reexport-stub.s
@@ -10,3 +10,19 @@
 # DYLIB-HEADERS:     cmd     LC_REEXPORT_DYLIB
 # DYLIB-HEADERS-NOT: Load command
 # DYLIB-HEADERS:     name    /usr/lib/libc++.dylib
+
+# RUN: llvm-mc -filetype obj -triple x86_64-apple-darwin %s -o %t/test.o
+# RUN: lld -flavor darwinnew -o %t/test -syslibroot %S/Inputs/MacOSX.sdk -lSystem -L%t -lreexporter %t/test.o
+# RUN: llvm-objdump --bind --no-show-raw-insn -d %t/test | FileCheck %s
+
+# CHECK: Bind table:
+# CHECK-DAG: __DATA __data {{.*}} pointer 0 libreexporter ___gxx_personality_v0
+
+.text
+.globl _main
+
+_main:
+  ret
+
+.data
+  .quad ___gxx_personality_v0

diff  --git a/lld/test/MachO/stub-link.s b/lld/test/MachO/stub-link.s
index 0d6b7fec3f2a..04d01047b32c 100644
--- a/lld/test/MachO/stub-link.s
+++ b/lld/test/MachO/stub-link.s
@@ -3,7 +3,7 @@
 # RUN: mkdir -p %t
 #
 # RUN: llvm-mc -filetype obj -triple x86_64-apple-darwin %s -o %t/test.o
-# RUN: lld -flavor darwinnew -o %t/test -syslibroot %S/Inputs/MacOSX.sdk -lSystem -framework CoreFoundation %t/test.o
+# RUN: lld -flavor darwinnew -o %t/test -syslibroot %S/Inputs/MacOSX.sdk -lSystem -lc++ -framework CoreFoundation %t/test.o
 #
 # RUN: llvm-objdump --bind --no-show-raw-insn -d -r %t/test | FileCheck %s
 
@@ -16,11 +16,13 @@
 # CHECK-DAG: __DATA __data {{.*}} pointer 0 CoreFoundation _OBJC_METACLASS_$_NSObject
 # CHECK-DAG: __DATA __data {{.*}} pointer 0 CoreFoundation _OBJC_IVAR_$_NSConstantArray._count
 # CHECK-DAG: __DATA __data {{.*}} pointer 0 CoreFoundation _OBJC_EHTYPE_$_NSException
+# CHECK-DAG: __DATA __data {{.*}} pointer 0 libc++         ___gxx_personality_v0
 
 .section __TEXT,__text
 .global _main
 
 _main:
+## This symbol is defined in an inner TAPI document within libSystem.tbd.
   movq ___nan at GOTPCREL(%rip), %rax
   ret
 
@@ -29,3 +31,9 @@ _main:
   .quad _OBJC_METACLASS_$_NSObject
   .quad _OBJC_IVAR_$_NSConstantArray._count
   .quad _OBJC_EHTYPE_$_NSException
+
+## This symbol is defined in libc++abi.tbd, but we are linking test.o against
+## libc++.tbd (which re-exports libc++abi). Linking against this symbol verifies
+## that .tbd file re-exports can refer not just to TAPI documents within the
+## same .tbd file, but to other on-disk files as well.
+  .quad ___gxx_personality_v0

diff  --git a/lld/test/MachO/sub-library.s b/lld/test/MachO/sub-library.s
index e858eaf0bff5..bbaafd4a5d81 100644
--- a/lld/test/MachO/sub-library.s
+++ b/lld/test/MachO/sub-library.s
@@ -52,7 +52,7 @@
 # RUN: rm -f %t/libgoodbye.dylib
 # RUN: not lld -flavor darwinnew -o %t/sub-library -Z -L%t -lsuper %t/sub-library.o 2>&1 \
 # RUN:  | FileCheck %s --check-prefix=MISSING-REEXPORT -DDIR=%t
-# MISSING-REEXPORT: error: unable to read re-exported dylib at [[DIR]]/libgoodbye.dylib
+# MISSING-REEXPORT: error: unable to locate re-export with install name [[DIR]]/libgoodbye.dylib
 
 .text
 .globl _main


        


More information about the llvm-commits mailing list