From 72644419f7b8490936a0233cd2c06adb07a28b88 Mon Sep 17 00:00:00 2001 From: Daniel Bertalan Date: Fri, 18 Jul 2025 11:00:41 +0200 Subject: [PATCH 1/2] [lld-macho] Clean up chained fixup entry emission (NFC) These changes are in preparation for arm64e support: - Store inline addends as `uint32_t` instead of `uint8_t`, as arm64e fixup formats store these in more than 8 bits. - Do not assume `DYLD_CHAINED_PTR_64` format. - Construct the bit-fields in the fixup entries by hand instead of using `reinterpret_cast`, as the latter is a code smell and will work incorrectly on big-endian hosts. --- lld/MachO/SyntheticSections.cpp | 78 ++++++++++++++++++-------- lld/MachO/SyntheticSections.h | 7 ++- lld/MachO/Writer.cpp | 8 ++- llvm/include/llvm/BinaryFormat/MachO.h | 2 +- 4 files changed, 67 insertions(+), 28 deletions(-) diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp index 979a4ee6d8133..b3a10524158d0 100644 --- a/lld/MachO/SyntheticSections.cpp +++ b/lld/MachO/SyntheticSections.cpp @@ -345,17 +345,28 @@ void NonLazyPointerSectionBase::addEntry(Symbol *sym) { 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); + + uint64_t encodedVA = 0; + switch (in.chainedFixups->pointerFormat()) { + case DYLD_CHAINED_PTR_64: { + // struct dyld_chained_ptr_64_rebase { + // uint64_t target : 36; // lower 36 bits of target VA + // uint64_t high8 : 8; // upper 8 bits of target VA + // uint64_t reserved : 7; // set to 0 + // uint64_t next : 12; // filled in by Writer later + // uint64_t bind : 1; // set to 0 + // }; + + uint64_t target36 = targetVA & 0xf'ffff'ffff; + uint64_t high8 = targetVA >> 56; + write64le(buf, target36 | (high8 << 36)); + encodedVA = target36 | (high8 << 56); + break; + } + default: + llvm_unreachable("unsupported chained fixup pointer format"); + } + if (encodedVA != targetVA) error("rebase target address 0x" + Twine::utohexstr(targetVA) + " does not fit into chained fixup. Re-link with -no_fixup_chains"); @@ -363,14 +374,35 @@ void macho::writeChainedRebase(uint8_t *buf, uint64_t targetVA) { 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; + + if (!isUInt<24>(ordinal)) + error("Import ordinal for symbol '" + sym->getName() + "' (" + + utohexstr(ordinal) + + ") is larger than maximum import count (0xffffff). Re-link with " + "-no_fixup_chains"); + + ordinal &= 0xffffff; + + switch (in.chainedFixups->pointerFormat()) { + case DYLD_CHAINED_PTR_64: { + // struct dyld_chained_ptr_64_bind { + // uint64_t ordinal : 24; // import ordinal + // uint64_t addend : 8; + // uint64_t reserved : 19; // set to 0 + // uint64_t next : 12; // filled in by Writer later + // uint64_t bind : 1; // set to 1 + // }; + + assert(isUInt<8>(inlineAddend) && + "large addend should be stored out-of-line"); + + write64le(buf, ordinal | (inlineAddend << 24) | (1ULL << 63)); + break; + } + default: + llvm_unreachable("unsupported chained fixup pointer format"); + } } void macho::writeChainedFixup(uint8_t *buf, const Symbol *sym, int64_t addend) { @@ -2300,7 +2332,10 @@ void macho::createSyntheticSymbols() { } ChainedFixupsSection::ChainedFixupsSection() - : LinkEditSection(segment_names::linkEdit, section_names::chainFixups) {} + : LinkEditSection(segment_names::linkEdit, section_names::chainFixups) { + // FIXME: ld64 uses DYLD_CHAINED_PTR_64_OFFSET on macOS 12+. + ptrFormat = DYLD_CHAINED_PTR_64; +} bool ChainedFixupsSection::isNeeded() const { assert(config->emitChainedFixups); @@ -2328,7 +2363,7 @@ void ChainedFixupsSection::addBinding(const Symbol *sym, } } -std::pair +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}); @@ -2379,8 +2414,7 @@ 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->pointer_format = in.chainedFixups->pointerFormat(); 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; diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h index 5796b0790c83a..1370b51381baf 100644 --- a/lld/MachO/SyntheticSections.h +++ b/lld/MachO/SyntheticSections.h @@ -800,14 +800,16 @@ class ChainedFixupsSection final : public LinkEditSection { 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; + 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; } + llvm::MachO::ChainedPointerFormat pointerFormat() const { return ptrFormat; } + private: // Location::offset initially stores the offset within an InputSection, but // contains output segment offsets after finalizeContents(). @@ -835,6 +837,7 @@ class ChainedFixupsSection final : public LinkEditSection { bool hasWeakBind = false; bool hasNonWeakDef = false; llvm::MachO::ChainedImportFormat importFormat; + llvm::MachO::ChainedPointerFormat ptrFormat; }; void writeChainedRebase(uint8_t *buf, uint64_t targetVA); diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp index f288fadc0d14f..65ba7895239f1 100644 --- a/lld/MachO/Writer.cpp +++ b/lld/MachO/Writer.cpp @@ -36,6 +36,7 @@ using namespace llvm; using namespace llvm::MachO; +using namespace llvm::support::endian; using namespace llvm::sys; using namespace lld; using namespace lld::macho; @@ -1283,9 +1284,10 @@ void Writer::buildFixupChains() { "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; + // Set the previous fixup's next offset (bits 51-62) to point to this. + void *prev = buf + loc[i - 1].offset; + write64le(prev, read64le(prev) | ((offset / stride) << 51)); + ++i; } } diff --git a/llvm/include/llvm/BinaryFormat/MachO.h b/llvm/include/llvm/BinaryFormat/MachO.h index 5dbdfb13d1a5f..0552e18132c2d 100644 --- a/llvm/include/llvm/BinaryFormat/MachO.h +++ b/llvm/include/llvm/BinaryFormat/MachO.h @@ -1043,7 +1043,7 @@ enum { }; // Values for dyld_chained_starts_in_segment::pointer_format. -enum { +enum ChainedPointerFormat { DYLD_CHAINED_PTR_ARM64E = 1, DYLD_CHAINED_PTR_64 = 2, DYLD_CHAINED_PTR_32 = 3, From e9a8bba7f76729dae5f42271b7342acb5d54dc81 Mon Sep 17 00:00:00 2001 From: Daniel Bertalan Date: Fri, 18 Jul 2025 14:14:26 +0200 Subject: [PATCH 2/2] fixup! re-add deleted comment --- lld/MachO/SyntheticSections.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp index b3a10524158d0..5ca59ae9f2960 100644 --- a/lld/MachO/SyntheticSections.cpp +++ b/lld/MachO/SyntheticSections.cpp @@ -367,6 +367,8 @@ void macho::writeChainedRebase(uint8_t *buf, uint64_t targetVA) { llvm_unreachable("unsupported chained fixup pointer format"); } + // The fixup format places a 64 GiB limit on the output's size. + // Should we handle this gracefully? if (encodedVA != targetVA) error("rebase target address 0x" + Twine::utohexstr(targetVA) + " does not fit into chained fixup. Re-link with -no_fixup_chains");