diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h --- a/lld/MachO/Config.h +++ b/lld/MachO/Config.h @@ -21,9 +21,10 @@ struct Configuration { Symbol *entry; - llvm::MachO::HeaderFileType outputType; + bool hasReexports = false; llvm::StringRef installName; llvm::StringRef outputFile; + llvm::MachO::HeaderFileType outputType; std::vector searchPaths; }; diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -28,6 +28,7 @@ #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" using namespace llvm; using namespace llvm::MachO; @@ -114,6 +115,21 @@ } } +// We expect sub-library names of the form "libfoo", which will match a dylib +// with a path of .*/libfoo.dylib. +static bool markSubLibrary(StringRef searchName) { + for (InputFile *file : inputFiles) { + if (auto *dylibFile = dyn_cast(file)) { + StringRef filename = path::filename(dylibFile->getName()); + if (filename.consume_front(searchName) && filename == ".dylib") { + dylibFile->reexport = true; + return true; + } + } + } + return false; +} + bool macho::link(llvm::ArrayRef argsArr, bool canExitEarly, raw_ostream &stdoutOS, raw_ostream &stderrOS) { lld::stdoutOS = &stdoutOS; @@ -157,6 +173,19 @@ } } + // Now that all dylibs have been loaded, search for those that should be + // re-exported. + for (opt::Arg *arg : args) { + if (arg->getOption().getID() == OPT_sub_library) { + config->hasReexports = true; + StringRef searchName = arg->getValue(); + if (!markSubLibrary(searchName)) { + error("-sub_library " + searchName + + " does not match a supplied dylib"); + } + } + } + // dyld requires us to load libSystem. Since we may run tests on non-OSX // systems which do not have libSystem, we mock it out here. // TODO: Replace this with a stub tbd file once we have TAPI support. diff --git a/lld/MachO/InputFiles.h b/lld/MachO/InputFiles.h --- a/lld/MachO/InputFiles.h +++ b/lld/MachO/InputFiles.h @@ -60,7 +60,14 @@ // .dylib file class DylibFile : public InputFile { public: - explicit DylibFile(MemoryBufferRef mb); + // Mach-O dylibs can re-export other dylibs as sub-libraries, meaning that the + // symbols in those sub-libraries will be available under the umbrella + // library's namespace. Those sub-libraries can also have their own + // re-exports. When loading a re-exported dylib, `umbrella` should be set to + // the root dylib to ensure symbols in the child library are correctly bound + // to the root. On the other hand, if a dylib is being directly loaded + // (through an -lfoo flag), then `umbrella` should be a nullptr. + explicit DylibFile(MemoryBufferRef mb, DylibFile *umbrella = nullptr); static bool classof(const InputFile *f) { return f->kind() == DylibKind; } // Do not use this constructor!! This is meant only for createLibSystemMock(), @@ -70,6 +77,12 @@ StringRef dylibName; uint64_t ordinal = 0; // Ordinal numbering starts from 1, so 0 is a sentinel + bool reexport = false; + std::vector reexported; + +private: + // Expand special path prefixes like @executable_path/. + StringRef expandPath(StringRef path); }; extern std::vector inputFiles; diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp --- a/lld/MachO/InputFiles.cpp +++ b/lld/MachO/InputFiles.cpp @@ -42,6 +42,7 @@ //===----------------------------------------------------------------------===// #include "InputFiles.h" +#include "Config.h" #include "ExportTrie.h" #include "InputSection.h" #include "OutputSegment.h" @@ -54,10 +55,12 @@ #include "llvm/BinaryFormat/MachO.h" #include "llvm/Support/Endian.h" #include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" using namespace llvm; using namespace llvm::MachO; using namespace llvm::support::endian; +using namespace llvm::sys; using namespace lld; using namespace lld::macho; @@ -124,6 +127,21 @@ return nullptr; } +template +static const load_command *forEachCommand(const mach_header_64 *hdr, + uint32_t type, F callback) { + const uint8_t *p = + reinterpret_cast(hdr) + sizeof(mach_header_64); + + for (uint32_t i = 0, n = hdr->ncmds; i < n; ++i) { + auto *cmd = reinterpret_cast(p); + if (cmd->cmd == type) + callback(reinterpret_cast(cmd)); + p += cmd->cmdsize; + } + return nullptr; +} + std::vector InputFile::parseSections(ArrayRef sections) { std::vector ret; @@ -235,7 +253,11 @@ } } -DylibFile::DylibFile(MemoryBufferRef mb) : InputFile(DylibKind, mb) { +DylibFile::DylibFile(MemoryBufferRef mb, DylibFile *umbrella) + : InputFile(DylibKind, mb) { + if (umbrella == nullptr) + umbrella = this; + auto *buf = reinterpret_cast(mb.getBufferStart()); auto *hdr = reinterpret_cast(mb.getBufferStart()); @@ -253,11 +275,27 @@ auto *c = reinterpret_cast(cmd); parseTrie(buf + c->export_off, c->export_size, [&](const Twine &name, uint64_t flags) { - symbols.push_back(symtab->addDylib(saver.save(name), this)); + symbols.push_back(symtab->addDylib(saver.save(name), umbrella)); }); } else { error("LC_DYLD_INFO_ONLY not found"); + return; } + + if (hdr->flags & MH_NO_REEXPORTED_DYLIBS) + return; + + forEachCommand( + hdr, LC_REEXPORT_DYLIB, [&](const dylib_command *c) { + StringRef reexportPath = + reinterpret_cast(c) + read32le(&c->dylib.name); + Optional buffer = readFile(expandPath(reexportPath)); + if (!buffer) { + error("unable to read re-exported dylib at " + reexportPath); + return; + } + reexported.push_back(make(*buffer, umbrella)); + }); } DylibFile::DylibFile() : InputFile(DylibKind, MemoryBufferRef()) {} @@ -270,6 +308,17 @@ return file; } +StringRef DylibFile::expandPath(StringRef path) { + if (path.consume_front("@executable_path/")) { + SmallString<64> executablePath{getName()}; + path::remove_filename(executablePath); + return saver.save(executablePath + "/" + path); + } + // TODO: implement other expansions like @rpath + + return path; +} + // Returns "" or "baz.o". std::string lld::toString(const InputFile *file) { return file ? std::string(file->getName()) : ""; diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td --- a/lld/MachO/Options.td +++ b/lld/MachO/Options.td @@ -23,6 +23,9 @@ def o: Separate<["-"], "o">, MetaVarName<"">, HelpText<"Path to file to write output">; +def sub_library: Separate<["-"], "sub_library">, MetaVarName<"">, + HelpText<"Re-export the specified dylib">; + def v: Flag<["-"], "v">, HelpText<"Display the version number and exit">; // Ignored options diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp --- a/lld/MachO/SyntheticSections.cpp +++ b/lld/MachO/SyntheticSections.cpp @@ -52,7 +52,7 @@ hdr->ncmds = loadCommands.size(); hdr->sizeofcmds = sizeOfCmds; hdr->flags = MH_NOUNDEFS | MH_DYLDLINK | MH_TWOLEVEL; - if (config->outputType == MH_DYLIB) + if (config->outputType == MH_DYLIB && !config->hasReexports) hdr->flags |= MH_NO_REEXPORTED_DYLIBS; uint8_t *p = reinterpret_cast(hdr + 1); diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp --- a/lld/MachO/Writer.cpp +++ b/lld/MachO/Writer.cpp @@ -197,9 +197,13 @@ StringTableSection *stringTableSection = nullptr; }; -class LCLoadDylib : public LoadCommand { +// There are several dylib load commands that share the same structure: +// * LC_LOAD_DYLIB +// * LC_ID_DYLIB +// * LC_REEXPORT_DYLIB +class LCDylib : public LoadCommand { public: - LCLoadDylib(StringRef path) : path(path) {} + LCDylib(LoadCommandType type, StringRef path) : type(type), path(path) {} uint32_t getSize() const override { return alignTo(sizeof(dylib_command) + path.size() + 1, 8); @@ -209,7 +213,7 @@ auto *c = reinterpret_cast(buf); buf += sizeof(dylib_command); - c->cmd = LC_LOAD_DYLIB; + c->cmd = type; c->cmdsize = getSize(); c->dylib.name = sizeof(dylib_command); @@ -218,33 +222,10 @@ } private: + LoadCommandType type; StringRef path; }; -class LCIdDylib : public LoadCommand { -public: - LCIdDylib(StringRef name) : name(name) {} - - uint32_t getSize() const override { - return alignTo(sizeof(dylib_command) + name.size() + 1, 8); - } - - void writeTo(uint8_t *buf) const override { - auto *c = reinterpret_cast(buf); - buf += sizeof(dylib_command); - - c->cmd = LC_ID_DYLIB; - c->cmdsize = getSize(); - c->dylib.name = sizeof(dylib_command); - - memcpy(buf, name.data(), name.size()); - buf[name.size()] = '\0'; - } - -private: - StringRef name; -}; - class LCLoadDylinker : public LoadCommand { public: uint32_t getSize() const override { @@ -359,7 +340,8 @@ headerSection->addLoadCommand(make()); break; case MH_DYLIB: - headerSection->addLoadCommand(make(config->installName)); + headerSection->addLoadCommand( + make(LC_ID_DYLIB, config->installName)); break; default: llvm_unreachable("unhandled output file type"); @@ -376,8 +358,13 @@ uint64_t dylibOrdinal = 1; for (InputFile *file : inputFiles) { if (auto *dylibFile = dyn_cast(file)) { - headerSection->addLoadCommand(make(dylibFile->dylibName)); + headerSection->addLoadCommand( + make(LC_LOAD_DYLIB, dylibFile->dylibName)); dylibFile->ordinal = dylibOrdinal++; + + if (dylibFile->reexport) + headerSection->addLoadCommand( + make(LC_REEXPORT_DYLIB, dylibFile->dylibName)); } } } diff --git a/lld/test/MachO/sub-library.s b/lld/test/MachO/sub-library.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/sub-library.s @@ -0,0 +1,75 @@ +# REQUIRES: x86 +# RUN: mkdir -p %t + +## Create a libsuper that has libgoodbye as a sub-library, which in turns has +## libhello as another sub-library. +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %p/Inputs/libhello.s \ +# RUN: -o %t/libhello.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %p/Inputs/libgoodbye.s \ +# RUN: -o %t/libgoodbye.o +# RUN: echo "" | llvm-mc -filetype=obj -triple=x86_64-apple-darwin -o %t/libsuper.o +# RUN: lld -flavor darwinnew -dylib -install_name \ +# RUN: @executable_path/libhello.dylib %t/libhello.o -o %t/libhello.dylib +# RUN: lld -flavor darwinnew -dylib -L%t -sub_library libhello -lhello -install_name \ +# RUN: @executable_path/libgoodbye.dylib %t/libgoodbye.o -o %t/libgoodbye.dylib +# RUN: lld -flavor darwinnew -dylib -L%t -sub_library libgoodbye -lgoodbye -install_name \ +# RUN: @executable_path/libsuper.dylib %t/libsuper.o -o %t/libsuper.dylib + + +## Check that they have the appropriate LC_REEXPORT_DYLIB commands, and that +## NO_REEXPORTED_DYLIBS is (un)set as appropriate. + +# RUN: llvm-objdump --macho --all-headers %t/libhello.dylib | FileCheck %s \ +# RUN: --check-prefix=HELLO-HEADERS +# HELLO-HEADERS: NO_REEXPORTED_DYLIBS + +# RUN: llvm-objdump --macho --all-headers %t/libgoodbye.dylib | FileCheck %s \ +# RUN: --check-prefix=GOODBYE-HEADERS +# GOODBYE-HEADERS-NOT: NO_REEXPORTED_DYLIBS +# GOODBYE-HEADERS: cmd LC_REEXPORT_DYLIB +# GOODBYE-HEADERS-NOT: Load command +# GOODBYE-HEADERS: name @executable_path/libhello.dylib + +# RUN: llvm-objdump --macho --all-headers %t/libsuper.dylib | FileCheck %s \ +# RUN: --check-prefix=SUPER-HEADERS +# SUPER-HEADERS-NOT: NO_REEXPORTED_DYLIBS +# SUPER-HEADERS: cmd LC_REEXPORT_DYLIB +# SUPER-HEADERS-NOT: Load command +# SUPER-HEADERS: name @executable_path/libgoodbye.dylib + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t/sub-library.o +# RUN: lld -flavor darwinnew -o %t/sub-library -Z -L%t -lsuper %t/sub-library.o + +# RUN: llvm-objdump --macho --bind %t/sub-library | FileCheck %s +# CHECK-LABEL: Bind table: +# CHECK-DAG: __DATA_CONST __got {{.*}} libsuper _hello_world +# CHECK-DAG: __DATA_CONST __got {{.*}} libsuper _goodbye_world + + +## Check that we fail gracefully if the sub-library is missing +# RUN: not lld -flavor darwinnew -dylib -Z -o %t/sub-library -sub_library libmissing %t/sub-library.o 2>&1 \ +# RUN: | FileCheck %s --check-prefix=MISSING-SUB-LIBRARY +# MISSING-SUB-LIBRARY: error: -sub_library libmissing does not match a supplied dylib +# 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 +# MISSING-REEXPORT: error: unable to read re-exported dylib at @executable_path/libgoodbye.dylib + +.text +.globl _main + +_main: + movl $0x2000004, %eax # write() syscall + mov $1, %rdi # stdout + movq _hello_world@GOTPCREL(%rip), %rsi + mov $13, %rdx # length of str + syscall + mov $0, %rax + + movl $0x2000004, %eax # write() syscall + mov $1, %rdi # stdout + movq _goodbye_world@GOTPCREL(%rip), %rsi + mov $15, %rdx # length of str + syscall + mov $0, %rax + ret