diff --git a/lld/MachO/CMakeLists.txt b/lld/MachO/CMakeLists.txt --- a/lld/MachO/CMakeLists.txt +++ b/lld/MachO/CMakeLists.txt @@ -5,6 +5,7 @@ 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 --- a/lld/MachO/Config.h +++ b/lld/MachO/Config.h @@ -39,6 +39,7 @@ llvm::MachO::Architecture arch; PlatformInfo platform; llvm::MachO::HeaderFileType outputType; + std::vector systemLibraryRoots; std::vector librarySearchPaths; std::vector frameworkSearchPaths; std::vector runtimePaths; diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- 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 @@ // Suffix lookup failed, fall through to the no-suffix case. } - if (Optional path = findWithExtension(symlink, {".tbd", ""})) + if (Optional path = resolveDylibPath(symlink)) return path; } return {}; @@ -233,13 +234,10 @@ inputFiles.push_back(make(mbref)); break; case file_magic::tapi_file: { - Expected> result = TextAPIReader::get(mbref); - if (!result) { - error("could not load TAPI file at " + mbref.getBufferIdentifier() + - ": " + toString(result.takeError())); + Optional dylibFile = makeDylibFromTAPI(mbref); + if (!dylibFile) return; - } - inputFiles.push_back(make(**result)); + inputFiles.push_back(*dylibFile); break; } default: @@ -506,7 +504,7 @@ config->outputType = args.hasArg(OPT_dylib) ? MH_DYLIB : MH_EXECUTE; config->runtimePaths = args::getStrings(args, OPT_rpath); - std::vector roots; + std::vector &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.h b/lld/MachO/DriverUtils.h new file mode 100644 --- /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 resolveDylibPath(llvm::StringRef path); + +llvm::Optional makeDylibFromTAPI(llvm::MemoryBufferRef mbref, + DylibFile *umbrella = nullptr); + +} // namespace macho +} // namespace lld + +#endif diff --git a/lld/MachO/DriverUtils.cpp b/lld/MachO/DriverUtils.cpp new file mode 100644 --- /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 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 macho::makeDylibFromTAPI(MemoryBufferRef mbref, + DylibFile *umbrella) { + Expected> result = TextAPIReader::get(mbref); + if (!result) { + error("could not load TAPI file at " + mbref.getBufferIdentifier() + ": " + + toString(result.takeError())); + return {}; + } + return make(**result, umbrella); +} diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp --- 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 @@ parseRelocations(sectionHeaders[i], subsections[i]); } +// The path can point to either a dylib or a .tbd file. +static Optional loadDylib(StringRef path, DylibFile *umbrella) { + Optional 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(*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 loadReexport(StringRef path, DylibFile *umbrella) { + if (path::is_absolute(path, path::Style::posix)) + for (StringRef root : config->systemLibraryRoots) + if (Optional 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(child, umbrella); + assert(child.documents().empty()); + } + } + + if (Optional 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 @@ } // 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(cmd); parseTrie(buf + c->export_off, c->export_size, @@ -386,13 +445,8 @@ auto *c = reinterpret_cast(cmd); StringRef reexportPath = reinterpret_cast(c) + read32le(&c->dylib.name); - // TODO: Expand @loader_path, @executable_path etc in reexportPath - Optional buffer = readFile(reexportPath); - if (!buffer) { - error("unable to read re-exported dylib at " + reexportPath); - return; - } - reexported.push_back(make(*buffer, umbrella)); + if (Optional reexport = loadReexport(reexportPath, umbrella)) + reexported.push_back(*reexport); } } @@ -431,11 +485,20 @@ 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 &intf : interface.documents()) - reexported.push_back(make(*intf, umbrella)); + + bool isTopLevelTapi = false; + if (currentTopLevelTapi == nullptr) { + currentTopLevelTapi = &interface; + isTopLevelTapi = true; + } + + for (InterfaceFileRef intfRef : interface.reexportedLibraries()) + if (Optional reexport = + loadReexport(intfRef.getInstallName(), umbrella)) + reexported.push_back(*reexport); + + if (isTopLevelTapi) + currentTopLevelTapi = nullptr; } ArchiveFile::ArchiveFile(std::unique_ptr &&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 --- 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++.tbd b/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libc++abi.tbd copy from lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libc++.tbd copy to lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libc++abi.tbd --- a/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libc++.tbd +++ b/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libc++abi.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' +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 --- 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 @@ 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 --- 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@GOTPCREL(%rip), %rax + movq _from_non_reexported_tapi_dylib@GOTPCREL(%rip), %rax ret diff --git a/lld/test/MachO/reexport-stub.s b/lld/test/MachO/reexport-stub.s --- 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 --- 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@GOTPCREL(%rip), %rax ret @@ -29,3 +31,9 @@ .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 --- 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