| //===- CompactUnwindSupportImpl.h - Compact Unwind format impl --*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // Compact Unwind format support implementation details. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #ifndef LIB_EXECUTIONENGINE_JITLINK_COMPACTUNWINDSUPPORTIMPL_H | |
| #define LIB_EXECUTIONENGINE_JITLINK_COMPACTUNWINDSUPPORTIMPL_H | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/ExecutionEngine/JITLink/MachO.h" | |
| #include "llvm/Support/Debug.h" | |
| #include "llvm/Support/Endian.h" | |
| #define DEBUG_TYPE "jitlink_cu" | |
| namespace llvm { | |
| namespace jitlink { | |
| /// Split blocks in an __LD,__compact_unwind section on record boundaries. | |
| /// When this function returns edges within each record are guaranteed to be | |
| /// sorted by offset. | |
| Error splitCompactUnwindBlocks(LinkGraph &G, Section &CompactUnwindSection, | |
| size_t RecordSize); | |
| /// CRTP base for compact unwind traits classes. Automatically provides derived | |
| /// constants. | |
| /// | |
| /// Derived classes should implement the following properties and methods: | |
| /// | |
| /// Endianness: | |
| /// | |
| /// constexpr static endianness Endianness = ...; | |
| /// | |
| /// Encoding mask for offset to DWARF section for records that need DWARF: | |
| /// | |
| /// constexpr static uint32_t DWARFSectionOffsetMask = ...; | |
| /// | |
| /// Predicate for checking whether encoding needs DWARF: | |
| /// | |
| /// static bool encodingSpecifiesDWARF(uint32_t Encoding) { ... } | |
| /// | |
| /// Predicate for checking whether record can be merged with a previous | |
| /// record with the same encoding: | |
| /// | |
| /// static bool encodingCanBeMerged(uint32_t Encoding) { ... } | |
| /// | |
| /// FIXME: Passing PtrSize as a template parameter is a hack to work around a | |
| /// bug in older MSVC compilers (until at least MSVC 15) where constexpr | |
| /// fields in the CRTP impl class were not visible to the base class. | |
| /// Once we no longer need to support these compilers the PtrSize | |
| /// template argument should be removed and PointerSize should be | |
| /// defined as a member in the CRTP Impl classes. | |
| template <typename CRTPImpl, size_t PtrSize> struct CompactUnwindTraits { | |
| static constexpr size_t PointerSize = PtrSize; | |
| static constexpr size_t Size = 3 * PointerSize + 2 * 4; | |
| static constexpr size_t FnFieldOffset = 0; | |
| static constexpr size_t SizeFieldOffset = FnFieldOffset + PointerSize; | |
| static constexpr size_t EncodingFieldOffset = SizeFieldOffset + 4; | |
| static constexpr size_t PersonalityFieldOffset = EncodingFieldOffset + 4; | |
| static constexpr size_t LSDAFieldOffset = | |
| PersonalityFieldOffset + PointerSize; | |
| static uint32_t readPCRangeSize(ArrayRef<char> RecordContent) { | |
| assert(SizeFieldOffset + 4 <= RecordContent.size() && | |
| "Truncated CU record?"); | |
| return support::endian::read32<CRTPImpl::Endianness>(RecordContent.data() + | |
| SizeFieldOffset); | |
| } | |
| static uint32_t readEncoding(ArrayRef<char> RecordContent) { | |
| assert(EncodingFieldOffset + 4 <= RecordContent.size() && | |
| "Truncated CU record?"); | |
| return support::endian::read32<CRTPImpl::Endianness>(RecordContent.data() + | |
| EncodingFieldOffset); | |
| } | |
| static std::optional<uint32_t> encodeDWARFOffset(size_t Delta) { | |
| uint32_t Encoded = | |
| static_cast<uint32_t>(Delta) & CRTPImpl::DWARFSectionOffsetMask; | |
| if (Encoded != Delta) | |
| return std::nullopt; | |
| return Encoded; | |
| } | |
| }; | |
| /// Architecture specific implementation of CompactUnwindManager. | |
| template <typename CURecTraits> class CompactUnwindManager { | |
| public: | |
| CompactUnwindManager(StringRef CompactUnwindSectionName, | |
| StringRef UnwindInfoSectionName, | |
| StringRef EHFrameSectionName) | |
| : CompactUnwindSectionName(CompactUnwindSectionName), | |
| UnwindInfoSectionName(UnwindInfoSectionName), | |
| EHFrameSectionName(EHFrameSectionName) {} | |
| // Split compact unwind records, add keep-alive edges from functions to | |
| // compact unwind records, and from compact unwind records to FDEs where | |
| // needed. | |
| // | |
| // This method must be called *after* __eh_frame has been processed: it | |
| // assumes that eh-frame records have been split up and keep-alive edges have | |
| // been inserted. | |
| Error prepareForPrune(LinkGraph &G) { | |
| Section *CUSec = G.findSectionByName(CompactUnwindSectionName); | |
| if (!CUSec || CUSec->empty()) { | |
| LLVM_DEBUG({ | |
| dbgs() << "Compact unwind: No compact unwind info for " << G.getName() | |
| << "\n"; | |
| }); | |
| return Error::success(); | |
| } | |
| LLVM_DEBUG({ | |
| dbgs() << "Compact unwind: preparing " << G.getName() << " for prune\n"; | |
| }); | |
| Section *EHFrameSec = G.findSectionByName(EHFrameSectionName); | |
| if (auto Err = splitCompactUnwindBlocks(G, *CUSec, CURecTraits::Size)) | |
| return Err; | |
| LLVM_DEBUG({ | |
| dbgs() << " Preparing " << CUSec->blocks_size() << " blocks in " | |
| << CompactUnwindSectionName << "\n"; | |
| }); | |
| for (auto *B : CUSec->blocks()) { | |
| // Find target function edge. | |
| Edge *PCBeginEdge = nullptr; | |
| for (auto &E : B->edges_at(CURecTraits::FnFieldOffset)) { | |
| PCBeginEdge = &E; | |
| break; | |
| } | |
| if (!PCBeginEdge) | |
| return make_error<JITLinkError>( | |
| "In " + G.getName() + ", compact unwind record at " + | |
| formatv("{0:x}", B->getAddress()) + " has no pc-begin edge"); | |
| if (!PCBeginEdge->getTarget().isDefined()) | |
| return make_error<JITLinkError>( | |
| "In " + G.getName() + ", compact unwind record at " + | |
| formatv("{0:x}", B->getAddress()) + " points at external symbol " + | |
| *PCBeginEdge->getTarget().getName()); | |
| auto &Fn = PCBeginEdge->getTarget(); | |
| if (!Fn.isDefined()) { | |
| LLVM_DEBUG({ | |
| dbgs() << "In " << CompactUnwindSectionName << " for " << G.getName() | |
| << " encountered unexpected pc-edge to undefined symbol " | |
| << Fn.getName() << "\n"; | |
| }); | |
| continue; | |
| } | |
| uint32_t Encoding = CURecTraits::readEncoding(B->getContent()); | |
| bool NeedsDWARF = CURecTraits::encodingSpecifiesDWARF(Encoding); | |
| LLVM_DEBUG({ | |
| dbgs() << " Found record for function "; | |
| if (Fn.hasName()) | |
| dbgs() << Fn.getName(); | |
| else | |
| dbgs() << "<anon @ " << Fn.getAddress() << '>'; | |
| dbgs() << ": encoding = " << formatv("{0:x}", Encoding); | |
| if (NeedsDWARF) | |
| dbgs() << " (needs DWARF)"; | |
| dbgs() << "\n"; | |
| }); | |
| auto &CURecSym = | |
| G.addAnonymousSymbol(*B, 0, CURecTraits::Size, false, false); | |
| bool KeepAliveAlreadyPresent = false; | |
| if (EHFrameSec) { | |
| Edge *KeepAliveEdge = nullptr; | |
| for (auto &E : Fn.getBlock().edges_at(0)) { | |
| if (E.getKind() == Edge::KeepAlive && E.getTarget().isDefined() && | |
| &E.getTarget().getSection() == EHFrameSec) { | |
| KeepAliveEdge = &E; | |
| break; | |
| } | |
| } | |
| if (KeepAliveEdge) { | |
| // Found a keep-alive edge to an FDE in the eh-frame. Switch the keep | |
| // alive edge to point to the CU and if the CU needs DWARF then add | |
| // an extra keep-alive edge from the CU to the FDE. | |
| auto &FDE = KeepAliveEdge->getTarget(); | |
| KeepAliveEdge->setTarget(CURecSym); | |
| KeepAliveAlreadyPresent = true; | |
| if (NeedsDWARF) { | |
| LLVM_DEBUG({ | |
| dbgs() << " Adding keep-alive edge to FDE at " | |
| << FDE.getAddress() << "\n"; | |
| }); | |
| B->addEdge(Edge::KeepAlive, 0, FDE, 0); | |
| } | |
| } else { | |
| if (NeedsDWARF) | |
| return make_error<JITLinkError>( | |
| "In " + G.getName() + ", compact unwind recard ot " + | |
| formatv("{0:x}", B->getAddress()) + | |
| " needs DWARF, but no FDE was found"); | |
| } | |
| } else { | |
| if (NeedsDWARF) | |
| return make_error<JITLinkError>( | |
| "In " + G.getName() + ", compact unwind recard ot " + | |
| formatv("{0:x}", B->getAddress()) + " needs DWARF, but no " + | |
| EHFrameSectionName + " section exists"); | |
| } | |
| if (!KeepAliveAlreadyPresent) { | |
| // No FDE edge. We'll need to add a new edge from the function back | |
| // to the CU record. | |
| Fn.getBlock().addEdge(Edge::KeepAlive, 0, CURecSym, 0); | |
| } | |
| } | |
| return Error::success(); | |
| } | |
| /// Process all __compact_unwind records and reserve space for __unwind_info. | |
| Error processAndReserveUnwindInfo(LinkGraph &G) { | |
| // Bail out early if no unwind info. | |
| Section *CUSec = G.findSectionByName(CompactUnwindSectionName); | |
| if (!CUSec) | |
| return Error::success(); | |
| // The __LD/__compact_unwind section is only used as input for the linker. | |
| // We'll create a new __TEXT,__unwind_info section for unwind info output. | |
| CUSec->setMemLifetime(orc::MemLifetime::NoAlloc); | |
| // Find / make a mach-header to act as the base for unwind-info offsets | |
| // (and to report the arch / subarch to libunwind). | |
| if (auto Err = getOrCreateCompactUnwindBase(G)) | |
| return Err; | |
| // Error out if there's already unwind-info in the graph: We have no idea | |
| // how to merge unwind-info sections. | |
| if (G.findSectionByName(UnwindInfoSectionName)) | |
| return make_error<JITLinkError>("In " + G.getName() + ", " + | |
| UnwindInfoSectionName + | |
| " already exists"); | |
| // Process the __compact_unwind section to build the Records vector that | |
| // we'll use for writing the __unwind_info section. | |
| if (auto Err = processCompactUnwind(G, *CUSec)) | |
| return Err; | |
| // Calculate the size of __unwind_info. | |
| size_t UnwindInfoSectionSize = | |
| UnwindInfoSectionHeaderSize + | |
| Personalities.size() * PersonalityEntrySize + | |
| (NumSecondLevelPages + 1) * IndexEntrySize + NumLSDAs * LSDAEntrySize + | |
| NumSecondLevelPages * SecondLevelPageHeaderSize + | |
| Records.size() * SecondLevelPageEntrySize; | |
| LLVM_DEBUG({ | |
| dbgs() << "In " << G.getName() << ", reserving " | |
| << formatv("{0:x}", UnwindInfoSectionSize) << " bytes for " | |
| << UnwindInfoSectionName << "\n"; | |
| }); | |
| // Create the __unwind_info section and reserve space for it. | |
| Section &UnwindInfoSec = | |
| G.createSection(UnwindInfoSectionName, orc::MemProt::Read); | |
| auto UnwindInfoSectionContent = G.allocateBuffer(UnwindInfoSectionSize); | |
| memset(UnwindInfoSectionContent.data(), 0, UnwindInfoSectionContent.size()); | |
| auto &B = G.createMutableContentBlock( | |
| UnwindInfoSec, UnwindInfoSectionContent, orc::ExecutorAddr(), 8, 0); | |
| // Add Keep-alive edges from the __unwind_info block to all of the target | |
| // functions. | |
| for (auto &R : Records) | |
| B.addEdge(Edge::KeepAlive, 0, *R.Fn, 0); | |
| return Error::success(); | |
| } | |
| Error writeUnwindInfo(LinkGraph &G) { | |
| Section *CUSec = G.findSectionByName(CompactUnwindSectionName); | |
| if (!CUSec || CUSec->empty()) | |
| return Error::success(); | |
| Section *UnwindInfoSec = G.findSectionByName(UnwindInfoSectionName); | |
| if (!UnwindInfoSec) | |
| return make_error<JITLinkError>("In " + G.getName() + ", " + | |
| UnwindInfoSectionName + | |
| " missing after allocation"); | |
| if (UnwindInfoSec->blocks_size() != 1) | |
| return make_error<JITLinkError>( | |
| "In " + G.getName() + ", " + UnwindInfoSectionName + | |
| " contains more than one block post-allocation"); | |
| LLVM_DEBUG( | |
| { dbgs() << "Writing unwind info for " << G.getName() << "...\n"; }); | |
| mergeRecords(); | |
| auto &UnwindInfoBlock = **UnwindInfoSec->blocks().begin(); | |
| auto Content = UnwindInfoBlock.getMutableContent(G); | |
| BinaryStreamWriter Writer( | |
| {reinterpret_cast<uint8_t *>(Content.data()), Content.size()}, | |
| CURecTraits::Endianness); | |
| // __unwind_info format, from mach-o/compact_unwind_encoding.h on Darwin: | |
| // | |
| // #define UNWIND_SECTION_VERSION 1 | |
| // struct unwind_info_section_header | |
| // { | |
| // uint32_t version; // UNWIND_SECTION_VERSION | |
| // uint32_t commonEncodingsArraySectionOffset; | |
| // uint32_t commonEncodingsArrayCount; | |
| // uint32_t personalityArraySectionOffset; | |
| // uint32_t personalityArrayCount; | |
| // uint32_t indexSectionOffset; | |
| // uint32_t indexCount; | |
| // // compact_unwind_encoding_t[] | |
| // // uint32_t personalities[] | |
| // // unwind_info_section_header_index_entry[] | |
| // // unwind_info_section_header_lsda_index_entry[] | |
| // }; | |
| if (auto Err = writeHeader(G, Writer)) | |
| return Err; | |
| // Skip common encodings: JITLink doesn't use them. | |
| if (auto Err = writePersonalities(G, Writer)) | |
| return Err; | |
| // Calculate the offset to the LSDAs. | |
| size_t SectionOffsetToLSDAs = | |
| Writer.getOffset() + (NumSecondLevelPages + 1) * IndexEntrySize; | |
| // Calculate offset to the 1st second-level page. | |
| size_t SectionOffsetToSecondLevelPages = | |
| SectionOffsetToLSDAs + NumLSDAs * LSDAEntrySize; | |
| if (auto Err = writeIndexes(G, Writer, SectionOffsetToLSDAs, | |
| SectionOffsetToSecondLevelPages)) | |
| return Err; | |
| if (auto Err = writeLSDAs(G, Writer)) | |
| return Err; | |
| if (auto Err = writeSecondLevelPages(G, Writer)) | |
| return Err; | |
| LLVM_DEBUG({ | |
| dbgs() << " Wrote " << formatv("{0:x}", Writer.getOffset()) | |
| << " bytes of unwind info.\n"; | |
| }); | |
| return Error::success(); | |
| } | |
| private: | |
| // Calculate the size of unwind-info. | |
| static constexpr size_t MaxPersonalities = 4; | |
| static constexpr size_t PersonalityShift = 28; | |
| static constexpr size_t UnwindInfoSectionHeaderSize = 4 * 7; | |
| static constexpr size_t PersonalityEntrySize = 4; | |
| static constexpr size_t IndexEntrySize = 3 * 4; | |
| static constexpr size_t LSDAEntrySize = 2 * 4; | |
| static constexpr size_t SecondLevelPageSize = 4096; | |
| static constexpr size_t SecondLevelPageHeaderSize = 8; | |
| static constexpr size_t SecondLevelPageEntrySize = 8; | |
| static constexpr size_t NumRecordsPerSecondLevelPage = | |
| (SecondLevelPageSize - SecondLevelPageHeaderSize) / | |
| SecondLevelPageEntrySize; | |
| struct CompactUnwindRecord { | |
| Symbol *Fn = nullptr; | |
| uint32_t Size = 0; | |
| uint32_t Encoding = 0; | |
| Symbol *LSDA = nullptr; | |
| Symbol *FDE = nullptr; | |
| }; | |
| Error processCompactUnwind(LinkGraph &G, Section &CUSec) { | |
| // TODO: Reset NumLSDAs, Personalities and CompactUnwindRecords if | |
| // processing more than once. | |
| assert(NumLSDAs == 0 && "NumLSDAs should be zero"); | |
| assert(Records.empty() && "CompactUnwindRecords vector should be empty."); | |
| assert(Personalities.empty() && "Personalities vector should be empty."); | |
| SmallVector<CompactUnwindRecord> NonUniquedRecords; | |
| NonUniquedRecords.reserve(CUSec.blocks_size()); | |
| // Process __compact_unwind blocks. | |
| for (auto *B : CUSec.blocks()) { | |
| CompactUnwindRecord R; | |
| R.Encoding = CURecTraits::readEncoding(B->getContent()); | |
| for (auto &E : B->edges()) { | |
| switch (E.getOffset()) { | |
| case CURecTraits::FnFieldOffset: | |
| // This could be the function-pointer, or the FDE keep-alive. Check | |
| // the type to decide. | |
| if (E.getKind() == Edge::KeepAlive) | |
| R.FDE = &E.getTarget(); | |
| else | |
| R.Fn = &E.getTarget(); | |
| break; | |
| case CURecTraits::PersonalityFieldOffset: { | |
| // Add the Personality to the Personalities map and update the | |
| // encoding. | |
| size_t PersonalityIdx = 0; | |
| for (; PersonalityIdx != Personalities.size(); ++PersonalityIdx) | |
| if (Personalities[PersonalityIdx] == &E.getTarget()) | |
| break; | |
| if (PersonalityIdx == MaxPersonalities) | |
| return make_error<JITLinkError>( | |
| "In " + G.getName() + | |
| ", __compact_unwind contains too many personalities (max " + | |
| formatv("{}", MaxPersonalities) + ")"); | |
| if (PersonalityIdx == Personalities.size()) | |
| Personalities.push_back(&E.getTarget()); | |
| R.Encoding |= (PersonalityIdx + 1) << PersonalityShift; | |
| break; | |
| } | |
| case CURecTraits::LSDAFieldOffset: | |
| ++NumLSDAs; | |
| R.LSDA = &E.getTarget(); | |
| break; | |
| default: | |
| return make_error<JITLinkError>("In " + G.getName() + | |
| ", compact unwind record at " + | |
| formatv("{0:x}", B->getAddress()) + | |
| " has unrecognized edge at offset " + | |
| formatv("{0:x}", E.getOffset())); | |
| } | |
| } | |
| Records.push_back(R); | |
| } | |
| // Sort the records into ascending order. | |
| llvm::sort(Records, [](const CompactUnwindRecord &LHS, | |
| const CompactUnwindRecord &RHS) { | |
| return LHS.Fn->getAddress() < RHS.Fn->getAddress(); | |
| }); | |
| // Calculate the number of second-level pages required. | |
| NumSecondLevelPages = (Records.size() + NumRecordsPerSecondLevelPage - 1) / | |
| NumRecordsPerSecondLevelPage; | |
| // Convert personality symbols to GOT entry pointers. | |
| typename CURecTraits::GOTManager GOT(G); | |
| for (auto &Personality : Personalities) | |
| Personality = &GOT.getEntryForTarget(G, *Personality); | |
| LLVM_DEBUG({ | |
| dbgs() << " In " << G.getName() << ", " << CompactUnwindSectionName | |
| << ": raw records = " << Records.size() | |
| << ", personalities = " << Personalities.size() | |
| << ", lsdas = " << NumLSDAs << "\n"; | |
| }); | |
| return Error::success(); | |
| } | |
| void mergeRecords() { | |
| SmallVector<CompactUnwindRecord> NonUniqued = std::move(Records); | |
| Records.reserve(NonUniqued.size()); | |
| // Retain the final raw record: merging may remove it. | |
| LastFunction = NonUniqued.back().Fn; | |
| Records.push_back(NonUniqued.front()); | |
| for (size_t I = 1; I != NonUniqued.size(); ++I) { | |
| auto &Next = NonUniqued[I]; | |
| auto &Last = Records.back(); | |
| bool CanBeMerged = CURecTraits::encodingCanBeMerged(Next.Encoding); | |
| if (!CanBeMerged || (Next.Encoding != Last.Encoding) || Next.LSDA || | |
| Last.LSDA) | |
| Records.push_back(Next); | |
| } | |
| // Recalculate derived values that may have changed. | |
| NumSecondLevelPages = (Records.size() + NumRecordsPerSecondLevelPage - 1) / | |
| NumRecordsPerSecondLevelPage; | |
| } | |
| Error writeHeader(LinkGraph &G, BinaryStreamWriter &W) { | |
| if (!isUInt<32>(NumSecondLevelPages + 1)) | |
| return make_error<JITLinkError>("In " + G.getName() + ", too many " + | |
| UnwindInfoSectionName + | |
| "second-level pages required"); | |
| // Write __unwind_info header. | |
| size_t IndexArrayOffset = UnwindInfoSectionHeaderSize + | |
| Personalities.size() * PersonalityEntrySize; | |
| cantFail(W.writeInteger<uint32_t>(1)); | |
| cantFail(W.writeInteger<uint32_t>(UnwindInfoSectionHeaderSize)); | |
| cantFail(W.writeInteger<uint32_t>(0)); | |
| cantFail(W.writeInteger<uint32_t>(UnwindInfoSectionHeaderSize)); | |
| cantFail(W.writeInteger<uint32_t>(Personalities.size())); | |
| cantFail(W.writeInteger<uint32_t>(IndexArrayOffset)); | |
| cantFail(W.writeInteger<uint32_t>(NumSecondLevelPages + 1)); | |
| return Error::success(); | |
| } | |
| Error writePersonalities(LinkGraph &G, BinaryStreamWriter &W) { | |
| // Write personalities. | |
| for (auto *PSym : Personalities) { | |
| auto Delta = PSym->getAddress() - CompactUnwindBase->getAddress(); | |
| if (!isUInt<32>(Delta)) | |
| return makePersonalityRangeError(G, *PSym); | |
| cantFail(W.writeInteger<uint32_t>(Delta)); | |
| } | |
| return Error::success(); | |
| } | |
| Error writeIndexes(LinkGraph &G, BinaryStreamWriter &W, | |
| size_t SectionOffsetToLSDAs, | |
| size_t SectionOffsetToSecondLevelPages) { | |
| // Assume that function deltas are ok in this method -- we'll error | |
| // check all of them when we write the second level pages. | |
| // Write the header index entries. | |
| size_t RecordIdx = 0; | |
| size_t NumPreviousLSDAs = 0; | |
| for (auto &R : Records) { | |
| // If this record marks the start of a new second level page. | |
| if (RecordIdx % NumRecordsPerSecondLevelPage == 0) { | |
| auto FnDelta = R.Fn->getAddress() - CompactUnwindBase->getAddress(); | |
| auto SecondLevelPageOffset = | |
| SectionOffsetToSecondLevelPages + | |
| SecondLevelPageSize * (RecordIdx / NumRecordsPerSecondLevelPage); | |
| auto LSDAOffset = | |
| SectionOffsetToLSDAs + NumPreviousLSDAs * LSDAEntrySize; | |
| cantFail(W.writeInteger<uint32_t>(FnDelta)); | |
| cantFail(W.writeInteger<uint32_t>(SecondLevelPageOffset)); | |
| cantFail(W.writeInteger<uint32_t>(LSDAOffset)); | |
| } | |
| if (R.LSDA) | |
| ++NumPreviousLSDAs; | |
| ++RecordIdx; | |
| } | |
| // Write the index array terminator. | |
| { | |
| auto FnEndDelta = | |
| LastFunction->getRange().End - CompactUnwindBase->getAddress(); | |
| if (LLVM_UNLIKELY(!isUInt<32>(FnEndDelta))) | |
| return make_error<JITLinkError>( | |
| "In " + G.getName() + " " + UnwindInfoSectionName + | |
| ", delta to end of functions " + | |
| formatv("{0:x}", LastFunction->getRange().End) + | |
| " exceeds 32 bits"); | |
| cantFail(W.writeInteger<uint32_t>(FnEndDelta)); | |
| cantFail(W.writeInteger<uint32_t>(0)); | |
| cantFail(W.writeInteger<uint32_t>(SectionOffsetToSecondLevelPages)); | |
| } | |
| return Error::success(); | |
| } | |
| Error writeLSDAs(LinkGraph &G, BinaryStreamWriter &W) { | |
| // As with writeIndexes, assume that function deltas are ok for now. | |
| for (auto &R : Records) { | |
| if (R.LSDA) { | |
| auto FnDelta = R.Fn->getAddress() - CompactUnwindBase->getAddress(); | |
| auto LSDADelta = R.LSDA->getAddress() - CompactUnwindBase->getAddress(); | |
| if (LLVM_UNLIKELY(!isUInt<32>(LSDADelta))) | |
| return make_error<JITLinkError>( | |
| "In " + G.getName() + " " + UnwindInfoSectionName + | |
| ", delta to lsda at " + formatv("{0:x}", R.LSDA->getAddress()) + | |
| " exceeds 32 bits"); | |
| cantFail(W.writeInteger<uint32_t>(FnDelta)); | |
| cantFail(W.writeInteger<uint32_t>(LSDADelta)); | |
| } | |
| } | |
| return Error::success(); | |
| } | |
| Error writeSecondLevelPages(LinkGraph &G, BinaryStreamWriter &W) { | |
| size_t RecordIdx = 0; | |
| for (auto &R : Records) { | |
| // When starting a new second-level page, write the page header: | |
| // | |
| // 2 : uint32_t -- UNWIND_SECOND_LEVEL_REGULAR | |
| // 8 : uint16_t -- size of second level page table header | |
| // count : uint16_t -- num entries in this second-level page | |
| if (RecordIdx % NumRecordsPerSecondLevelPage == 0) { | |
| constexpr uint32_t SecondLevelPageHeaderKind = 2; | |
| constexpr uint16_t SecondLevelPageHeaderSize = 8; | |
| uint16_t SecondLevelPageNumEntries = | |
| std::min(Records.size() - RecordIdx, NumRecordsPerSecondLevelPage); | |
| cantFail(W.writeInteger<uint32_t>(SecondLevelPageHeaderKind)); | |
| cantFail(W.writeInteger<uint16_t>(SecondLevelPageHeaderSize)); | |
| cantFail(W.writeInteger<uint16_t>(SecondLevelPageNumEntries)); | |
| } | |
| // Write entry. | |
| auto FnDelta = R.Fn->getAddress() - CompactUnwindBase->getAddress(); | |
| if (LLVM_UNLIKELY(!isUInt<32>(FnDelta))) | |
| return make_error<JITLinkError>( | |
| "In " + G.getName() + " " + UnwindInfoSectionName + | |
| ", delta to function at " + formatv("{0:x}", R.Fn->getAddress()) + | |
| " exceeds 32 bits"); | |
| auto Encoding = R.Encoding; | |
| if (LLVM_UNLIKELY(CURecTraits::encodingSpecifiesDWARF(R.Encoding))) { | |
| if (!EHFrameBase) | |
| EHFrameBase = SectionRange(R.FDE->getSection()).getStart(); | |
| auto FDEDelta = R.FDE->getAddress() - EHFrameBase; | |
| if (auto EncodedFDEDelta = CURecTraits::encodeDWARFOffset(FDEDelta)) | |
| Encoding |= *EncodedFDEDelta; | |
| else | |
| return make_error<JITLinkError>( | |
| "In " + G.getName() + " " + UnwindInfoSectionName + | |
| ", cannot encode delta " + formatv("{0:x}", FDEDelta) + | |
| " to FDE at " + formatv("{0:x}", R.FDE->getAddress())); | |
| } | |
| cantFail(W.writeInteger<uint32_t>(FnDelta)); | |
| cantFail(W.writeInteger<uint32_t>(Encoding)); | |
| ++RecordIdx; | |
| } | |
| return Error::success(); | |
| } | |
| Error getOrCreateCompactUnwindBase(LinkGraph &G) { | |
| auto Name = G.intern("__jitlink$libunwind_dso_base"); | |
| CompactUnwindBase = G.findAbsoluteSymbolByName(Name); | |
| if (!CompactUnwindBase) { | |
| if (auto LocalCUBase = getOrCreateLocalMachOHeader(G)) { | |
| CompactUnwindBase = &*LocalCUBase; | |
| auto &B = LocalCUBase->getBlock(); | |
| G.addDefinedSymbol(B, 0, *Name, B.getSize(), Linkage::Strong, | |
| Scope::Local, false, true); | |
| } else | |
| return LocalCUBase.takeError(); | |
| } | |
| CompactUnwindBase->setLive(true); | |
| return Error::success(); | |
| } | |
| Error makePersonalityRangeError(LinkGraph &G, Symbol &PSym) { | |
| std::string ErrMsg; | |
| { | |
| raw_string_ostream ErrStream(ErrMsg); | |
| ErrStream << "In " << G.getName() << " " << UnwindInfoSectionName | |
| << ", personality "; | |
| if (PSym.hasName()) | |
| ErrStream << PSym.getName() << " "; | |
| ErrStream << "at " << PSym.getAddress() | |
| << " is out of 32-bit delta range of compact-unwind base at " | |
| << CompactUnwindBase->getAddress(); | |
| } | |
| return make_error<JITLinkError>(std::move(ErrMsg)); | |
| } | |
| StringRef CompactUnwindSectionName; | |
| StringRef UnwindInfoSectionName; | |
| StringRef EHFrameSectionName; | |
| Symbol *CompactUnwindBase = nullptr; | |
| orc::ExecutorAddr EHFrameBase; | |
| size_t NumLSDAs = 0; | |
| size_t NumSecondLevelPages = 0; | |
| Symbol *LastFunction = nullptr; | |
| SmallVector<Symbol *, MaxPersonalities> Personalities; | |
| SmallVector<CompactUnwindRecord> Records; | |
| }; | |
| } // end namespace jitlink | |
| } // end namespace llvm | |
| #undef DEBUG_TYPE | |
| #endif // LIB_EXECUTIONENGINE_JITLINK_COMPACTUNWINDSUPPORTIMPL_H |