Index: lld/MachO/Arch/ARM.cpp =================================================================== --- lld/MachO/Arch/ARM.cpp +++ lld/MachO/Arch/ARM.cpp @@ -32,7 +32,7 @@ void relocateOne(uint8_t *loc, const Reloc &, uint64_t va, uint64_t pc) const override; - void writeStub(uint8_t *buf, const Symbol &) const override; + void writeStub(uint8_t *buf, const Symbol &, uint64_t) const override; void writeStubHelperHeader(uint8_t *buf) const override; void writeStubHelperEntry(uint8_t *buf, const Symbol &, uint64_t entryAddr) const override; @@ -140,7 +140,7 @@ } } -void ARM::writeStub(uint8_t *buf, const Symbol &sym) const { +void ARM::writeStub(uint8_t *buf, const Symbol &sym, uint64_t) const { fatal("TODO: implement this"); } Index: lld/MachO/Arch/ARM64.cpp =================================================================== --- lld/MachO/Arch/ARM64.cpp +++ lld/MachO/Arch/ARM64.cpp @@ -31,7 +31,7 @@ struct ARM64 : ARM64Common { ARM64(); - void writeStub(uint8_t *buf, const Symbol &) const override; + void writeStub(uint8_t *buf, const Symbol &, uint64_t) const override; void writeStubHelperHeader(uint8_t *buf) const override; void writeStubHelperEntry(uint8_t *buf, const Symbol &, uint64_t entryAddr) const override; @@ -77,8 +77,9 @@ 0xd61f0200, // 08: br x16 }; -void ARM64::writeStub(uint8_t *buf8, const Symbol &sym) const { - ::writeStub(buf8, stubCode, sym); +void ARM64::writeStub(uint8_t *buf8, const Symbol &sym, + uint64_t pointerVA) const { + ::writeStub(buf8, stubCode, sym, pointerVA); } static constexpr uint32_t stubHelperHeaderCode[] = { Index: lld/MachO/Arch/ARM64Common.h =================================================================== --- lld/MachO/Arch/ARM64Common.h +++ lld/MachO/Arch/ARM64Common.h @@ -107,18 +107,15 @@ return address & pageMask; } -template inline void writeStub(uint8_t *buf8, const uint32_t stubCode[3], - const macho::Symbol &sym) { + const macho::Symbol &sym, uint64_t pointerVA) { auto *buf32 = reinterpret_cast(buf8); constexpr size_t stubCodeSize = 3 * sizeof(uint32_t); SymbolDiagnostic d = {&sym, "stub"}; uint64_t pcPageBits = pageBits(in.stubs->addr + sym.stubsIndex * stubCodeSize); - uint64_t lazyPointerVA = - in.lazyPointers->addr + sym.stubsIndex * LP::wordSize; - encodePage21(&buf32[0], d, stubCode[0], pageBits(lazyPointerVA) - pcPageBits); - encodePageOff12(&buf32[1], d, stubCode[1], lazyPointerVA); + encodePage21(&buf32[0], d, stubCode[0], pageBits(pointerVA) - pcPageBits); + encodePageOff12(&buf32[1], d, stubCode[1], pointerVA); buf32[2] = stubCode[2]; } Index: lld/MachO/Arch/ARM64_32.cpp =================================================================== --- lld/MachO/Arch/ARM64_32.cpp +++ lld/MachO/Arch/ARM64_32.cpp @@ -29,7 +29,7 @@ struct ARM64_32 : ARM64Common { ARM64_32(); - void writeStub(uint8_t *buf, const Symbol &) const override; + void writeStub(uint8_t *buf, const Symbol &, uint64_t) const override; void writeStubHelperHeader(uint8_t *buf) const override; void writeStubHelperEntry(uint8_t *buf, const Symbol &, uint64_t entryAddr) const override; @@ -70,8 +70,9 @@ 0xd61f0200, // 08: br x16 }; -void ARM64_32::writeStub(uint8_t *buf8, const Symbol &sym) const { - ::writeStub(buf8, stubCode, sym); +void ARM64_32::writeStub(uint8_t *buf8, const Symbol &sym, + uint64_t pointerVA) const { + ::writeStub(buf8, stubCode, sym, pointerVA); } static constexpr uint32_t stubHelperHeaderCode[] = { Index: lld/MachO/Arch/X86_64.cpp =================================================================== --- lld/MachO/Arch/X86_64.cpp +++ lld/MachO/Arch/X86_64.cpp @@ -31,7 +31,8 @@ void relocateOne(uint8_t *loc, const Reloc &, uint64_t va, uint64_t relocVA) const override; - void writeStub(uint8_t *buf, const Symbol &) const override; + void writeStub(uint8_t *buf, const Symbol &, + uint64_t pointerVA) const override; void writeStubHelperHeader(uint8_t *buf) const override; void writeStubHelperEntry(uint8_t *buf, const Symbol &, uint64_t entryAddr) const override; @@ -138,11 +139,11 @@ 0xff, 0x25, 0, 0, 0, 0, // jmpq *__la_symbol_ptr(%rip) }; -void X86_64::writeStub(uint8_t *buf, const Symbol &sym) const { +void X86_64::writeStub(uint8_t *buf, const Symbol &sym, + uint64_t pointerVA) const { memcpy(buf, stub, 2); // just copy the two nonzero bytes uint64_t stubAddr = in.stubs->addr + sym.stubsIndex * sizeof(stub); - writeRipRelative({&sym, "stub"}, buf, stubAddr, sizeof(stub), - in.lazyPointers->addr + sym.stubsIndex * LP64::wordSize); + writeRipRelative({&sym, "stub"}, buf, stubAddr, sizeof(stub), pointerVA); } static constexpr uint8_t stubHelperHeader[] = { Index: lld/MachO/Config.h =================================================================== --- lld/MachO/Config.h +++ lld/MachO/Config.h @@ -132,6 +132,7 @@ bool emitDataInCodeInfo = false; bool emitEncryptionInfo = false; bool emitInitOffsets = false; + bool emitChainedFixups = false; bool timeTraceEnabled = false; bool dataConst = false; bool dedupLiterals = true; Index: lld/MachO/Driver.cpp =================================================================== --- lld/MachO/Driver.cpp +++ lld/MachO/Driver.cpp @@ -957,6 +957,47 @@ return false; } +static bool shouldEmitChainedFixups(const InputArgList &args) { + const Arg *arg = args.getLastArg(OPT_fixup_chains, OPT_no_fixup_chains); + if (arg && arg->getOption().matches(OPT_no_fixup_chains)) + return false; + + bool isRequested = arg != nullptr; + + // Version numbers taken from the Xcode 13.3 release notes. + static const std::array, 4> minVersion = + {{{PLATFORM_MACOS, VersionTuple(11, 0)}, + {PLATFORM_IOS, VersionTuple(13, 4)}, + {PLATFORM_TVOS, VersionTuple(14, 0)}, + {PLATFORM_WATCHOS, VersionTuple(7, 0)}}}; + PlatformType platform = removeSimulator(config->platformInfo.target.Platform); + auto it = llvm::find_if(minVersion, + [&](const auto &p) { return p.first == platform; }); + if (it != minVersion.end() && it->second > config->platformInfo.minimum) { + if (!isRequested) + return false; + + warn("-fixup_chains requires " + getPlatformName(config->platform()) + " " + + it->second.getAsString() + ", which is newer than target minimum of " + + config->platformInfo.minimum.getAsString()); + } + + if (!is_contained({AK_x86_64, AK_x86_64h, AK_arm64}, config->arch())) { + if (isRequested) + error("-fixup_chains is only supported on x86_64 and arm64 targets"); + return false; + } + + if (!config->isPic) { + if (isRequested) + error("-fixup_chains is incompatible with -no_pie"); + return false; + } + + // TODO: Enable by default once stable. + return isRequested; +} + void SymbolPatterns::clear() { literals.clear(); globs.clear(); @@ -1376,6 +1417,11 @@ } } + config->isPic = config->outputType == MH_DYLIB || + config->outputType == MH_BUNDLE || + (config->outputType == MH_EXECUTE && + args.hasFlag(OPT_pie, OPT_no_pie, true)); + // Must be set before any InputSections and Symbols are created. config->deadStrip = args.hasArg(OPT_dead_strip); @@ -1475,7 +1521,9 @@ config->emitBitcodeBundle = args.hasArg(OPT_bitcode_bundle); config->emitDataInCodeInfo = args.hasFlag(OPT_data_in_code_info, OPT_no_data_in_code_info, true); - config->emitInitOffsets = args.hasArg(OPT_init_offsets); + config->emitChainedFixups = shouldEmitChainedFixups(args); + config->emitInitOffsets = + config->emitChainedFixups || args.hasArg(OPT_init_offsets); config->icfLevel = getICFLevel(args); config->dedupLiterals = args.hasFlag(OPT_deduplicate_literals, OPT_icf_eq, false) || @@ -1698,11 +1746,6 @@ initLLVM(); // must be run before any call to addFile() createFiles(args); - config->isPic = config->outputType == MH_DYLIB || - config->outputType == MH_BUNDLE || - (config->outputType == MH_EXECUTE && - args.hasFlag(OPT_pie, OPT_no_pie, true)); - // Now that all dylibs have been loaded, search for those that should be // re-exported. { Index: lld/MachO/InputSection.h =================================================================== --- lld/MachO/InputSection.h +++ lld/MachO/InputSection.h @@ -299,6 +299,7 @@ constexpr const char cString[] = "__cstring"; constexpr const char cfString[] = "__cfstring"; constexpr const char cgProfile[] = "__cg_profile"; +constexpr const char chainFixups[] = "__chainfixups"; constexpr const char codeSignature[] = "__code_signature"; constexpr const char common[] = "__common"; constexpr const char compactUnwind[] = "__compact_unwind"; Index: lld/MachO/InputSection.cpp =================================================================== --- lld/MachO/InputSection.cpp +++ lld/MachO/InputSection.cpp @@ -181,6 +181,9 @@ const Reloc &r = relocs[i]; uint8_t *loc = buf + r.offset; uint64_t referentVA = 0; + + const bool needsFixup = config->emitChainedFixups && + target->hasAttr(r.type, RelocAttrBits::UNSIGNED); if (target->hasAttr(r.type, RelocAttrBits::SUBTRAHEND)) { const Symbol *fromSym = r.referent.get(); const Reloc &minuend = relocs[++i]; @@ -205,17 +208,24 @@ } referentVA = resolveSymbolVA(referentSym, r.type) + r.addend; - if (isThreadLocalVariables(getFlags())) { + if (isThreadLocalVariables(getFlags()) && isa(referentSym)) { // References from thread-local variable sections are treated as offsets // relative to the start of the thread-local data memory area, which // is initialized via copying all the TLV data sections (which are all // contiguous). - if (isa(referentSym)) - referentVA -= firstTLVDataSection->addr; + referentVA -= firstTLVDataSection->addr; + } else if (needsFixup) { + writeChainedFixup(loc, referentSym, r.addend); + continue; } } else if (auto *referentIsec = r.referent.dyn_cast()) { assert(!::shouldOmitFromOutput(referentIsec)); referentVA = referentIsec->getVA(r.addend); + + if (needsFixup) { + writeChainedRebase(loc, referentVA); + continue; + } } target->relocateOne(loc, r, referentVA, getVA() + r.offset); } Index: lld/MachO/Options.td =================================================================== --- lld/MachO/Options.td +++ lld/MachO/Options.td @@ -1230,8 +1230,10 @@ Flags<[HelpHidden]>, Group; def fixup_chains : Flag<["-"], "fixup_chains">, - HelpText<"This option is undocumented in ld64">, - Flags<[HelpHidden]>, + HelpText<"Emit chained fixups">, + Group; +def no_fixup_chains : Flag<["-"], "no_fixup_chains">, + HelpText<"Emit fixup information as classic dyld opcodes">, Group; def fixup_chains_section : Flag<["-"], "fixup_chains_section">, HelpText<"This option is undocumented in ld64">, Index: lld/MachO/Symbols.h =================================================================== --- lld/MachO/Symbols.h +++ lld/MachO/Symbols.h @@ -75,6 +75,7 @@ bool isInStubs() const { return stubsIndex != UINT32_MAX; } uint64_t getStubVA() const; + uint64_t getLazyPtrVA() const; uint64_t getGotVA() const; uint64_t getTlvVA() const; uint64_t resolveBranchVA() const { Index: lld/MachO/Symbols.cpp =================================================================== --- lld/MachO/Symbols.cpp +++ lld/MachO/Symbols.cpp @@ -37,6 +37,9 @@ } uint64_t Symbol::getStubVA() const { return in.stubs->getVA(stubsIndex); } +uint64_t Symbol::getLazyPtrVA() const { + return in.lazyPointers->getVA(stubsIndex); +} uint64_t Symbol::getGotVA() const { return in.got->getVA(gotIndex); } uint64_t Symbol::getTlvVA() const { return in.tlvPointers->getVA(gotIndex); } Index: lld/MachO/SyntheticSections.h =================================================================== --- lld/MachO/SyntheticSections.h +++ lld/MachO/SyntheticSections.h @@ -21,6 +21,7 @@ #include "llvm/ADT/Hashing.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SetVector.h" +#include "llvm/BinaryFormat/MachO.h" #include "llvm/MC/StringTableBuilder.h" #include "llvm/Support/MathExtras.h" #include "llvm/Support/raw_ostream.h" @@ -270,6 +271,12 @@ // order that the weak bindings may overwrite the non-lazy bindings if an // appropriate symbol is found at runtime. However, the bound addresses will // still be written (non-lazily) into the LazyPointerSection. +// +// Symbols are always bound eagerly when chained fixups are used. In that case, +// StubsSection contains indirect jumps to addresses stored in the GotSection. +// The GOT directly contains the fixup entries, which will be replaced by the +// address of the target symbols on load. LazyPointerSection and +// StubHelperSection are not used. class StubsSection final : public SyntheticSection { public: @@ -342,6 +349,9 @@ uint64_t getSize() const override; bool isNeeded() const override; void writeTo(uint8_t *buf) const override; + uint64_t getVA(uint32_t index) const { + return addr + (index << target->p2WordSize); + } }; class LazyBindingSection final : public LinkEditSection { @@ -674,6 +684,111 @@ std::vector sections; }; +// Chained fixups are a replacement for classic dyld opcodes. In this format, +// most of the metadata necessary for binding symbols and rebasing addresses is +// stored directly in the memory location that will have the fixup applied. +// +// The fixups form singly linked lists; each one covering a single page in +// memory. The __LINKEDIT,__chainfixups section stores the page offset of the +// first fixup of each page; the rest can be found by walking the chain using +// the offset that is embedded in each entry. +// +// This setup allows pages to be relocated lazily at page-in time and without +// being dirtied. The kernel can discard and load them again as needed. This +// technique, called page-in linking, was introduced in macOS 13. +// +// The benefits of this format are: +// - smaller __LINKEDIT segment, as most of the fixup information is stored in +// the data segment +// - faster startup, since not all relocations need to be done upfront +// - slightly lower memory usage, as fewer pages are dirtied +// +// Userspace x86_64 and arm64 binaries have two types of fixup entries: +// - Rebase entries contain an absolute address, to which the object's load +// address will be added to get the final value. This is used for loading +// the address of a symbol defined in the same binary. +// - Binding entries are mostly used for symbols imported from other dylibs, +// but for weakly bound and interposable symbols as well. They are looked up +// by a (symbol name, library) pair stored in __chainfixups. This import +// entry also encodes whether the import is weak (i.e. if the symbol is +// missing, it should be set to null instead of producing a load error). +// The fixup encodes an ordinal associated with the import, and an optional +// addend. +// +// The entries are tightly packed 64-bit bitfields. One of the bits specifies +// which kind of fixup to interpret them as. +// +// LLD generates the fixup data in 5 stages: +// 1. While scanning relocations, we make a note of each location that needs +// a fixup by calling addRebase() or addBinding(). During this, we assign +// a unique ordinal for each (symbol name, library, addend) import tuple. +// 2. After addresses have been assigned to all sections, and thus the memory +// layout of the linked image is final; finalizeContents() is called. Here, +// the page offsets of the chain start entries are calculated. +// 3. ChainedFixupsSection::writeTo() writes the page start offsets and the +// imports table to the output file. +// 4. Each section's fixup entries are encoded and written to disk in +// ConcatInputSection::writeTo(), but without writing the offsets that form +// the chain. +// 5. Finally, each page's (which might correspond to multiple sections) +// fixups are linked together in Writer::buildFixupChains(). +class ChainedFixupsSection final : public LinkEditSection { +public: + ChainedFixupsSection(); + void finalizeContents() override; + uint64_t getRawSize() const override { return size; } + bool isNeeded() const override; + void writeTo(uint8_t *buf) const override; + + void addRebase(const InputSection *isec, uint64_t offset) { + locations.emplace_back(isec, offset); + } + void addBinding(const Symbol *dysym, const InputSection *isec, + uint64_t offset, int64_t addend = 0); + + void setHasNonWeakDefinition() { hasNonWeakDef = true; } + + // Returns an (ordinal, inline addend) tuple used by dyld_chained_ptr_64_bind. + std::pair getBinding(const Symbol *sym, + int64_t addend) const; + + const std::vector &getLocations() const { return locations; } + + bool hasWeakBinding() const { return hasWeakBind; } + bool hasNonWeakDefinition() const { return hasNonWeakDef; } + +private: + // Location::offset initially stores the offset within an InputSection, but + // contains output segment offsets after finalizeContents(). + std::vector locations; + // (target symbol, addend) => import ordinal + llvm::MapVector, uint32_t> bindings; + + struct SegmentInfo { + SegmentInfo(const OutputSegment *oseg) : oseg(oseg) {} + + const OutputSegment *oseg; + // (page index, fixup starts offset) + llvm::SmallVector> pageStarts; + + size_t getSize() const; + size_t writeTo(uint8_t *buf) const; + }; + llvm::SmallVector fixupSegments; + + size_t symtabSize = 0; + size_t size = 0; + + bool needsAddend = false; + bool needsLargeAddend = false; + bool hasWeakBind = false; + bool hasNonWeakDef = false; + llvm::MachO::ChainedImportFormat importFormat; +}; + +void writeChainedRebase(uint8_t *buf, uint64_t targetVA); +void writeChainedFixup(uint8_t *buf, const Symbol *sym, int64_t addend); + struct InStruct { const uint8_t *bufferStart = nullptr; MachHeaderSection *header = nullptr; @@ -696,6 +811,7 @@ ObjCImageInfoSection *objCImageInfo = nullptr; ConcatInputSection *imageLoaderCache = nullptr; InitOffsetsSection *initOffsets = nullptr; + ChainedFixupsSection *chainedFixups = nullptr; }; extern InStruct in; Index: lld/MachO/SyntheticSections.cpp =================================================================== --- lld/MachO/SyntheticSections.cpp +++ lld/MachO/SyntheticSections.cpp @@ -111,6 +111,16 @@ return subtype; } +static bool hasWeakBinding() { + return config->emitChainedFixups ? in.chainedFixups->hasWeakBinding() + : in.weakBinding->hasEntry(); +} + +static bool hasNonWeakDefinition() { + return config->emitChainedFixups ? in.chainedFixups->hasNonWeakDefinition() + : in.weakBinding->hasNonWeakDefinition(); +} + void MachHeaderSection::writeTo(uint8_t *buf) const { auto *hdr = reinterpret_cast(buf); hdr->magic = target->magic; @@ -136,10 +146,10 @@ if (config->outputType == MH_DYLIB && config->applicationExtension) hdr->flags |= MH_APP_EXTENSION_SAFE; - if (in.exports->hasWeakSymbol || in.weakBinding->hasNonWeakDefinition()) + if (in.exports->hasWeakSymbol || hasNonWeakDefinition()) hdr->flags |= MH_WEAK_DEFINES; - if (in.exports->hasWeakSymbol || in.weakBinding->hasEntry()) + if (in.exports->hasWeakSymbol || hasWeakBinding()) hdr->flags |= MH_BINDS_TO_WEAK; for (const OutputSegment *seg : outputSegments) { @@ -304,6 +314,16 @@ void macho::addNonLazyBindingEntries(const Symbol *sym, const InputSection *isec, uint64_t offset, int64_t addend) { + if (config->emitChainedFixups) { + if (needsBinding(sym)) + in.chainedFixups->addBinding(sym, isec, offset, addend); + else if (isa(sym)) + in.chainedFixups->addRebase(isec, offset); + else + llvm_unreachable("cannot bind to an undefined symbol"); + return; + } + if (const auto *dysym = dyn_cast(sym)) { in.binding->addEntry(dysym, isec, offset, addend); if (dysym->isWeakDef()) @@ -330,10 +350,52 @@ } } +void macho::writeChainedRebase(uint8_t *buf, uint64_t targetVA) { + assert(config->emitChainedFixups); + assert(target->wordSize == 8 && "Only 64-bit platforms are supported"); + auto *rebase = reinterpret_cast(buf); + rebase->target = targetVA & 0xf'ffff'ffff; + rebase->high8 = (targetVA >> 56); + rebase->reserved = 0; + rebase->next = 0; + rebase->bind = 0; + + // The fixup format places a 64 GiB limit on the output's size. + // Should we handle this gracefully? + uint64_t encodedVA = rebase->target | ((uint64_t)rebase->high8 << 56); + if (encodedVA != targetVA) + error("rebase target address 0x" + Twine::utohexstr(targetVA) + + " does not fit into chained fixup. Re-link with -no_fixup_chains"); +} + +static void writeChainedBind(uint8_t *buf, const Symbol *sym, int64_t addend) { + assert(config->emitChainedFixups); + assert(target->wordSize == 8 && "Only 64-bit platforms are supported"); + auto *bind = reinterpret_cast(buf); + auto [ordinal, inlineAddend] = in.chainedFixups->getBinding(sym, addend); + bind->ordinal = ordinal; + bind->addend = inlineAddend; + bind->reserved = 0; + bind->next = 0; + bind->bind = 1; +} + +void macho::writeChainedFixup(uint8_t *buf, const Symbol *sym, int64_t addend) { + if (needsBinding(sym)) + writeChainedBind(buf, sym, addend); + else + writeChainedRebase(buf, sym->getVA() + addend); +} + void NonLazyPointerSectionBase::writeTo(uint8_t *buf) const { - for (size_t i = 0, n = entries.size(); i < n; ++i) - if (auto *defined = dyn_cast(entries[i])) - write64le(&buf[i * target->wordSize], defined->getVA()); + if (config->emitChainedFixups) { + for (size_t i = 0, n = entries.size(); i < n; ++i) + writeChainedFixup(&buf[i * target->wordSize], entries[i], 0); + } else { + for (size_t i = 0, n = entries.size(); i < n; ++i) + if (auto *defined = dyn_cast(entries[i])) + write64le(&buf[i * target->wordSize], defined->getVA()); + } } GotSection::GotSection() @@ -652,7 +714,9 @@ void StubsSection::writeTo(uint8_t *buf) const { size_t off = 0; for (const Symbol *sym : entries) { - target->writeStub(buf + off, *sym); + uint64_t pointerVA = + config->emitChainedFixups ? sym->getGotVA() : sym->getLazyPtrVA(); + target->writeStub(buf + off, *sym, pointerVA); off += target->stubSize; } } @@ -660,6 +724,7 @@ void StubsSection::finalize() { isFinal = true; } static void addBindingsForStub(Symbol *sym) { + assert(!config->emitChainedFixups); if (auto *dysym = dyn_cast(sym)) { if (sym->isWeakDef()) { in.binding->addEntry(dysym, in.lazyPointers->isec, @@ -689,7 +754,11 @@ bool inserted = entries.insert(sym); if (inserted) { sym->stubsIndex = entries.size() - 1; - addBindingsForStub(sym); + + if (config->emitChainedFixups) + in.got->addEntry(sym); + else + addBindingsForStub(sym); } } @@ -864,6 +933,7 @@ } void LazyBindingSection::addEntry(Symbol *sym) { + assert(!config->emitChainedFixups && "Chained fixups always bind eagerly"); if (entries.insert(sym)) { sym->stubsHelperIndex = entries.size() - 1; in.rebase->addEntry(in.lazyPointers->isec, @@ -1188,8 +1258,8 @@ // __dyld_private is a local symbol too. It's linker-created and doesn't // exist in any object file. - if (Defined *dyldPrivate = in.stubHelper->dyldPrivate) - localSymbolsHandler(dyldPrivate); + if (in.stubHelper && in.stubHelper->dyldPrivate) + localSymbolsHandler(in.stubHelper->dyldPrivate); for (Symbol *sym : symtab->getSymbols()) { if (!sym->isLive()) @@ -1311,8 +1381,12 @@ section_names::indirectSymbolTable) {} uint32_t IndirectSymtabSection::getNumSymbols() const { - return in.got->getEntries().size() + in.tlvPointers->getEntries().size() + - 2 * in.stubs->getEntries().size(); + uint32_t size = in.got->getEntries().size() + + in.tlvPointers->getEntries().size() + + in.stubs->getEntries().size(); + if (!config->emitChainedFixups) + size += in.stubs->getEntries().size(); + return size; } bool IndirectSymtabSection::isNeeded() const { @@ -1327,8 +1401,10 @@ in.tlvPointers->reserved1 = off; off += in.tlvPointers->getEntries().size(); in.stubs->reserved1 = off; - off += in.stubs->getEntries().size(); - in.lazyPointers->reserved1 = off; + if (in.lazyPointers) { + off += in.stubs->getEntries().size(); + in.lazyPointers->reserved1 = off; + } } static uint32_t indirectValue(const Symbol *sym) { @@ -1354,14 +1430,17 @@ write32le(buf + off * sizeof(uint32_t), indirectValue(sym)); ++off; } - // There is a 1:1 correspondence between stubs and LazyPointerSection - // entries. But giving __stubs and __la_symbol_ptr the same reserved1 - // (the offset into the indirect symbol table) so that they both refer - // to the same range of offsets confuses `strip`, so write the stubs - // symbol table offsets a second time. - for (const Symbol *sym : in.stubs->getEntries()) { - write32le(buf + off * sizeof(uint32_t), indirectValue(sym)); - ++off; + + if (in.lazyPointers) { + // There is a 1:1 correspondence between stubs and LazyPointerSection + // entries. But giving __stubs and __la_symbol_ptr the same reserved1 + // (the offset into the indirect symbol table) so that they both refer + // to the same range of offsets confuses `strip`, so write the stubs + // symbol table offsets a second time. + for (const Symbol *sym : in.stubs->getEntries()) { + write32le(buf + off * sizeof(uint32_t), indirectValue(sym)); + ++off; + } } } @@ -1932,5 +2011,246 @@ addHeaderSymbol("___dso_handle"); } +ChainedFixupsSection::ChainedFixupsSection() + : LinkEditSection(segment_names::linkEdit, section_names::chainFixups) {} + +bool ChainedFixupsSection::isNeeded() const { + assert(config->emitChainedFixups); + // dyld always expects LC_DYLD_CHAINED_FIXUPS to point to a valid + // dyld_chained_fixups_header, so we create this section even if there aren't + // any fixups. + return true; +} + +static bool needsWeakBind(const Symbol &sym) { + if (auto *dysym = dyn_cast(&sym)) + return dysym->isWeakDef(); + if (auto *defined = dyn_cast(&sym)) + return defined->isExternalWeakDef(); + return false; +} + +void ChainedFixupsSection::addBinding(const Symbol *sym, + const InputSection *isec, uint64_t offset, + int64_t addend) { + locations.emplace_back(isec, offset); + int64_t outlineAddend = (addend < 0 || addend > 0xFF) ? addend : 0; + auto [it, inserted] = bindings.insert( + {{sym, outlineAddend}, static_cast(bindings.size())}); + + if (inserted) { + symtabSize += sym->getName().size() + 1; + hasWeakBind = hasWeakBind || needsWeakBind(*sym); + if (!isInt<23>(outlineAddend)) + needsLargeAddend = true; + else if (outlineAddend != 0) + needsAddend = true; + } +} + +std::pair +ChainedFixupsSection::getBinding(const Symbol *sym, int64_t addend) const { + int64_t outlineAddend = (addend < 0 || addend > 0xFF) ? addend : 0; + auto it = bindings.find({sym, outlineAddend}); + assert(it != bindings.end() && "binding not found in the imports table"); + if (outlineAddend == 0) + return {it->second, addend}; + return {it->second, 0}; +} + +static size_t writeImport(uint8_t *buf, int format, uint32_t libOrdinal, + bool weakRef, uint32_t nameOffset, int64_t addend) { + switch (format) { + case DYLD_CHAINED_IMPORT: { + auto *import = reinterpret_cast(buf); + import->lib_ordinal = libOrdinal; + import->weak_import = weakRef; + import->name_offset = nameOffset; + return sizeof(dyld_chained_import); + } + case DYLD_CHAINED_IMPORT_ADDEND: { + auto *import = reinterpret_cast(buf); + import->lib_ordinal = libOrdinal; + import->weak_import = weakRef; + import->name_offset = nameOffset; + import->addend = addend; + return sizeof(dyld_chained_import_addend); + } + case DYLD_CHAINED_IMPORT_ADDEND64: { + auto *import = reinterpret_cast(buf); + import->lib_ordinal = libOrdinal; + import->weak_import = weakRef; + import->name_offset = nameOffset; + import->addend = addend; + return sizeof(dyld_chained_import_addend64); + } + default: + llvm_unreachable("Unknown import format"); + } +} + +size_t ChainedFixupsSection::SegmentInfo::getSize() const { + assert(pageStarts.size() > 0 && "SegmentInfo for segment with no fixups?"); + return alignTo<8>(sizeof(dyld_chained_starts_in_segment) + + pageStarts.back().first * sizeof(uint16_t)); +} + +size_t ChainedFixupsSection::SegmentInfo::writeTo(uint8_t *buf) const { + auto *segInfo = reinterpret_cast(buf); + segInfo->size = getSize(); + segInfo->page_size = target->getPageSize(); + // FIXME: Use DYLD_CHAINED_PTR_64_OFFSET on newer OS versions. + segInfo->pointer_format = DYLD_CHAINED_PTR_64; + segInfo->segment_offset = oseg->addr - in.header->addr; + segInfo->max_valid_pointer = 0; // not used on 64-bit + segInfo->page_count = pageStarts.back().first + 1; + + uint16_t *starts = segInfo->page_start; + for (size_t i = 0; i < segInfo->page_count; ++i) + starts[i] = DYLD_CHAINED_PTR_START_NONE; + + for (auto [pageIdx, startAddr] : pageStarts) + starts[pageIdx] = startAddr; + return segInfo->size; +} + +static size_t importEntrySize(int format) { + switch (format) { + case DYLD_CHAINED_IMPORT: + return sizeof(dyld_chained_import); + case DYLD_CHAINED_IMPORT_ADDEND: + return sizeof(dyld_chained_import_addend); + case DYLD_CHAINED_IMPORT_ADDEND64: + return sizeof(dyld_chained_import_addend64); + default: + llvm_unreachable("Unknown import format"); + } +} + +// This is step 3 of the algorithm described in the class comment of +// ChainedFixupsSection. +// +// LC_DYLD_CHAINED_FIXUPS data consists of (in this order): +// * A dyld_chained_fixups_header +// * A dyld_chained_starts_in_image +// * One dyld_chained_starts_in_segment per segment +// * List of all imports (dyld_chained_import, dyld_chained_import_addend, or +// dyld_chained_import_addend64) +// * Names of imported symbols +void ChainedFixupsSection::writeTo(uint8_t *buf) const { + auto *header = reinterpret_cast(buf); + header->fixups_version = 0; + header->imports_count = bindings.size(); + header->imports_format = importFormat; + header->symbols_format = 0; + + buf += alignTo<8>(sizeof(*header)); + + auto curOffset = [&buf, &header]() -> uint32_t { + return buf - reinterpret_cast(header); + }; + + header->starts_offset = curOffset(); + + auto *imageInfo = reinterpret_cast(buf); + imageInfo->seg_count = outputSegments.size(); + uint32_t *segStarts = imageInfo->seg_info_offset; + + // dyld_chained_starts_in_image ends in a flexible array member containing an + // uint32_t for each segment. Leave room for it, and fill it via segStarts. + buf += alignTo<8>(offsetof(dyld_chained_starts_in_image, seg_info_offset) + + outputSegments.size() * sizeof(uint32_t)); + + // Initialize all offsets to 0, which indicates that the segment does not have + // fixups. Those that do have them will be filled in below. + for (size_t i = 0; i < outputSegments.size(); ++i) + segStarts[i] = 0; + + for (const SegmentInfo &seg : fixupSegments) { + segStarts[seg.oseg->index] = curOffset() - header->starts_offset; + buf += seg.writeTo(buf); + } + + // Write imports table. + header->imports_offset = curOffset(); + uint64_t nameOffset = 0; + for (auto [import, idx] : bindings) { + const Symbol &sym = *import.first; + int16_t libOrdinal = needsWeakBind(sym) ? BIND_SPECIAL_DYLIB_WEAK_LOOKUP + : ordinalForSymbol(sym); + buf += writeImport(buf, importFormat, libOrdinal, sym.isWeakRef(), + nameOffset, import.second); + nameOffset += sym.getName().size() + 1; + } + + // Write imported symbol names. + header->symbols_offset = curOffset(); + for (auto [import, idx] : bindings) { + StringRef name = import.first->getName(); + memcpy(buf, name.data(), name.size()); + buf += name.size() + 1; // account for null terminator + } + + assert(curOffset() == getRawSize()); +} + +// This is step 2 of the algorithm described in the class comment of +// ChainedFixupsSection. +void ChainedFixupsSection::finalizeContents() { + assert(target->wordSize == 8 && "Only 64-bit platforms are supported"); + assert(config->emitChainedFixups); + + if (!isUInt<32>(symtabSize)) + error("cannot encode chained fixups: imported symbols table size " + + Twine(symtabSize) + " exceeds 4 GiB"); + + if (needsLargeAddend || !isUInt<23>(symtabSize)) + importFormat = DYLD_CHAINED_IMPORT_ADDEND64; + else if (needsAddend) + importFormat = DYLD_CHAINED_IMPORT_ADDEND; + else + importFormat = DYLD_CHAINED_IMPORT; + + for (Location &loc : locations) + loc.offset = + loc.isec->parent->getSegmentOffset() + loc.isec->getOffset(loc.offset); + + llvm::sort(locations, [](const Location &a, const Location &b) { + const OutputSegment *segA = a.isec->parent->parent; + const OutputSegment *segB = b.isec->parent->parent; + if (segA == segB) + return a.offset < b.offset; + return segA->addr < segB->addr; + }); + + auto sameSegment = [](const Location &a, const Location &b) { + return a.isec->parent->parent == b.isec->parent->parent; + }; + + const uint64_t pageSize = target->getPageSize(); + for (size_t i = 0, count = locations.size(); i < count;) { + const Location &firstLoc = locations[i]; + fixupSegments.emplace_back(firstLoc.isec->parent->parent); + while (i < count && sameSegment(locations[i], firstLoc)) { + uint32_t pageIdx = locations[i].offset / pageSize; + fixupSegments.back().pageStarts.emplace_back( + pageIdx, locations[i].offset % pageSize); + ++i; + while (i < count && sameSegment(locations[i], firstLoc) && + locations[i].offset / pageSize == pageIdx) + ++i; + } + } + + // Compute expected encoded size. + size = alignTo<8>(sizeof(dyld_chained_fixups_header)); + size += alignTo<8>(offsetof(dyld_chained_starts_in_image, seg_info_offset) + + outputSegments.size() * sizeof(uint32_t)); + for (const SegmentInfo &seg : fixupSegments) + size += seg.getSize(); + size += importEntrySize(importFormat) * bindings.size(); + size += symtabSize; +} + template SymtabSection *macho::makeSymtabSection(StringTableSection &); template SymtabSection *macho::makeSymtabSection(StringTableSection &); Index: lld/MachO/Target.h =================================================================== --- lld/MachO/Target.h +++ lld/MachO/Target.h @@ -52,7 +52,8 @@ // Write code for lazy binding. See the comments on StubsSection for more // details. - virtual void writeStub(uint8_t *buf, const Symbol &) const = 0; + virtual void writeStub(uint8_t *buf, const Symbol &, + uint64_t pointerVA) const = 0; virtual void writeStubHelperHeader(uint8_t *buf) const = 0; virtual void writeStubHelperEntry(uint8_t *buf, const Symbol &, uint64_t entryAddr) const = 0; Index: lld/MachO/Writer.cpp =================================================================== --- lld/MachO/Writer.cpp +++ lld/MachO/Writer.cpp @@ -61,6 +61,7 @@ void openFile(); void writeSections(); void applyOptimizationHints(); + void buildFixupChains(); void writeUuid(); void writeCodeSignature(); void writeOutputFile(); @@ -579,6 +580,40 @@ CodeSignatureSection *section; }; +class LCExportsTrie final : public LoadCommand { +public: + LCExportsTrie(ExportSection *section) : section(section) {} + + uint32_t getSize() const override { return sizeof(linkedit_data_command); } + + void writeTo(uint8_t *buf) const override { + auto *c = reinterpret_cast(buf); + c->cmd = LC_DYLD_EXPORTS_TRIE; + c->cmdsize = getSize(); + c->dataoff = section->fileOff; + c->datasize = section->getSize(); + } + + ExportSection *section; +}; + +class LCChainedFixups final : public LoadCommand { +public: + LCChainedFixups(ChainedFixupsSection *section) : section(section) {} + + uint32_t getSize() const override { return sizeof(linkedit_data_command); } + + void writeTo(uint8_t *buf) const override { + auto *c = reinterpret_cast(buf); + c->cmd = LC_DYLD_CHAINED_FIXUPS; + c->cmdsize = getSize(); + c->dataoff = section->fileOff; + c->datasize = section->getSize(); + } + + ChainedFixupsSection *section; +}; + } // namespace void Writer::treatSpecialUndefineds() { @@ -657,8 +692,12 @@ // too... auto *referentIsec = r.referent.get(); r.referent = referentIsec->canonical(); - if (!r.pcrel) - in.rebase->addEntry(isec, r.offset); + if (!r.pcrel) { + if (config->emitChainedFixups) + in.chainedFixups->addRebase(isec, r.offset); + else + in.rebase->addEntry(isec, r.offset); + } } } } @@ -666,6 +705,13 @@ in.unwindInfo->prepareRelocations(); } +static void addNonWeakDefinition(const Defined *defined) { + if (config->emitChainedFixups) + in.chainedFixups->setHasNonWeakDefinition(); + else + in.weakBinding->addNonWeakDefinition(defined); +} + void Writer::scanSymbols() { TimeTraceScope timeScope("Scan symbols"); for (Symbol *sym : symtab->getSymbols()) { @@ -674,7 +720,7 @@ continue; defined->canonicalize(); if (defined->overridesWeakDef) - in.weakBinding->addNonWeakDefinition(defined); + addNonWeakDefinition(defined); if (!defined->isAbsolute() && isCodeSection(defined->isec)) in.unwindInfo->addSymbol(defined); } else if (const auto *dysym = dyn_cast(sym)) { @@ -727,8 +773,13 @@ seg->index = segIndex++; } - in.header->addLoadCommand(make( - in.rebase, in.binding, in.weakBinding, in.lazyBinding, in.exports)); + if (config->emitChainedFixups) { + in.header->addLoadCommand(make(in.chainedFixups)); + in.header->addLoadCommand(make(in.exports)); + } else { + in.header->addLoadCommand(make( + in.rebase, in.binding, in.weakBinding, in.lazyBinding, in.exports)); + } in.header->addLoadCommand(make(symtabSection, stringTableSection)); in.header->addLoadCommand( make(symtabSection, indirectSymtabSection)); @@ -1037,16 +1088,12 @@ void Writer::finalizeLinkEditSegment() { TimeTraceScope timeScope("Finalize __LINKEDIT segment"); // Fill __LINKEDIT contents. - std::vector linkEditSections{ - in.rebase, - in.binding, - in.weakBinding, - in.lazyBinding, - in.exports, - symtabSection, - indirectSymtabSection, - dataInCodeSection, - functionStartsSection, + std::array linkEditSections{ + in.rebase, in.binding, + in.weakBinding, in.lazyBinding, + in.exports, in.chainedFixups, + symtabSection, indirectSymtabSection, + dataInCodeSection, functionStartsSection, }; SmallVector> threadFutures; threadFutures.reserve(linkEditSections.size()); @@ -1147,6 +1194,53 @@ uuidCommand->writeUuid(digest); } +// This is step 5 of the algorithm described in the class comment of +// ChainedFixupsSection. +void Writer::buildFixupChains() { + if (!config->emitChainedFixups) + return; + + const std::vector &loc = in.chainedFixups->getLocations(); + if (loc.empty()) + return; + + TimeTraceScope timeScope("Build fixup chains"); + + const uint64_t pageSize = target->getPageSize(); + constexpr uint32_t stride = 4; // for DYLD_CHAINED_PTR_64 + + for (size_t i = 0, count = loc.size(); i < count;) { + const OutputSegment *oseg = loc[i].isec->parent->parent; + uint8_t *buf = buffer->getBufferStart() + oseg->fileOff; + uint64_t pageIdx = loc[i].offset / pageSize; + ++i; + + while (i < count && loc[i].isec->parent->parent == oseg && + (loc[i].offset / pageSize) == pageIdx) { + uint64_t offset = loc[i].offset - loc[i - 1].offset; + + auto fail = [&](Twine message) { + error(loc[i].isec->getSegName() + "," + loc[i].isec->getName() + + ", offset " + + Twine(loc[i].offset - loc[i].isec->parent->getSegmentOffset()) + + ": " + message); + }; + + if (offset < target->wordSize) + return fail("fixups overlap"); + if (offset % stride != 0) + return fail( + "fixups are unaligned (offset " + Twine(offset) + + " is not a multiple of the stride). Re-link with -no_fixup_chains"); + + // The "next" field is in the same location for bind and rebase entries. + reinterpret_cast(buf + loc[i - 1].offset) + ->next = offset / stride; + ++i; + } + } +} + void Writer::writeCodeSignature() { if (codeSignatureSection) { TimeTraceScope timeScope("Write code signature"); @@ -1162,6 +1256,7 @@ return; writeSections(); applyOptimizationHints(); + buildFixupChains(); writeUuid(); writeCodeSignature(); @@ -1191,7 +1286,7 @@ if (errorCount()) return; - if (in.stubHelper->isNeeded()) + if (in.stubHelper && in.stubHelper->isNeeded()) in.stubHelper->setUp(); if (in.objCImageInfo->isNeeded()) @@ -1235,16 +1330,20 @@ make(section_names::objcMethname); in.wordLiteralSection = config->dedupLiterals ? make() : nullptr; - in.rebase = make(); - in.binding = make(); - in.weakBinding = make(); - in.lazyBinding = make(); + if (config->emitChainedFixups) { + in.chainedFixups = make(); + } else { + in.rebase = make(); + in.binding = make(); + in.weakBinding = make(); + in.lazyBinding = make(); + in.lazyPointers = make(); + in.stubHelper = make(); + } in.exports = make(); in.got = make(); in.tlvPointers = make(); - in.lazyPointers = make(); in.stubs = make(); - in.stubHelper = make(); in.objcStubs = make(); in.unwindInfo = makeUnwindInfoSection(); in.objCImageInfo = make(); Index: lld/test/MachO/chained-fixups-addend.s =================================================================== --- /dev/null +++ lld/test/MachO/chained-fixups-addend.s @@ -0,0 +1,74 @@ +# REQUIRES: x86 +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/dylib.s -o %t/dylib.o +# RUN: %lld -lSystem -dylib %t/dylib.o -o %t/libdylib.dylib + +## FileCheck does not like wrapping arithmetic, so we specify all 3 check variables manually: +## ADDEND := inline/outline addend, unsigned +## OUTLINE := outline addend, signed +## REBASE := target of rebase; 0x1000 + ADDEND, unsigned + +## We can use the DYLD_CHAINED_IMPORT import format if 0 <= ADDEND <= 255 bytes. +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/main.s -o %t/main.o --defsym ADDEND=0 +# RUN: %lld -lSystem -dylib %t/main.o -L%t -ldylib -fixup_chains -o %t/out +# RUN: llvm-objdump --macho --chained-fixups --dyld-info %t/out | \ +# RUN: FileCheck %s -D#OUTLINE=0 -D#ADDEND=0 -D#%x,REBASE=0x1000 --check-prefixes=IMPORT,COMMON +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/main.s -o %t/main.o --defsym ADDEND=255 +# RUN: %lld -lSystem -dylib %t/main.o -L%t -ldylib -fixup_chains -o %t/out +# RUN: llvm-objdump --macho --chained-fixups --dyld-info %t/out | \ +# RUN: FileCheck %s -D#OUTLINE=0 -D#ADDEND=255 -D#%x,REBASE=0x10FF --check-prefixes=IMPORT,COMMON + +## DYLD_CHAINED_IMPORT_ADDEND is used if the addend fits in a 32-bit signed integer. +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/main.s -o %t/main.o --defsym ADDEND=-1 +# RUN: %lld -lSystem -dylib %t/main.o -L%t -ldylib -fixup_chains -o %t/out +# RUN: llvm-objdump --macho --chained-fixups --dyld-info %t/out | \ +# RUN: FileCheck %s -D#%d,OUTLINE=-1 -D#%x,ADDEND=0xFFFFFFFFFFFFFFFF -D#%x,REBASE=0xFFF \ +# RUN: --check-prefixes=IMPORT-ADDEND,COMMON +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/main.s -o %t/main.o --defsym ADDEND=256 +# RUN: %lld -lSystem -dylib %t/main.o -L%t -ldylib -fixup_chains -o %t/out +# RUN: llvm-objdump --macho --chained-fixups --dyld-info %t/out | \ +# RUN: FileCheck %s -D#OUTLINE=256 -D#ADDEND=256 -D#%x,REBASE=0x1100 \ +# RUN: --check-prefixes=IMPORT-ADDEND,COMMON + +## Otherwise, DYLD_CHAINED_IMPORT_ADDEND64 is used. +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/main.s -o %t/main.o --defsym ADDEND=0x100000000 +# RUN: %lld -lSystem -dylib %t/main.o -L%t -ldylib -fixup_chains -o %t/out +# RUN: llvm-objdump --macho --chained-fixups --dyld-info %t/out | \ +# RUN: FileCheck %s -D#%x,OUTLINE=0x100000000 -D#%x,ADDEND=0x100000000 \ +# RUN: -D#%x,REBASE=0x100001000 --check-prefixes=IMPORT-ADDEND64,COMMON + +# COMMON: dyld information: +# COMMON-NEXT: segment section address pointer type addend dylib symbol/vm address +# COMMON-NEXT: __DATA __data {{.*}} bind 0x[[#%X, ADDEND]] libdylib _dysym +# COMMON-NEXT: __DATA __data {{.*}} rebase 0x[[#%X, REBASE]] + +# COMMON: chained fixups header (LC_DYLD_CHAINED_FIXUPS) +# IMPORT: imports_format = 1 (DYLD_CHAINED_IMPORT) +# IMPORT-ADDEND: imports_format = 2 (DYLD_CHAINED_IMPORT_ADDEND) +# IMPORT-ADDEND64: imports_format = 3 (DYLD_CHAINED_IMPORT_ADDEND64) + +# IMPORT: dyld chained import[0] +# IMPORT-ADDEND: dyld chained import addend[0] +# IMPORT-ADDEND64: dyld chained import addend64[0] +# COMMON-NEXT: lib_ordinal = 2 (libdylib) +# COMMON-NEXT: weak_import = 0 +# COMMON-NEXT: name_offset = 0 (_dysym) +# IMPORT-ADDEND-NEXT: addend = [[#%d, OUTLINE]] +# IMPORT-ADDEND64-NEXT: addend = [[#%d, OUTLINE]] + +#--- dylib.s +.globl _dysym + +_dysym: + ret + +#--- main.s +.globl _dysym, _local + +.data +_local: +.skip 128 + +.p2align 3 +.quad _dysym + ADDEND +.quad _local + ADDEND Index: lld/test/MachO/chained-fixups-empty.s =================================================================== --- /dev/null +++ lld/test/MachO/chained-fixups-empty.s @@ -0,0 +1,24 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o +# RUN: %lld -dylib -fixup_chains -o %t %t.o +# RUN: llvm-objdump --macho --all-headers %t | FileCheck %s + +## dyld always expects LC_DYLD_CHAINED_FIXUPS to point to a valid +## chained fixups header, even if there aren't any fixups. +# CHECK: cmd LC_DYLD_CHAINED_FIXUPS +# CHECK-NEXT: cmdsize 16 +# CHECK-NEXT: dataoff [[#]] +# CHECK-NEXT: datasize 48 + +## While ld64 emits the root trie node even if there are no exports, +## setting the data size and offset to zero works too in practice. +# CHECK: cmd LC_DYLD_EXPORTS_TRIE +# CHECK-NEXT: cmdsize 16 +# CHECK-NEXT: dataoff 0 +# CHECK-NEXT: datasize 0 + +## Old load commands should not be generated. +# CHECK-NOT: cmd LC_DYLD_INFO_ONLY + +_not_exported: + .space 1 Index: lld/test/MachO/flat-namespace-interposable.s =================================================================== --- lld/test/MachO/flat-namespace-interposable.s +++ lld/test/MachO/flat-namespace-interposable.s @@ -9,14 +9,22 @@ # RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos -o %t/foo.o %t/foo.s # RUN: %lld -lSystem -flat_namespace -o %t/foo %t/foo.o +# RUN: %lld -lSystem -flat_namespace -fixup_chains -o %t/chained %t/foo.o # RUN: %lld -lSystem -dylib -flat_namespace -o %t/foo.dylib %t/foo.o +# RUN: %lld -lSystem -dylib -flat_namespace -fixup_chains -o %t/chained.dylib %t/foo.o # RUN: %lld -lSystem -bundle -flat_namespace -o %t/foo.bundle %t/foo.o +# RUN: %lld -lSystem -bundle -flat_namespace -fixup_chains -o %t/chained.bundle %t/foo.o # RUN: llvm-objdump --macho --syms --rebase --bind --lazy-bind --weak-bind %t/foo | \ # RUN: FileCheck %s --check-prefixes=SYMS,EXEC --implicit-check-not=_private_extern -# RUN: llvm-objdump --macho --syms --rebase --bind --lazy-bind --weak-bind %t/foo.dylib | \ -# RUN: FileCheck %s --check-prefixes=SYMS,DYLIB --implicit-check-not=_private_extern -# RUN: llvm-objdump --macho --syms --rebase --bind --lazy-bind --weak-bind %t/foo.bundle | \ -# RUN: FileCheck %s --check-prefixes=SYMS,DYLIB --implicit-check-not=_private_extern +# RUN: llvm-objdump --macho --syms %t/chained >> %t/chained.objdump +# RUN: llvm-objdump --macho --dyld-info %t/chained >> %t/chained.objdump +# RUN: FileCheck %s --check-prefixes=SYMS,CHAINED-EXEC < %t/chained.objdump +# RUN: llvm-objdump --macho --syms %t/chained.dylib >> %t/dylib.objdump +# RUN: llvm-objdump --macho --dyld-info %t/chained.dylib >> %t/dylib.objdump +# RUN: FileCheck %s --check-prefixes=SYMS,CHAINED-DYLIB < %t/dylib.objdump +# RUN: llvm-objdump --macho --syms %t/chained.bundle >> %t/bundle.objdump +# RUN: llvm-objdump --macho --dyld-info %t/chained.bundle >> %t/bundle.objdump +# RUN: FileCheck %s --check-prefixes=SYMS,CHAINED-DYLIB < %t/bundle.objdump # SYMS: SYMBOL TABLE: # SYMS-DAG: [[#%x, EXTERN_REF:]] l O __DATA,__data _extern_ref @@ -44,6 +52,14 @@ # EXEC-NEXT: __DATA __data 0x[[#%.8X, WEAK_REF]] pointer 0 _weak_extern # EXEC-EMPTY: +# CHAINED-EXEC: dyld information: +# CHAINED-EXEC-NEXT: segment section address pointer type addend dylib symbol/vm address +# CHAINED-EXEC-DAG: __DATA_CONST __got {{.*}} {{.*}} bind 0x0 weak _weak_extern +# CHAINED-EXEC-DAG: __DATA __data 0x[[#%x, EXTERN_REF]] {{.*}} rebase {{.*}} +# CHAINED-EXEC-DAG: __DATA __data 0x[[#%x, WEAK_REF]] {{.*}} bind 0x0 weak _weak_extern +# CHAINED-EXEC-DAG: __DATA __data 0x[[#%x, LOCAL_REF]] {{.*}} rebase {{.*}} +# CHAINED-EXEC-EMPTY: + # DYLIB: Rebase table: # DYLIB-NEXT: segment section address type # DYLIB-DAG: __DATA __la_symbol_ptr 0x[[#%X, WEAK_LAZY:]] pointer @@ -69,6 +85,16 @@ # DYLIB-NEXT: __DATA __data 0x[[#%.8X, WEAK_REF]] pointer 0 _weak_extern # DYLIB-EMPTY: +# CHAINED-DYLIB: dyld information: +# CHAINED-DYLIB-NEXT: segment section address pointer type addend dylib symbol/vm address +# CHAINED-DYLIB-DAG: __DATA_CONST __got {{.*}} {{.*}} bind 0x0 weak _weak_extern +# CHAINED-DYLIB-DAG: __DATA_CONST __got {{.*}} {{.*}} bind 0x0 flat-namespace _extern +# CHAINED-DYLIB-DAG: __DATA __data 0x[[#%x, EXTERN_REF]] {{.*}} bind 0x0 flat-namespace _extern +# CHAINED-DYLIB-DAG: __DATA __data 0x[[#%x, WEAK_REF]] {{.*}} bind 0x0 weak _weak_extern +# CHAINED-DYLIB-DAG: __DATA __data 0x[[#%x, LOCAL_REF]] {{.*}} rebase {{.*}} +# CHAINED-DYLIB-DAG: __DATA __thread_ptrs 0x[[#%x, TLV_REF]] {{.*}} bind 0x0 flat-namespace _tlv +# CHAINED-DYLIB-EMPTY: + #--- foo.s .globl _main, _extern, _weak_extern, _tlv Index: lld/test/MachO/invalid/chained-fixups-incompatible.s =================================================================== --- /dev/null +++ lld/test/MachO/invalid/chained-fixups-incompatible.s @@ -0,0 +1,20 @@ +# REQUIRES: x86, aarch64 +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o +# RUN: not %lld -lSystem -no_pie -fixup_chains %t.o -o /dev/null 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NO-PIE +# RUN: %no-fatal-warnings-lld -fixup_chains %t.o -o /dev/null \ +# RUN: -lSystem -platform_version macos 10.15 10.15 2>&1 | \ +# RUN: FileCheck %s --check-prefix=VERSION +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-darwin %s -o %t-arm64_32.o +# RUN: not %lld-watchos -lSystem -fixup_chains %t-arm64_32.o -o /dev/null 2>&1 | \ +# RUN: FileCheck %s --check-prefix=ARCH + +## Check that we emit diagnostics when -fixup_chains is explicitly specified, +## but we don't support creating chained fixups for said configuration. +# NO-PIE: error: -fixup_chains is incompatible with -no_pie +# VERSION: warning: -fixup_chains requires macOS 11.0, which is newer than target minimum of 10.15 +# ARCH: error: -fixup_chains is only supported on x86_64 and arm64 targets + +.globl _main +_main: + ret Index: lld/test/MachO/tlv-dylib.s =================================================================== --- lld/test/MachO/tlv-dylib.s +++ lld/test/MachO/tlv-dylib.s @@ -13,6 +13,13 @@ # DYLIB-NEXT: segment section address type # DYLIB-EMPTY: +# RUN: %lld -dylib -install_name @executable_path/libtlv.dylib \ +# RUN: -lSystem -fixup_chains -o %t/libtlv.dylib %t/libtlv.o +## Make sure we don't emit fixups in __thread_vars. +# RUN: llvm-objdump --macho --chained-fixups %t/libtlv.dylib | \ +# RUN: FileCheck %s --check-prefix=CHAINED +# CHAINED-NOT: __thread_vars + # RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/test.s -o %t/test.o # RUN: %lld -lSystem -L%t -ltlv %t/test.o -o %t/test # RUN: llvm-objdump --bind -d --no-show-raw-insn %t/test | FileCheck %s Index: lld/test/MachO/weak-binding.s =================================================================== --- lld/test/MachO/weak-binding.s +++ lld/test/MachO/weak-binding.s @@ -4,13 +4,21 @@ # RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/libfoo.s -o %t/libfoo.o # RUN: %lld -dylib %t/libfoo.o -o %t/libfoo.dylib # RUN: %lld %t/test.o -L%t -lfoo -o %t/test -lSystem -# RUN: llvm-objdump -d --no-show-raw-insn --rebase --bind --lazy-bind \ -# RUN: --weak-bind --full-contents %t/test | FileCheck %s +# RUN: llvm-objdump -d --no-show-raw-insn --rebase --bind --lazy-bind --weak-bind \ +# RUN: --full-contents %t/test | FileCheck --check-prefixes=COMMON,CHECK %s -# CHECK: Contents of section __DATA_CONST,__got: +# RUN: %lld -fixup_chains %t/test.o -L%t -lfoo -o %t/chained -lSystem +# RUN: llvm-objdump -d --no-show-raw-insn --syms --full-contents %t/chained > %t/chained.objdump +# RUN: llvm-objdump --macho --dyld-info %t/chained >> %t/chained.objdump +# RUN: FileCheck %s --check-prefixes=COMMON,CHAINED < %t/chained.objdump + +# CHAINED: SYMBOL TABLE: +# CHAINED: [[#%x,WEAK_INT:]] l F __TEXT,__text _weak_internal{{$}} + +# COMMON: Contents of section __DATA_CONST,__got: ## Check that this section contains a nonzero pointer. It should point to ## _weak_external_for_gotpcrel. -# CHECK-NEXT: {{[0-9a-f]+}} {{[0-9a-f ]*[1-9a-f]+[0-9a-f ]*}} +# COMMON-NEXT: {{[0-9a-f]+}} {{[0-9a-f ]*[1-9a-f]+[0-9a-f ]*}} # CHECK: Contents of section __DATA,__la_symbol_ptr: ## Check that this section contains a nonzero pointer. It should point to @@ -18,16 +26,16 @@ ## the bytes here are in little-endian order. # CHECK-NEXT: {{[0-9a-f]+}} {{[0-9a-f ]*[1-9a-f]+[0-9a-f ]*}} -# CHECK: <_main>: -# CHECK-NEXT: movq [[#]](%rip), %rax ## 0x[[#%X,WEAK_DY_GOT_ADDR:]] -# CHECK-NEXT: movq [[#]](%rip), %rax ## 0x[[#%X,WEAK_EXT_GOT_ADDR:]] -# CHECK-NEXT: leaq [[#]](%rip), %rax ## 0x[[#%X,WEAK_INT_GOT_ADDR:]] -# CHECK-NEXT: movq [[#]](%rip), %rax ## 0x[[#%X,WEAK_TLV_ADDR:]] -# CHECK-NEXT: movq [[#]](%rip), %rax ## 0x[[#%X,WEAK_DY_TLV_ADDR:]] -# CHECK-NEXT: leaq [[#]](%rip), %rax ## 0x[[#%X,WEAK_INT_TLV_ADDR:]] -# CHECK-NEXT: callq 0x{{[0-9a-f]*}} -# CHECK-NEXT: callq 0x{{[0-9a-f]*}} -# CHECK-NEXT: callq 0x{{[0-9a-f]*}} +# COMMON-LABEL: <_main>: +# COMMON-NEXT: movq [[#]](%rip), %rax ## 0x[[#%X,WEAK_DY_GOT_ADDR:]] +# COMMON-NEXT: movq [[#]](%rip), %rax ## 0x[[#%X,WEAK_EXT_GOT_ADDR:]] +# COMMON-NEXT: leaq [[#]](%rip), %rax ## 0x[[#%X,WEAK_INT_GOT_ADDR:]] +# COMMON-NEXT: movq [[#]](%rip), %rax ## 0x[[#%X,WEAK_TLV_ADDR:]] +# COMMON-NEXT: movq [[#]](%rip), %rax ## 0x[[#%X,WEAK_DY_TLV_ADDR:]] +# COMMON-NEXT: leaq [[#]](%rip), %rax ## 0x[[#%X,WEAK_INT_TLV_ADDR:]] +# COMMON-NEXT: callq 0x{{[0-9a-f]*}} +# COMMON-NEXT: callq 0x{{[0-9a-f]*}} +# COMMON-NEXT: callq 0x{{[0-9a-f]*}} # CHECK-LABEL: Rebase table: # CHECK: __DATA __la_symbol_ptr 0x[[#%x,WEAK_EXT_FN:]] pointer @@ -65,6 +73,21 @@ # WEAK-INTERNAL-NOT: _weak_internal_fn # WEAK-INTERNAL-NOT: _weak_internal_tlv +# CHAINED-LABEL: dyld information: +# CHAINED-NEXT: segment section address pointer type addend dylib symbol/vm address +# CHAINED-DAG: __DATA_CONST __got 0x{{[0-9a-f]*}} {{.*}} bind 0x0 weak _weak_external_fn +# CHAINED-DAG: __DATA_CONST __got 0x{{[0-9a-f]*}} {{.*}} bind 0x0 weak _weak_dysym_fn +# CHAINED-DAG: __DATA_CONST __got 0x[[#WEAK_EXT_GOT_ADDR]] {{.*}} bind 0x0 weak _weak_external_for_gotpcrel +# CHAINED-DAG: __DATA_CONST __got 0x[[#WEAK_DY_GOT_ADDR]] {{.*}} bind 0x0 weak _weak_dysym_for_gotpcrel +# CHAINED-DAG: __DATA __data 0x{{[0-9a-f]*}} {{.*}} bind 0x0 weak _weak_dysym +# CHAINED-DAG: __DATA __data 0x{{[0-9a-f]*}} {{.*}} bind 0x2 weak _weak_external +# CHAINED-DAG: __DATA __data 0x{{[0-9a-f]*}} {{.*}} rebase 0x[[#%X,WEAK_INT]] +# CHAINED-DAG: __DATA __thread_vars 0x{{[0-9a-f]*}} {{.*}} bind 0x0 libSystem __tlv_bootstrap +# CHAINED-DAG: __DATA __thread_vars 0x{{[0-9a-f]*}} {{.*}} bind 0x0 libSystem __tlv_bootstrap +# CHAINED-DAG: __DATA __thread_ptrs 0x[[#WEAK_DY_TLV_ADDR]] {{.*}} bind 0x0 weak _weak_dysym_tlv +# CHAINED-DAG: __DATA __thread_ptrs 0x[[#WEAK_TLV_ADDR]] {{.*}} bind 0x0 weak _weak_tlv +# CHAINED-EMPTY: + #--- libfoo.s .globl _weak_dysym Index: lld/test/MachO/weak-reference.s =================================================================== --- lld/test/MachO/weak-reference.s +++ lld/test/MachO/weak-reference.s @@ -7,13 +7,15 @@ # RUN: %lld -lSystem -dylib %t/libfoo.o -o %t/libfoo.dylib # RUN: %lld -lSystem %t/test.o %t/libfoo.dylib -o %t/test -# RUN: llvm-objdump --macho --syms --bind %t/test | FileCheck %s --check-prefixes=SYMS,BIND +# RUN: %lld -fixup_chains -lSystem %t/test.o %t/libfoo.dylib -o %t/chained +# RUN: llvm-objdump --macho --syms --bind --lazy-bind %t/test | FileCheck %s --check-prefixes=SYMS,BIND +# RUN: llvm-objdump --macho --syms --dyld-info %t/chained | FileCheck %s --check-prefixes=CHAINED ## llvm-objdump doesn't print out all the flags info for lazy & weak bindings, ## so we use obj2yaml instead to test them. # RUN: obj2yaml %t/test | FileCheck %s --check-prefix=YAML # RUN: %lld -lSystem %t/libfoo.dylib %t/test.o -o %t/test -# RUN: llvm-objdump --macho --syms --bind %t/test | FileCheck %s --check-prefixes=SYMS,BIND +# RUN: llvm-objdump --macho --syms --bind --lazy-bind %t/test | FileCheck %s --check-prefixes=SYMS,BIND # RUN: obj2yaml %t/test | FileCheck %s --check-prefix=YAML # SYMS: SYMBOL TABLE: @@ -30,6 +32,18 @@ # BIND-DAG: __DATA __thread_ptrs 0x{{[0-9a-f]+}} pointer 0 libfoo _foo_tlv (weak_import) # BIND-DAG: __DATA __data 0x{{[0-9a-f]+}} pointer 0 libfoo _weak_foo (weak_import) # BIND-DAG: __DATA __la_symbol_ptr 0x{{[0-9a-f]+}} pointer 0 libfoo _weak_foo_fn (weak_import) +# BIND: Lazy bind table: +# BIND-NEXT: segment section address dylib symbol +# BIND-DAG: __DATA __la_symbol_ptr 0x{{[0-9a-f]+}} libfoo _foo_fn + +# CHAINED: dyld information: +# CHAINED-NEXT: segment section address pointer type addend dylib symbol/vm address +# CHAINED-DAG: __DATA_CONST __got {{.*}} bind 0x0 libfoo _foo (weak import) +# CHAINED-DAG: __DATA __data {{.*}} bind 0x0 libfoo _foo (weak import) +# CHAINED-DAG: __DATA __thread_ptrs {{.*}} bind 0x0 libfoo _foo_tlv (weak import) +# CHAINED-DAG: __DATA_CONST __got {{.*}} bind 0x0 libfoo _foo_fn (weak import) +# CHAINED-DAG: __DATA __data {{.*}} bind 0x0 weak _weak_foo (weak import) +# CHAINED-DAG: __DATA_CONST __got {{.*}} bind 0x0 weak _weak_foo_fn (weak import) # YAML-LABEL: WeakBindOpcodes: # YAML: - Opcode: BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM @@ -48,22 +62,34 @@ ## the reference is to a function symbol or a TLV. I'm not sure if there's a ## good reason for that, so I'm deviating here for a simpler implementation. # RUN: %lld -lSystem %t/test.o %t/strongref.o %t/libfoo.dylib -o %t/with-strong +# RUN: %lld -fixup_chains -lSystem %t/test.o %t/strongref.o %t/libfoo.dylib -o %t/with-strong-chained # RUN: llvm-objdump --macho --bind %t/with-strong | FileCheck %s --check-prefix=STRONG-BIND +# RUN: llvm-objdump --macho --dyld-info %t/with-strong-chained | FileCheck %s --check-prefix=STRONG-CHAINED # RUN: obj2yaml %t/with-strong | FileCheck %s --check-prefix=STRONG-YAML # RUN: %lld -lSystem %t/strongref.o %t/test.o %t/libfoo.dylib -o %t/with-strong +# RUN: %lld -fixup_chains -lSystem %t/strongref.o %t/test.o %t/libfoo.dylib -o %t/with-strong-chained # RUN: llvm-objdump --macho --bind %t/with-strong | FileCheck %s --check-prefix=STRONG-BIND +# RUN: llvm-objdump --macho --dyld-info %t/with-strong-chained | FileCheck %s --check-prefix=STRONG-CHAINED # RUN: obj2yaml %t/with-strong | FileCheck %s --check-prefix=STRONG-YAML # RUN: %lld -lSystem %t/libfoo.dylib %t/strongref.o %t/test.o -o %t/with-strong +# RUN: %lld -fixup_chains -lSystem %t/libfoo.dylib %t/strongref.o %t/test.o -o %t/with-strong-chained # RUN: llvm-objdump --macho --bind %t/with-strong | FileCheck %s --check-prefix=STRONG-BIND +# RUN: llvm-objdump --macho --dyld-info %t/with-strong-chained | FileCheck %s --check-prefix=STRONG-CHAINED # RUN: obj2yaml %t/with-strong | FileCheck %s --check-prefix=STRONG-YAML # RUN: %lld -lSystem %t/libfoo.dylib %t/test.o %t/strongref.o -o %t/with-strong +# RUN: %lld -fixup_chains -lSystem %t/libfoo.dylib %t/test.o %t/strongref.o -o %t/with-strong-chained # RUN: llvm-objdump --macho --bind %t/with-strong | FileCheck %s --check-prefix=STRONG-BIND +# RUN: llvm-objdump --macho --dyld-info %t/with-strong-chained | FileCheck %s --check-prefix=STRONG-CHAINED # RUN: obj2yaml %t/with-strong | FileCheck %s --check-prefix=STRONG-YAML # RUN: %lld -lSystem %t/test.o %t/libfoo.dylib %t/strongref.o -o %t/with-strong +# RUN: %lld -fixup_chains -lSystem %t/test.o %t/libfoo.dylib %t/strongref.o -o %t/with-strong-chained # RUN: llvm-objdump --macho --bind %t/with-strong | FileCheck %s --check-prefix=STRONG-BIND +# RUN: llvm-objdump --macho --dyld-info %t/with-strong-chained | FileCheck %s --check-prefix=STRONG-CHAINED # RUN: obj2yaml %t/with-strong | FileCheck %s --check-prefix=STRONG-YAML # RUN: %lld -lSystem %t/strongref.o %t/libfoo.dylib %t/test.o -o %t/with-strong +# RUN: %lld -fixup_chains -lSystem %t/strongref.o %t/libfoo.dylib %t/test.o -o %t/with-strong-chained # RUN: llvm-objdump --macho --bind %t/with-strong | FileCheck %s --check-prefix=STRONG-BIND +# RUN: llvm-objdump --macho --dyld-info %t/with-strong-chained | FileCheck %s --check-prefix=STRONG-CHAINED # RUN: obj2yaml %t/with-strong | FileCheck %s --check-prefix=STRONG-YAML # STRONG-BIND: Bind table: @@ -76,6 +102,17 @@ # STRONG-BIND-DAG: __DATA __data 0x{{[0-9a-f]+}} pointer 0 libfoo _weak_foo{{$}} # STRONG-BIND-DAG: __DATA __la_symbol_ptr 0x{{[0-9a-f]+}} pointer 0 libfoo _weak_foo_fn{{$}} +# STRONG-CHAINED: dyld information: +# STRONG-CHAINED-NEXT: segment section address pointer type addend dylib symbol/vm address +# STRONG-CHAINED-DAG: __DATA_CONST __got {{.*}} bind 0x0 weak _weak_foo_fn{{$}} +# STRONG-CHAINED-DAG: __DATA_CONST __got {{.*}} bind 0x0 libfoo _foo_fn{{$}} +# STRONG-CHAINED-DAG: __DATA_CONST __got {{.*}} bind 0x0 libfoo _foo{{$}} +# STRONG-CHAINED-DAG: __DATA __data {{.*}} bind 0x0 libfoo _foo{{$}} +# STRONG-CHAINED-DAG: __DATA __data {{.*}} bind 0x0 libfoo _foo{{$}} +# STRONG-CHAINED-DAG: __DATA __data {{.*}} bind 0x0 weak _weak_foo{{$}} +# STRONG-CHAINED-DAG: __DATA __data {{.*}} bind 0x0 weak _weak_foo{{$}} +# STRONG-CHAINED-DAG: __DATA __thread_ptrs {{.*}} bind 0x0 libfoo _foo_tlv{{$}} + # STRONG-YAML-LABEL: WeakBindOpcodes: # STRONG-YAML: - Opcode: BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM # STRONG-YAML-NEXT: Imm: 0 Index: llvm/include/llvm/BinaryFormat/MachO.h =================================================================== --- llvm/include/llvm/BinaryFormat/MachO.h +++ llvm/include/llvm/BinaryFormat/MachO.h @@ -1022,7 +1022,7 @@ }; // Values for dyld_chained_fixups_header::imports_format. -enum { +enum ChainedImportFormat { DYLD_CHAINED_IMPORT = 1, DYLD_CHAINED_IMPORT_ADDEND = 2, DYLD_CHAINED_IMPORT_ADDEND64 = 3,