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;
}
}
-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");
}
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;
0xd61f0200, // 08: br x16
};
-void ARM64::writeStub(uint8_t *buf8, const Symbol &sym) const {
- ::writeStub<LP64>(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[] = {
return address & pageMask;
}
-template <class LP>
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<uint32_t *>(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];
}
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;
0xd61f0200, // 08: br x16
};
-void ARM64_32::writeStub(uint8_t *buf8, const Symbol &sym) const {
- ::writeStub<ILP32>(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[] = {
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;
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[] = {
bool emitDataInCodeInfo = false;
bool emitEncryptionInfo = false;
bool emitInitOffsets = false;
+ bool emitChainedFixups = false;
bool timeTraceEnabled = false;
bool dataConst = false;
bool dedupLiterals = true;
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<std::pair<PlatformType, VersionTuple>, 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();
}
}
+ 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);
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) ||
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.
{
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<Symbol *>();
const Reloc &minuend = relocs[++i];
}
referentVA = resolveSymbolVA(referentSym, r.type) + r.addend;
- if (isThreadLocalVariables(getFlags())) {
+ if (isThreadLocalVariables(getFlags()) && isa<Defined>(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<Defined>(referentSym))
- referentVA -= firstTLVDataSection->addr;
+ referentVA -= firstTLVDataSection->addr;
+ } else if (needsFixup) {
+ writeChainedFixup(loc, referentSym, r.addend);
+ continue;
}
} else if (auto *referentIsec = r.referent.dyn_cast<InputSection *>()) {
assert(!::shouldOmitFromOutput(referentIsec));
referentVA = referentIsec->getVA(r.addend);
+
+ if (needsFixup) {
+ writeChainedRebase(loc, referentVA);
+ continue;
+ }
}
target->relocateOne(loc, r, referentVA, getVA() + r.offset);
}
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";
Flags<[HelpHidden]>,
Group<grp_undocumented>;
def fixup_chains : Flag<["-"], "fixup_chains">,
- HelpText<"This option is undocumented in ld64">,
- Flags<[HelpHidden]>,
+ HelpText<"Emit chained fixups">,
+ Group<grp_undocumented>;
+def no_fixup_chains : Flag<["-"], "no_fixup_chains">,
+ HelpText<"Emit fixup information as classic dyld opcodes">,
Group<grp_undocumented>;
def fixup_chains_section : Flag<["-"], "fixup_chains_section">,
HelpText<"This option is undocumented in ld64">,
}
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); }
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 {
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<mach_header *>(buf);
hdr->magic = target->magic;
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) {
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<Defined>(sym))
+ in.chainedFixups->addRebase(isec, offset);
+ else
+ llvm_unreachable("cannot bind to an undefined symbol");
+ return;
+ }
+
if (const auto *dysym = dyn_cast<DylibSymbol>(sym)) {
in.binding->addEntry(dysym, isec, offset, addend);
if (dysym->isWeakDef())
}
}
+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<dyld_chained_ptr_64_rebase *>(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<dyld_chained_ptr_64_bind *>(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<Defined>(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<Defined>(entries[i]))
+ write64le(&buf[i * target->wordSize], defined->getVA());
+ }
}
GotSection::GotSection()
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;
}
}
void StubsSection::finalize() { isFinal = true; }
static void addBindingsForStub(Symbol *sym) {
+ assert(!config->emitChainedFixups);
if (auto *dysym = dyn_cast<DylibSymbol>(sym)) {
if (sym->isWeakDef()) {
in.binding->addEntry(dysym, in.lazyPointers->isec,
bool inserted = entries.insert(sym);
if (inserted) {
sym->stubsIndex = entries.size() - 1;
- addBindingsForStub(sym);
+
+ if (config->emitChainedFixups)
+ in.got->addEntry(sym);
+ else
+ addBindingsForStub(sym);
}
}
}
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,
// __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())
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 {
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) {
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;
+ }
}
}
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<DylibSymbol>(&sym))
+ return dysym->isWeakDef();
+ if (auto *defined = dyn_cast<Defined>(&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<uint32_t>(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<uint32_t, uint8_t>
+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<dyld_chained_import *>(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<dyld_chained_import_addend *>(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<dyld_chained_import_addend64 *>(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<dyld_chained_starts_in_segment *>(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<dyld_chained_fixups_header *>(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<uint8_t *>(header);
+ };
+
+ header->starts_offset = curOffset();
+
+ auto *imageInfo = reinterpret_cast<dyld_chained_starts_in_image *>(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<LP64>(StringTableSection &);
template SymtabSection *macho::makeSymtabSection<ILP32>(StringTableSection &);
#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"
// 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:
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 {
std::vector<ConcatInputSection *> 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<uint32_t, uint8_t> getBinding(const Symbol *sym,
+ int64_t addend) const;
+
+ const std::vector<Location> &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<Location> locations;
+ // (target symbol, addend) => import ordinal
+ llvm::MapVector<std::pair<const Symbol *, int64_t>, uint32_t> bindings;
+
+ struct SegmentInfo {
+ SegmentInfo(const OutputSegment *oseg) : oseg(oseg) {}
+
+ const OutputSegment *oseg;
+ // (page index, fixup starts offset)
+ llvm::SmallVector<std::pair<uint16_t, uint16_t>> pageStarts;
+
+ size_t getSize() const;
+ size_t writeTo(uint8_t *buf) const;
+ };
+ llvm::SmallVector<SegmentInfo, 4> 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;
ObjCImageInfoSection *objCImageInfo = nullptr;
ConcatInputSection *imageLoaderCache = nullptr;
InitOffsetsSection *initOffsets = nullptr;
+ ChainedFixupsSection *chainedFixups = nullptr;
};
extern InStruct in;
// 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;
void openFile();
void writeSections();
void applyOptimizationHints();
+ void buildFixupChains();
void writeUuid();
void writeCodeSignature();
void writeOutputFile();
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<linkedit_data_command *>(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<linkedit_data_command *>(buf);
+ c->cmd = LC_DYLD_CHAINED_FIXUPS;
+ c->cmdsize = getSize();
+ c->dataoff = section->fileOff;
+ c->datasize = section->getSize();
+ }
+
+ ChainedFixupsSection *section;
+};
+
} // namespace
void Writer::treatSpecialUndefineds() {
// too...
auto *referentIsec = r.referent.get<InputSection *>();
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);
+ }
}
}
}
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()) {
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<DylibSymbol>(sym)) {
seg->index = segIndex++;
}
- in.header->addLoadCommand(make<LCDyldInfo>(
- in.rebase, in.binding, in.weakBinding, in.lazyBinding, in.exports));
+ if (config->emitChainedFixups) {
+ in.header->addLoadCommand(make<LCChainedFixups>(in.chainedFixups));
+ in.header->addLoadCommand(make<LCExportsTrie>(in.exports));
+ } else {
+ in.header->addLoadCommand(make<LCDyldInfo>(
+ in.rebase, in.binding, in.weakBinding, in.lazyBinding, in.exports));
+ }
in.header->addLoadCommand(make<LCSymtab>(symtabSection, stringTableSection));
in.header->addLoadCommand(
make<LCDysymtab>(symtabSection, indirectSymtabSection));
void Writer::finalizeLinkEditSegment() {
TimeTraceScope timeScope("Finalize __LINKEDIT segment");
// Fill __LINKEDIT contents.
- std::vector<LinkEditSection *> linkEditSections{
- in.rebase,
- in.binding,
- in.weakBinding,
- in.lazyBinding,
- in.exports,
- symtabSection,
- indirectSymtabSection,
- dataInCodeSection,
- functionStartsSection,
+ std::array<LinkEditSection *, 10> linkEditSections{
+ in.rebase, in.binding,
+ in.weakBinding, in.lazyBinding,
+ in.exports, in.chainedFixups,
+ symtabSection, indirectSymtabSection,
+ dataInCodeSection, functionStartsSection,
};
SmallVector<std::shared_future<void>> threadFutures;
threadFutures.reserve(linkEditSections.size());
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<Location> &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<dyld_chained_ptr_64_bind *>(buf + loc[i - 1].offset)
+ ->next = offset / stride;
+ ++i;
+ }
+ }
+}
+
void Writer::writeCodeSignature() {
if (codeSignatureSection) {
TimeTraceScope timeScope("Write code signature");
return;
writeSections();
applyOptimizationHints();
+ buildFixupChains();
writeUuid();
writeCodeSignature();
if (errorCount())
return;
- if (in.stubHelper->isNeeded())
+ if (in.stubHelper && in.stubHelper->isNeeded())
in.stubHelper->setUp();
if (in.objCImageInfo->isNeeded())
make<DeduplicatedCStringSection>(section_names::objcMethname);
in.wordLiteralSection =
config->dedupLiterals ? make<WordLiteralSection>() : nullptr;
- in.rebase = make<RebaseSection>();
- in.binding = make<BindingSection>();
- in.weakBinding = make<WeakBindingSection>();
- in.lazyBinding = make<LazyBindingSection>();
+ if (config->emitChainedFixups) {
+ in.chainedFixups = make<ChainedFixupsSection>();
+ } else {
+ in.rebase = make<RebaseSection>();
+ in.binding = make<BindingSection>();
+ in.weakBinding = make<WeakBindingSection>();
+ in.lazyBinding = make<LazyBindingSection>();
+ in.lazyPointers = make<LazyPointerSection>();
+ in.stubHelper = make<StubHelperSection>();
+ }
in.exports = make<ExportSection>();
in.got = make<GotSection>();
in.tlvPointers = make<TlvPointerSection>();
- in.lazyPointers = make<LazyPointerSection>();
in.stubs = make<StubsSection>();
- in.stubHelper = make<StubHelperSection>();
in.objcStubs = make<ObjCStubsSection>();
in.unwindInfo = makeUnwindInfoSection();
in.objCImageInfo = make<ObjCImageInfoSection>();
--- /dev/null
+# 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
--- /dev/null
+# 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
# 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
# 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
# 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
--- /dev/null
+# 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
# 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
# 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
## 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
# 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
# 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:
# 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
## 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:
# 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
};
// 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,