diff --git a/lld/MachO/Driver.h b/lld/MachO/Driver.h --- a/lld/MachO/Driver.h +++ b/lld/MachO/Driver.h @@ -44,7 +44,8 @@ llvm::Optional resolveDylibPath(llvm::StringRef path); llvm::Optional loadDylib(llvm::MemoryBufferRef mbref, - DylibFile *umbrella = nullptr); + DylibFile *umbrella = nullptr, + bool isBundleLoader = false); uint32_t getModTime(llvm::StringRef path); diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -257,7 +257,8 @@ return v; } -static InputFile *addFile(StringRef path, bool forceLoadArchive) { +static InputFile *addFile(StringRef path, bool forceLoadArchive, + bool isBundleLoader = false) { Optional buffer = readFile(path); if (!buffer) return nullptr; @@ -317,6 +318,17 @@ case file_magic::bitcode: newFile = make(mbref); break; + case file_magic::macho_executable: + case file_magic::macho_bundle: + if (!isBundleLoader) + error(path + ": unhandled file type"); + if (Optional dylibFile = + loadDylib(mbref, nullptr, isBundleLoader)) + newFile = *dylibFile; + else + error(Twine("Unable to parse bundle_loader target ") + path); + + break; default: error(path + ": unhandled file type"); } @@ -780,8 +792,17 @@ const auto &opt = arg->getOption(); warnIfDeprecatedOption(opt); warnIfUnimplementedOption(opt); + // TODO: are any of these better handled via filtered() or getLastArg()? switch (opt.getID()) { + case OPT_bundle_loader: + if (config->outputType != MH_BUNDLE) + error("-bundle_loader can only be used with MachO bundle output"); + if (fs::exists(arg->getValue())) + addFile(arg->getValue(), false, true); + else + warn(Twine("bundle_loader not found: ") + arg->getValue()); + break; case OPT_INPUT: addFile(arg->getValue(), false); break; diff --git a/lld/MachO/DriverUtils.cpp b/lld/MachO/DriverUtils.cpp --- a/lld/MachO/DriverUtils.cpp +++ b/lld/MachO/DriverUtils.cpp @@ -173,7 +173,8 @@ static DenseMap loadedDylibs; Optional macho::loadDylib(MemoryBufferRef mbref, - DylibFile *umbrella) { + DylibFile *umbrella, + bool isBundleLoader) { StringRef path = mbref.getBufferIdentifier(); DylibFile *&file = loadedDylibs[CachedHashStringRef(path)]; if (file) @@ -187,11 +188,13 @@ ": " + toString(result.takeError())); return {}; } - file = make(**result, umbrella); + file = make(**result, umbrella, isBundleLoader); } else { assert(magic == file_magic::macho_dynamically_linked_shared_lib || - magic == file_magic::macho_dynamically_linked_shared_lib_stub); - file = make(mbref, umbrella); + magic == file_magic::macho_dynamically_linked_shared_lib_stub || + magic == file_magic::macho_executable || + magic == file_magic::macho_bundle); + file = make(mbref, umbrella, isBundleLoader); } return file; } diff --git a/lld/MachO/InputFiles.h b/lld/MachO/InputFiles.h --- a/lld/MachO/InputFiles.h +++ b/lld/MachO/InputFiles.h @@ -125,10 +125,12 @@ // 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); + explicit DylibFile(MemoryBufferRef mb, DylibFile *umbrella = nullptr, + bool isBundleLoader = false); explicit DylibFile(const llvm::MachO::InterfaceFile &interface, - DylibFile *umbrella = nullptr); + DylibFile *umbrella = nullptr, + bool isBundleLoader = false); static bool classof(const InputFile *f) { return f->kind() == DylibKind; } @@ -139,6 +141,7 @@ RefState refState; bool reexport = false; bool forceWeakImport = false; + bool isBundleLoader; }; // .a file diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp --- a/lld/MachO/InputFiles.cpp +++ b/lld/MachO/InputFiles.cpp @@ -606,8 +606,10 @@ inputFiles.insert(*reexport); } -DylibFile::DylibFile(MemoryBufferRef mb, DylibFile *umbrella) - : InputFile(DylibKind, mb), refState(RefState::Unreferenced) { +DylibFile::DylibFile(MemoryBufferRef mb, DylibFile *umbrella, + bool isBundleLoader) + : InputFile(DylibKind, mb), refState(RefState::Unreferenced), + isBundleLoader(isBundleLoader) { if (umbrella == nullptr) umbrella = this; @@ -620,13 +622,15 @@ currentVersion = read32le(&c->dylib.current_version); compatibilityVersion = read32le(&c->dylib.compatibility_version); dylibName = reinterpret_cast(cmd) + read32le(&c->dylib.name); - } else { + } else if (!isBundleLoader) { + // macho_executable and macho_bundle don't have LC_ID_DYLIB error("dylib " + toString(this) + " missing LC_ID_DYLIB load command"); return; } // Initialize symbols. - DylibFile *exportingFile = isImplicitlyLinked(dylibName) ? this : umbrella; + DylibFile *exportingFile = + (isBundleLoader || isImplicitlyLinked(dylibName)) ? this : umbrella; if (const load_command *cmd = findCommand(hdr, LC_DYLD_INFO_ONLY)) { auto *c = reinterpret_cast(cmd); parseTrie(buf + c->export_off, c->export_size, @@ -659,8 +663,10 @@ } } -DylibFile::DylibFile(const InterfaceFile &interface, DylibFile *umbrella) - : InputFile(DylibKind, interface), refState(RefState::Unreferenced) { +DylibFile::DylibFile(const InterfaceFile &interface, DylibFile *umbrella, + bool isBundleLoader) + : InputFile(DylibKind, interface), refState(RefState::Unreferenced), + isBundleLoader(isBundleLoader) { if (umbrella == nullptr) umbrella = this; diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td --- a/lld/MachO/Options.td +++ b/lld/MachO/Options.td @@ -396,7 +396,6 @@ def bundle_loader : Separate<["-"], "bundle_loader">, MetaVarName<"">, HelpText<"Resolve undefined symbols from ">, - Flags<[HelpHidden]>, Group; def grp_object : OptionGroup<"object">, HelpText<"CREATING AN OBJECT FILE">; diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp --- a/lld/MachO/SyntheticSections.cpp +++ b/lld/MachO/SyntheticSections.cpp @@ -789,6 +789,8 @@ MachO::SET_LIBRARY_ORDINAL(n_desc, dysym->file->ordinal); nList->n_type = MachO::N_EXT; n_desc |= dysym->isWeakRef() ? MachO::N_WEAK_REF : 0; + if (dysym->file->isBundleLoader) + MachO::SET_LIBRARY_ORDINAL(n_desc, MachO::EXECUTABLE_ORDINAL); nList->n_desc = n_desc; } ++nList; diff --git a/lld/test/MachO/bundle_loader-darwin.test b/lld/test/MachO/bundle_loader-darwin.test new file mode 100644 --- /dev/null +++ b/lld/test/MachO/bundle_loader-darwin.test @@ -0,0 +1,65 @@ +REQUIRES: x86 + +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/2.s -o %t/2.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/3.s -o %t/3.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/main.s -o %t/main.o + +# Smoke check to ensure the object files are produced correctly. +# RUN: llvm-nm -p %t/2.o | FileCheck %s --check-prefix TWO_CHECK +# TWO_CHECK: 0000000000000000 T my_func + +# RUN: llvm-nm -p %t/3.o | FileCheck %s --check-prefix THREE_CHECK +# THREE_CHECK: t my_user +# THREE_CHECK: U my_func + +# RUN: llvm-nm -p %t/main.o | FileCheck %s --check-prefix MAIN_CHECK +# MAIN_CHECK: T _main + +# Produce the dylib +# RUN: %lld -dylib -install_name %t/my_lib.dylib -o %t/mylib.dylib %t/2.o + +# Produce main (with dyblib) +# RUN: %lld %t/2.o %t/main.o -o %t/main + +# Produce bundle.bunlde which uses `main` as bundle_loader +# RUN: %lld -lSystem -demangle -dynamic -bundle -bundle_loader %t/main -o %t/bundle.bundle %t/3.o %t/mylib.dylib + +# Check bundle.bundle to ensure the `my_func` symbol is from executable +# RUN: llvm-nm -m %t/bundle.bundle | grep 'my_func' | FileCheck %s --check-prefix BUNDLE_CHECK +# BUNDLE_CHECK: (undefined) external my_func (from executable) + +#--- 2.s +# my_lib: This contains the exported function +.globl my_func +my_func: + pushq %rbp + movq %rsp, %rbp + movl $2021, %eax + popq %rbp + retq + +#--- 3.s +# my_user.s: This is the user/caller of the +# exported function +.text +my_user: # @my_user() + pushq %rbp + movq %rsp, %rbp + callq my_func() + popq %rbp + retq + +#--- main.s +# main.s: dummy exec/main loads the exported function. +# This is basically a way to say `my_user` should get +# `my_func` from this executable. +.globl _main +.text + _main: # @main + pushq %rbp + movq %rsp, %rbp + movl $0, -4(%rbp) + xorl %eax, %eax + popq %rbp + retq \ No newline at end of file