diff --git a/lld/MachO/Relocations.h b/lld/MachO/Relocations.h --- a/lld/MachO/Relocations.h +++ b/lld/MachO/Relocations.h @@ -12,6 +12,7 @@ #include "llvm/ADT/BitmaskEnum.h" #include "llvm/ADT/PointerUnion.h" #include "llvm/BinaryFormat/MachO.h" +#include "llvm/Support/Endian.h" #include #include @@ -93,6 +94,19 @@ reportRangeError(d, llvm::Twine(v), bits, 0, llvm::maxUIntN(bits)); } +inline void writeAddress(uint8_t *loc, uint64_t addr, uint8_t length) { + switch (length) { + case 2: + llvm::support::endian::write32le(loc, addr); + break; + case 3: + llvm::support::endian::write64le(loc, addr); + break; + default: + llvm_unreachable("invalid r_length"); + } +} + extern const RelocAttrs invalidRelocAttrs; } // namespace macho diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h --- a/lld/MachO/SyntheticSections.h +++ b/lld/MachO/SyntheticSections.h @@ -32,6 +32,7 @@ class DylibSymbol; class LoadCommand; class ObjFile; +class UnwindInfoSection; class SyntheticSection : public OutputSection { public: @@ -503,6 +504,7 @@ StubsSection *stubs = nullptr; StubHelperSection *stubHelper = nullptr; ImageLoaderCacheSection *imageLoaderCache = nullptr; + UnwindInfoSection *unwindInfo = nullptr; }; extern InStruct in; diff --git a/lld/MachO/UnwindInfoSection.h b/lld/MachO/UnwindInfoSection.h --- a/lld/MachO/UnwindInfoSection.h +++ b/lld/MachO/UnwindInfoSection.h @@ -17,66 +17,30 @@ #include -// In 2020, we mostly care about 64-bit targets: x86_64 and arm64 -struct CompactUnwindEntry64 { - uint64_t functionAddress; - uint32_t functionLength; - compact_unwind_encoding_t encoding; - uint64_t personality; - uint64_t lsda; -}; - -// FIXME(gkm): someday we might care about 32-bit targets: x86 & arm -struct CompactUnwindEntry32 { - uint32_t functionAddress; - uint32_t functionLength; - compact_unwind_encoding_t encoding; - uint32_t personality; - uint32_t lsda; -}; - namespace lld { namespace macho { class UnwindInfoSection : public SyntheticSection { public: - UnwindInfoSection(); + bool isNeeded() const override { return compactUnwindSection != nullptr; } uint64_t getSize() const override { return unwindInfoSize; } - bool isNeeded() const override; - void finalize() override; - void writeTo(uint8_t *buf) const override; + virtual void prepareRelocations(InputSection *) = 0; + void setCompactUnwindSection(MergedOutputSection *cuSection) { compactUnwindSection = cuSection; } - using EncodingMap = llvm::DenseMap; - - struct SecondLevelPage { - uint32_t kind; - size_t entryIndex; - size_t entryCount; - size_t byteCount; - std::vector localEncodings; - EncodingMap localEncodingIndexes; - }; +protected: + UnwindInfoSection() + : SyntheticSection(segment_names::text, section_names::unwindInfo) { + align = 4; + } -private: - std::vector> commonEncodings; - EncodingMap commonEncodingIndexes; - // Indices of personality functions within the GOT. - std::vector personalities; - std::vector lsdaEntries; - // Map of function offset (from the image base) to an index within the LSDA - // array. - llvm::DenseMap functionToLsdaIndex; - std::vector cuVector; - std::vector cuPtrVector; - std::vector secondLevelPages; MergedOutputSection *compactUnwindSection = nullptr; - uint64_t level2PagesOffset = 0; uint64_t unwindInfoSize = 0; }; +UnwindInfoSection *makeUnwindInfoSection(); void prepareCompactUnwind(InputSection *isec); } // namespace macho diff --git a/lld/MachO/UnwindInfoSection.cpp b/lld/MachO/UnwindInfoSection.cpp --- a/lld/MachO/UnwindInfoSection.cpp +++ b/lld/MachO/UnwindInfoSection.cpp @@ -91,31 +91,62 @@ // TODO(gkm): prune __eh_frame entries superseded by __unwind_info // TODO(gkm): how do we align the 2nd-level pages? -UnwindInfoSection::UnwindInfoSection() - : SyntheticSection(segment_names::text, section_names::unwindInfo) { - align = 4; // mimic ld64 -} - -bool UnwindInfoSection::isNeeded() const { - return (compactUnwindSection != nullptr); -} - -SmallDenseMap, Symbol *> - personalityTable; +using EncodingMap = llvm::DenseMap; + +template struct CompactUnwindEntry { + Ptr functionAddress; + uint32_t functionLength; + compact_unwind_encoding_t encoding; + Ptr personality; + Ptr lsda; +}; + +struct SecondLevelPage { + uint32_t kind; + size_t entryIndex; + size_t entryCount; + size_t byteCount; + std::vector localEncodings; + EncodingMap localEncodingIndexes; +}; + +template class UnwindInfoSectionImpl : public UnwindInfoSection { +public: + void prepareRelocations(InputSection *) override; + void finalize() override; + void writeTo(uint8_t *buf) const override; + +private: + std::vector> commonEncodings; + EncodingMap commonEncodingIndexes; + // Indices of personality functions within the GOT. + std::vector personalities; + SmallDenseMap, Symbol *> + personalityTable; + std::vector lsdaEntries; + // Map of function offset (from the image base) to an index within the LSDA + // array. + llvm::DenseMap functionToLsdaIndex; + std::vector> cuVector; + std::vector *> cuPtrVector; + std::vector secondLevelPages; + uint64_t level2PagesOffset = 0; +}; // Compact unwind relocations have different semantics, so we handle them in a // separate code path from regular relocations. First, we do not wish to add // rebase opcodes for __LD,__compact_unwind, because that section doesn't // actually end up in the final binary. Second, personality pointers always // reside in the GOT and must be treated specially. -void macho::prepareCompactUnwind(InputSection *isec) { +template +void UnwindInfoSectionImpl::prepareRelocations(InputSection *isec) { assert(isec->segname == segment_names::ld && isec->name == section_names::compactUnwind); for (Reloc &r : isec->relocs) { assert(target->hasAttr(r.type, RelocAttrBits::UNSIGNED)); - if (r.offset % sizeof(CompactUnwindEntry64) != - offsetof(struct CompactUnwindEntry64, personality)) + if (r.offset % sizeof(CompactUnwindEntry) != + offsetof(CompactUnwindEntry, personality)) continue; if (auto *s = r.referent.dyn_cast()) { @@ -172,8 +203,10 @@ // before converting it to post-link form. There should only be absolute // relocations here: since we are not emitting the pre-link CU section, there // is no source address to make a relative location meaningful. -static void relocateCompactUnwind(MergedOutputSection *compactUnwindSection, - std::vector &cuVector) { +template +static void +relocateCompactUnwind(MergedOutputSection *compactUnwindSection, + std::vector> &cuVector) { for (const InputSection *isec : compactUnwindSection->inputs) { uint8_t *buf = reinterpret_cast(cuVector.data()) + isec->outSecFileOff; @@ -195,21 +228,23 @@ checkTextSegment(referentIsec); referentVA = referentIsec->getVA() + r.addend; } - support::endian::write64le(buf + r.offset, referentVA); + + writeAddress(buf + r.offset, referentVA, r.length); } } } // There should only be a handful of unique personality pointers, so we can // encode them as 2-bit indices into a small array. -void encodePersonalities(const std::vector &cuPtrVector, - std::vector &personalities) { - for (CompactUnwindEntry64 *cu : cuPtrVector) { +template +void encodePersonalities( + const std::vector *> &cuPtrVector, + std::vector &personalities) { + for (CompactUnwindEntry *cu : cuPtrVector) { if (cu->personality == 0) continue; - uint32_t personalityOffset = cu->personality - in.header->addr; // Linear search is fast enough for a small array. - auto it = find(personalities, personalityOffset); + auto it = find(personalities, cu->personality); uint32_t personalityIndex; // 1-based index if (it != personalities.end()) { personalityIndex = std::distance(personalities.begin(), it) + 1; @@ -228,7 +263,7 @@ // Scan the __LD,__compact_unwind entries and compute the space needs of // __TEXT,__unwind_info and __TEXT,__eh_frame -void UnwindInfoSection::finalize() { +template void UnwindInfoSectionImpl::finalize() { if (compactUnwindSection == nullptr) return; @@ -240,21 +275,23 @@ // encoding+personality+lsda. Folding is necessary because it reduces // the number of CU entries by as much as 3 orders of magnitude! compactUnwindSection->finalize(); - assert(compactUnwindSection->getSize() % sizeof(CompactUnwindEntry64) == 0); + assert(compactUnwindSection->getSize() % sizeof(CompactUnwindEntry) == + 0); size_t cuCount = - compactUnwindSection->getSize() / sizeof(CompactUnwindEntry64); + compactUnwindSection->getSize() / sizeof(CompactUnwindEntry); cuVector.resize(cuCount); relocateCompactUnwind(compactUnwindSection, cuVector); // Rather than sort & fold the 32-byte entries directly, we create a // vector of pointers to entries and sort & fold that instead. cuPtrVector.reserve(cuCount); - for (CompactUnwindEntry64 &cuEntry : cuVector) + for (CompactUnwindEntry &cuEntry : cuVector) cuPtrVector.emplace_back(&cuEntry); - std::sort(cuPtrVector.begin(), cuPtrVector.end(), - [](const CompactUnwindEntry64 *a, const CompactUnwindEntry64 *b) { - return a->functionAddress < b->functionAddress; - }); + std::sort( + cuPtrVector.begin(), cuPtrVector.end(), + [](const CompactUnwindEntry *a, const CompactUnwindEntry *b) { + return a->functionAddress < b->functionAddress; + }); // Fold adjacent entries with matching encoding+personality+lsda // We use three iterators on the same cuPtrVector to fold in-situ: @@ -280,7 +317,7 @@ // Count frequencies of the folded encodings EncodingMap encodingFrequencies; - for (const CompactUnwindEntry64 *cuPtrEntry : cuPtrVector) + for (const CompactUnwindEntry *cuPtrEntry : cuPtrVector) encodingFrequencies[cuPtrEntry->encoding]++; // Make a vector of encodings, sorted by descending frequency @@ -316,7 +353,7 @@ // If more entries fit in the regular format, we use that. for (size_t i = 0; i < cuPtrVector.size();) { secondLevelPages.emplace_back(); - UnwindInfoSection::SecondLevelPage &page = secondLevelPages.back(); + SecondLevelPage &page = secondLevelPages.back(); page.entryIndex = i; uintptr_t functionAddressMax = cuPtrVector[i]->functionAddress + COMPRESSED_ENTRY_FUNC_OFFSET_MASK; @@ -326,7 +363,7 @@ sizeof(unwind_info_compressed_second_level_page_header) / sizeof(uint32_t); while (wordsRemaining >= 1 && i < cuPtrVector.size()) { - const CompactUnwindEntry64 *cuPtr = cuPtrVector[i]; + const CompactUnwindEntry *cuPtr = cuPtrVector[i]; if (cuPtr->functionAddress >= functionAddressMax) { break; } else if (commonEncodingIndexes.count(cuPtr->encoding) || @@ -359,7 +396,7 @@ } } - for (const CompactUnwindEntry64 *cu : cuPtrVector) { + for (const CompactUnwindEntry *cu : cuPtrVector) { uint32_t functionOffset = cu->functionAddress - in.header->addr; functionToLsdaIndex[functionOffset] = lsdaEntries.size(); if (cu->lsda != 0) @@ -382,7 +419,8 @@ // All inputs are relocated and output addresses are known, so write! -void UnwindInfoSection::writeTo(uint8_t *buf) const { +template +void UnwindInfoSectionImpl::writeTo(uint8_t *buf) const { // section header auto *uip = reinterpret_cast(buf); uip->version = 1; @@ -403,7 +441,8 @@ // Personalities for (const uint32_t &personality : personalities) - *i32p++ = in.got->addr + (personality - 1) * target->wordSize; + *i32p++ = + in.got->addr + (personality - 1) * target->wordSize - in.header->addr; // Level-1 index uint32_t lsdaOffset = @@ -422,7 +461,7 @@ l2PagesOffset += SECOND_LEVEL_PAGE_BYTES; } // Level-1 sentinel - const CompactUnwindEntry64 &cuEnd = cuVector.back(); + const CompactUnwindEntry &cuEnd = cuVector.back(); iep->functionOffset = cuEnd.functionAddress + cuEnd.functionLength; iep->secondLevelPagesSectionOffset = 0; iep->lsdaIndexArraySectionOffset = @@ -455,7 +494,7 @@ p2p->encodingsCount = page.localEncodings.size(); auto *ep = reinterpret_cast(&p2p[1]); for (size_t i = 0; i < page.entryCount; i++) { - const CompactUnwindEntry64 *cuep = cuPtrVector[page.entryIndex + i]; + const CompactUnwindEntry *cuep = cuPtrVector[page.entryIndex + i]; auto it = commonEncodingIndexes.find(cuep->encoding); if (it == commonEncodingIndexes.end()) it = page.localEncodingIndexes.find(cuep->encoding); @@ -474,7 +513,7 @@ p2p->entryCount = page.entryCount; auto *ep = reinterpret_cast(&p2p[1]); for (size_t i = 0; i < page.entryCount; i++) { - const CompactUnwindEntry64 *cuep = cuPtrVector[page.entryIndex + i]; + const CompactUnwindEntry *cuep = cuPtrVector[page.entryIndex + i]; *ep++ = cuep->functionAddress; *ep++ = cuep->encoding; } @@ -482,3 +521,10 @@ pp += SECOND_LEVEL_PAGE_WORDS; } } + +UnwindInfoSection *macho::makeUnwindInfoSection() { + if (target->wordSize == 8) + return make>(); + else + return make>(); +} diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp --- a/lld/MachO/Writer.cpp +++ b/lld/MachO/Writer.cpp @@ -71,7 +71,6 @@ SymtabSection *symtabSection = nullptr; IndirectSymtabSection *indirectSymtabSection = nullptr; CodeSignatureSection *codeSignatureSection = nullptr; - UnwindInfoSection *unwindInfoSection = nullptr; FunctionStartsSection *functionStartsSection = nullptr; LCUuid *uuidCommand = nullptr; @@ -517,7 +516,7 @@ TimeTraceScope timeScope("Scan relocations"); for (InputSection *isec : inputSections) { if (isec->segname == segment_names::ld) { - prepareCompactUnwind(isec); + in.unwindInfo->prepareRelocations(isec); continue; } @@ -798,7 +797,6 @@ TimeTraceScope timeScope("Create output sections"); // First, create hidden sections stringTableSection = make(); - unwindInfoSection = make(); // TODO(gkm): only when no -r symtabSection = makeSymtabSection(*stringTableSection); indirectSymtabSection = make(); if (config->adhocCodesign) @@ -830,9 +828,9 @@ for (const auto &it : mergedOutputSections) { StringRef segname = it.first.first; MergedOutputSection *osec = it.second; - if (unwindInfoSection && segname == segment_names::ld) { + if (segname == segment_names::ld) { assert(osec->name == section_names::compactUnwind); - unwindInfoSection->setCompactUnwindSection(osec); + in.unwindInfo->setCompactUnwindSection(osec); } else { getOrCreateOutputSegment(segname)->addOutputSection(osec); } @@ -993,6 +991,7 @@ in.stubs = make(); in.stubHelper = make(); in.imageLoaderCache = make(); + in.unwindInfo = makeUnwindInfoSection(); } OutputSection *macho::firstTLVDataSection = nullptr; diff --git a/lld/test/MachO/compact-unwind.s b/lld/test/MachO/compact-unwind.s --- a/lld/test/MachO/compact-unwind.s +++ b/lld/test/MachO/compact-unwind.s @@ -3,16 +3,23 @@ # RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin19.0.0 %t/my-personality.s -o %t/x86_64-my-personality.o # RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin19.0.0 %t/main.s -o %t/x86_64-main.o # RUN: %lld -arch x86_64 -pie -lSystem -lc++ %t/x86_64-my-personality.o %t/x86_64-main.o -o %t/x86_64-personality-first -# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/x86_64-personality-first | FileCheck %s --check-prefixes=FIRST,CHECK +# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/x86_64-personality-first | FileCheck %s --check-prefixes=FIRST,CHECK -D#%x,BASE=0x100000000 # RUN: %lld -arch x86_64 -pie -lSystem -lc++ %t/x86_64-main.o %t/x86_64-my-personality.o -o %t/x86_64-personality-second -# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/x86_64-personality-second | FileCheck %s --check-prefixes=SECOND,CHECK +# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/x86_64-personality-second | FileCheck %s --check-prefixes=SECOND,CHECK -D#%x,BASE=0x100000000 # RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin19.0.0 %t/my-personality.s -o %t/arm64-my-personality.o # RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin19.0.0 %t/main.s -o %t/arm64-main.o # RUN: %lld -arch arm64 -pie -lSystem -lc++ %t/arm64-my-personality.o %t/arm64-main.o -o %t/arm64-personality-first -# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/arm64-personality-first | FileCheck %s --check-prefixes=FIRST,CHECK +# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/arm64-personality-first | FileCheck %s --check-prefixes=FIRST,CHECK -D#%x,BASE=0x100000000 # RUN: %lld -arch arm64 -pie -lSystem -lc++ %t/arm64-main.o %t/arm64-my-personality.o -o %t/arm64-personality-second -# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/arm64-personality-second | FileCheck %s --check-prefixes=SECOND,CHECK +# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/arm64-personality-second | FileCheck %s --check-prefixes=SECOND,CHECK -D#%x,BASE=0x100000000 + +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-watchos %t/my-personality.s -o %t/arm64-32-my-personality.o +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-watchos %t/main.s -o %t/arm64-32-main.o +# RUN: %lld-watchos -pie -lSystem -lc++ %t/arm64-32-my-personality.o %t/arm64-32-main.o -o %t/arm64-32-personality-first +# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/arm64-32-personality-first | FileCheck %s --check-prefixes=FIRST,CHECK -D#%x,BASE=0x4000 +# RUN: %lld-watchos -pie -lSystem -lc++ %t/arm64-32-main.o %t/arm64-32-my-personality.o -o %t/arm64-32-personality-second +# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/arm64-32-personality-second | FileCheck %s --check-prefixes=SECOND,CHECK -D#%x,BASE=0x4000 # FIRST: Indirect symbols for (__DATA_CONST,__got) # FIRST-NEXT: address index name @@ -32,16 +39,16 @@ # CHECK: Contents of __unwind_info section: # CHECK: Personality functions: (count = 2) -# CHECK-DAG: personality[{{[0-9]+}}]: 0x{{0*}}[[#MY_PERSONALITY-0x100000000]] -# CHECK-DAG: personality[{{[0-9]+}}]: 0x{{0*}}[[#GXX_PERSONALITY-0x100000000]] +# CHECK-DAG: personality[{{[0-9]+}}]: 0x{{0*}}[[#MY_PERSONALITY-BASE]] +# CHECK-DAG: personality[{{[0-9]+}}]: 0x{{0*}}[[#GXX_PERSONALITY-BASE]] # CHECK: LSDA descriptors: -# CHECK-DAG: function offset=0x{{0*}}[[#FOO-0x100000000]], LSDA offset=0x{{0*}}[[#EXCEPTION0-0x100000000]] -# CHECK-DAG: function offset=0x{{0*}}[[#MAIN-0x100000000]], LSDA offset=0x{{0*}}[[#EXCEPTION1-0x100000000]] +# CHECK-DAG: function offset=0x[[#%.8x,FOO-BASE]], LSDA offset=0x[[#%.8x,EXCEPTION0-BASE]] +# CHECK-DAG: function offset=0x[[#%.8x,MAIN-BASE]], LSDA offset=0x[[#%.8x,EXCEPTION1-BASE]] ## Check that we do not add rebase opcodes to the compact unwind section. # CHECK: Rebase table: # CHECK-NEXT: segment section address type -# CHECK-NEXT: __DATA_CONST __got 0x{{[0-9a-f]*}} pointer +# CHECK-NEXT: __DATA_CONST __got 0x{{[0-9A-F]*}} pointer # CHECK-NOT: __TEXT #--- my-personality.s