| //===------- DebugObjectManagerPlugin.cpp - JITLink debug objects ---------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // FIXME: Update Plugin to poke the debug object into a new JITLink section, |
| // rather than creating a new allocation. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/ExecutionEngine/Orc/DebugObjectManagerPlugin.h" |
| |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/ADT/StringMap.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/BinaryFormat/ELF.h" |
| #include "llvm/ExecutionEngine/JITLink/JITLinkDylib.h" |
| #include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h" |
| #include "llvm/ExecutionEngine/JITSymbol.h" |
| #include "llvm/Object/ELFObjectFile.h" |
| #include "llvm/Object/ObjectFile.h" |
| #include "llvm/Support/Errc.h" |
| #include "llvm/Support/MSVCErrorWorkarounds.h" |
| #include "llvm/Support/MemoryBuffer.h" |
| #include "llvm/Support/Process.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| #include <set> |
| |
| #define DEBUG_TYPE "orc" |
| |
| using namespace llvm::jitlink; |
| using namespace llvm::object; |
| |
| namespace llvm { |
| namespace orc { |
| |
| class DebugObjectSection { |
| public: |
| virtual void setTargetMemoryRange(SectionRange Range) = 0; |
| virtual void dump(raw_ostream &OS, StringRef Name) {} |
| virtual ~DebugObjectSection() {} |
| }; |
| |
| template <typename ELFT> |
| class ELFDebugObjectSection : public DebugObjectSection { |
| public: |
| // BinaryFormat ELF is not meant as a mutable format. We can only make changes |
| // that don't invalidate the file structure. |
| ELFDebugObjectSection(const typename ELFT::Shdr *Header) |
| : Header(const_cast<typename ELFT::Shdr *>(Header)) {} |
| |
| void setTargetMemoryRange(SectionRange Range) override; |
| void dump(raw_ostream &OS, StringRef Name) override; |
| |
| Error validateInBounds(StringRef Buffer, const char *Name) const; |
| |
| private: |
| typename ELFT::Shdr *Header; |
| |
| bool isTextOrDataSection() const; |
| }; |
| |
| template <typename ELFT> |
| void ELFDebugObjectSection<ELFT>::setTargetMemoryRange(SectionRange Range) { |
| // Only patch load-addresses for executable and data sections. |
| if (isTextOrDataSection()) { |
| Header->sh_addr = static_cast<typename ELFT::uint>(Range.getStart()); |
| } |
| } |
| |
| template <typename ELFT> |
| bool ELFDebugObjectSection<ELFT>::isTextOrDataSection() const { |
| switch (Header->sh_type) { |
| case ELF::SHT_PROGBITS: |
| case ELF::SHT_X86_64_UNWIND: |
| return Header->sh_flags & (ELF::SHF_EXECINSTR | ELF::SHF_ALLOC); |
| } |
| return false; |
| } |
| |
| template <typename ELFT> |
| Error ELFDebugObjectSection<ELFT>::validateInBounds(StringRef Buffer, |
| const char *Name) const { |
| const uint8_t *Start = Buffer.bytes_begin(); |
| const uint8_t *End = Buffer.bytes_end(); |
| const uint8_t *HeaderPtr = reinterpret_cast<uint8_t *>(Header); |
| if (HeaderPtr < Start || HeaderPtr + sizeof(typename ELFT::Shdr) > End) |
| return make_error<StringError>( |
| formatv("{0} section header at {1:x16} not within bounds of the " |
| "given debug object buffer [{2:x16} - {3:x16}]", |
| Name, &Header->sh_addr, Start, End), |
| inconvertibleErrorCode()); |
| if (Header->sh_offset + Header->sh_size > Buffer.size()) |
| return make_error<StringError>( |
| formatv("{0} section data [{1:x16} - {2:x16}] not within bounds of " |
| "the given debug object buffer [{3:x16} - {4:x16}]", |
| Name, Start + Header->sh_offset, |
| Start + Header->sh_offset + Header->sh_size, Start, End), |
| inconvertibleErrorCode()); |
| return Error::success(); |
| } |
| |
| template <typename ELFT> |
| void ELFDebugObjectSection<ELFT>::dump(raw_ostream &OS, StringRef Name) { |
| if (auto Addr = static_cast<JITTargetAddress>(Header->sh_addr)) { |
| OS << formatv(" {0:x16} {1}\n", Addr, Name); |
| } else { |
| OS << formatv(" {0}\n", Name); |
| } |
| } |
| |
| enum class Requirement { |
| // Request final target memory load-addresses for all sections. |
| ReportFinalSectionLoadAddresses, |
| }; |
| |
| /// The plugin creates a debug object from when JITLink starts processing the |
| /// corresponding LinkGraph. It provides access to the pass configuration of |
| /// the LinkGraph and calls the finalization function, once the resulting link |
| /// artifact was emitted. |
| /// |
| class DebugObject { |
| public: |
| DebugObject(JITLinkMemoryManager &MemMgr, const JITLinkDylib *JD, |
| ExecutionSession &ES) |
| : MemMgr(MemMgr), JD(JD), ES(ES) {} |
| |
| void set(Requirement Req) { Reqs.insert(Req); } |
| bool has(Requirement Req) const { return Reqs.count(Req) > 0; } |
| |
| using FinalizeContinuation = std::function<void(Expected<ExecutorAddrRange>)>; |
| |
| void finalizeAsync(FinalizeContinuation OnFinalize); |
| |
| virtual ~DebugObject() { |
| if (Alloc) { |
| std::vector<FinalizedAlloc> Allocs; |
| Allocs.push_back(std::move(Alloc)); |
| if (Error Err = MemMgr.deallocate(std::move(Allocs))) |
| ES.reportError(std::move(Err)); |
| } |
| } |
| |
| virtual void reportSectionTargetMemoryRange(StringRef Name, |
| SectionRange TargetMem) {} |
| |
| protected: |
| using InFlightAlloc = JITLinkMemoryManager::InFlightAlloc; |
| using FinalizedAlloc = JITLinkMemoryManager::FinalizedAlloc; |
| |
| virtual Expected<SimpleSegmentAlloc> finalizeWorkingMemory() = 0; |
| |
| JITLinkMemoryManager &MemMgr; |
| const JITLinkDylib *JD = nullptr; |
| |
| private: |
| ExecutionSession &ES; |
| std::set<Requirement> Reqs; |
| FinalizedAlloc Alloc; |
| }; |
| |
| // Finalize working memory and take ownership of the resulting allocation. Start |
| // copying memory over to the target and pass on the result once we're done. |
| // Ownership of the allocation remains with us for the rest of our lifetime. |
| void DebugObject::finalizeAsync(FinalizeContinuation OnFinalize) { |
| assert(!Alloc && "Cannot finalize more than once"); |
| |
| if (auto SimpleSegAlloc = finalizeWorkingMemory()) { |
| auto ROSeg = SimpleSegAlloc->getSegInfo(MemProt::Read); |
| ExecutorAddrRange DebugObjRange(ExecutorAddr(ROSeg.Addr), |
| ExecutorAddrDiff(ROSeg.WorkingMem.size())); |
| SimpleSegAlloc->finalize( |
| [this, DebugObjRange, |
| OnFinalize = std::move(OnFinalize)](Expected<FinalizedAlloc> FA) { |
| if (FA) { |
| Alloc = std::move(*FA); |
| OnFinalize(DebugObjRange); |
| } else |
| OnFinalize(FA.takeError()); |
| }); |
| } else |
| OnFinalize(SimpleSegAlloc.takeError()); |
| } |
| |
| /// The current implementation of ELFDebugObject replicates the approach used in |
| /// RuntimeDyld: It patches executable and data section headers in the given |
| /// object buffer with load-addresses of their corresponding sections in target |
| /// memory. |
| /// |
| class ELFDebugObject : public DebugObject { |
| public: |
| static Expected<std::unique_ptr<DebugObject>> |
| Create(MemoryBufferRef Buffer, JITLinkContext &Ctx, ExecutionSession &ES); |
| |
| void reportSectionTargetMemoryRange(StringRef Name, |
| SectionRange TargetMem) override; |
| |
| StringRef getBuffer() const { return Buffer->getMemBufferRef().getBuffer(); } |
| |
| protected: |
| Expected<SimpleSegmentAlloc> finalizeWorkingMemory() override; |
| |
| template <typename ELFT> |
| Error recordSection(StringRef Name, |
| std::unique_ptr<ELFDebugObjectSection<ELFT>> Section); |
| DebugObjectSection *getSection(StringRef Name); |
| |
| private: |
| template <typename ELFT> |
| static Expected<std::unique_ptr<ELFDebugObject>> |
| CreateArchType(MemoryBufferRef Buffer, JITLinkMemoryManager &MemMgr, |
| const JITLinkDylib *JD, ExecutionSession &ES); |
| |
| static std::unique_ptr<WritableMemoryBuffer> |
| CopyBuffer(MemoryBufferRef Buffer, Error &Err); |
| |
| ELFDebugObject(std::unique_ptr<WritableMemoryBuffer> Buffer, |
| JITLinkMemoryManager &MemMgr, const JITLinkDylib *JD, |
| ExecutionSession &ES) |
| : DebugObject(MemMgr, JD, ES), Buffer(std::move(Buffer)) { |
| set(Requirement::ReportFinalSectionLoadAddresses); |
| } |
| |
| std::unique_ptr<WritableMemoryBuffer> Buffer; |
| StringMap<std::unique_ptr<DebugObjectSection>> Sections; |
| }; |
| |
| static const std::set<StringRef> DwarfSectionNames = { |
| #define HANDLE_DWARF_SECTION(ENUM_NAME, ELF_NAME, CMDLINE_NAME, OPTION) \ |
| ELF_NAME, |
| #include "llvm/BinaryFormat/Dwarf.def" |
| #undef HANDLE_DWARF_SECTION |
| }; |
| |
| static bool isDwarfSection(StringRef SectionName) { |
| return DwarfSectionNames.count(SectionName) == 1; |
| } |
| |
| std::unique_ptr<WritableMemoryBuffer> |
| ELFDebugObject::CopyBuffer(MemoryBufferRef Buffer, Error &Err) { |
| ErrorAsOutParameter _(&Err); |
| size_t Size = Buffer.getBufferSize(); |
| StringRef Name = Buffer.getBufferIdentifier(); |
| if (auto Copy = WritableMemoryBuffer::getNewUninitMemBuffer(Size, Name)) { |
| memcpy(Copy->getBufferStart(), Buffer.getBufferStart(), Size); |
| return Copy; |
| } |
| |
| Err = errorCodeToError(make_error_code(errc::not_enough_memory)); |
| return nullptr; |
| } |
| |
| template <typename ELFT> |
| Expected<std::unique_ptr<ELFDebugObject>> |
| ELFDebugObject::CreateArchType(MemoryBufferRef Buffer, |
| JITLinkMemoryManager &MemMgr, |
| const JITLinkDylib *JD, ExecutionSession &ES) { |
| using SectionHeader = typename ELFT::Shdr; |
| |
| Error Err = Error::success(); |
| std::unique_ptr<ELFDebugObject> DebugObj( |
| new ELFDebugObject(CopyBuffer(Buffer, Err), MemMgr, JD, ES)); |
| if (Err) |
| return std::move(Err); |
| |
| Expected<ELFFile<ELFT>> ObjRef = ELFFile<ELFT>::create(DebugObj->getBuffer()); |
| if (!ObjRef) |
| return ObjRef.takeError(); |
| |
| // TODO: Add support for other architectures. |
| uint16_t TargetMachineArch = ObjRef->getHeader().e_machine; |
| if (TargetMachineArch != ELF::EM_X86_64) |
| return nullptr; |
| |
| Expected<ArrayRef<SectionHeader>> Sections = ObjRef->sections(); |
| if (!Sections) |
| return Sections.takeError(); |
| |
| bool HasDwarfSection = false; |
| for (const SectionHeader &Header : *Sections) { |
| Expected<StringRef> Name = ObjRef->getSectionName(Header); |
| if (!Name) |
| return Name.takeError(); |
| if (Name->empty()) |
| continue; |
| HasDwarfSection |= isDwarfSection(*Name); |
| |
| auto Wrapped = std::make_unique<ELFDebugObjectSection<ELFT>>(&Header); |
| if (Error Err = DebugObj->recordSection(*Name, std::move(Wrapped))) |
| return std::move(Err); |
| } |
| |
| if (!HasDwarfSection) { |
| LLVM_DEBUG(dbgs() << "Aborting debug registration for LinkGraph \"" |
| << DebugObj->Buffer->getBufferIdentifier() |
| << "\": input object contains no debug info\n"); |
| return nullptr; |
| } |
| |
| return std::move(DebugObj); |
| } |
| |
| Expected<std::unique_ptr<DebugObject>> |
| ELFDebugObject::Create(MemoryBufferRef Buffer, JITLinkContext &Ctx, |
| ExecutionSession &ES) { |
| unsigned char Class, Endian; |
| std::tie(Class, Endian) = getElfArchType(Buffer.getBuffer()); |
| |
| if (Class == ELF::ELFCLASS32) { |
| if (Endian == ELF::ELFDATA2LSB) |
| return CreateArchType<ELF32LE>(Buffer, Ctx.getMemoryManager(), |
| Ctx.getJITLinkDylib(), ES); |
| if (Endian == ELF::ELFDATA2MSB) |
| return CreateArchType<ELF32BE>(Buffer, Ctx.getMemoryManager(), |
| Ctx.getJITLinkDylib(), ES); |
| return nullptr; |
| } |
| if (Class == ELF::ELFCLASS64) { |
| if (Endian == ELF::ELFDATA2LSB) |
| return CreateArchType<ELF64LE>(Buffer, Ctx.getMemoryManager(), |
| Ctx.getJITLinkDylib(), ES); |
| if (Endian == ELF::ELFDATA2MSB) |
| return CreateArchType<ELF64BE>(Buffer, Ctx.getMemoryManager(), |
| Ctx.getJITLinkDylib(), ES); |
| return nullptr; |
| } |
| return nullptr; |
| } |
| |
| Expected<SimpleSegmentAlloc> ELFDebugObject::finalizeWorkingMemory() { |
| LLVM_DEBUG({ |
| dbgs() << "Section load-addresses in debug object for \"" |
| << Buffer->getBufferIdentifier() << "\":\n"; |
| for (const auto &KV : Sections) |
| KV.second->dump(dbgs(), KV.first()); |
| }); |
| |
| // TODO: This works, but what actual alignment requirements do we have? |
| unsigned PageSize = sys::Process::getPageSizeEstimate(); |
| size_t Size = Buffer->getBufferSize(); |
| |
| // Allocate working memory for debug object in read-only segment. |
| auto Alloc = SimpleSegmentAlloc::Create( |
| MemMgr, JD, {{MemProt::Read, {Size, Align(PageSize)}}}); |
| if (!Alloc) |
| return Alloc; |
| |
| // Initialize working memory with a copy of our object buffer. |
| auto SegInfo = Alloc->getSegInfo(MemProt::Read); |
| memcpy(SegInfo.WorkingMem.data(), Buffer->getBufferStart(), Size); |
| Buffer.reset(); |
| |
| return Alloc; |
| } |
| |
| void ELFDebugObject::reportSectionTargetMemoryRange(StringRef Name, |
| SectionRange TargetMem) { |
| if (auto *DebugObjSection = getSection(Name)) |
| DebugObjSection->setTargetMemoryRange(TargetMem); |
| } |
| |
| template <typename ELFT> |
| Error ELFDebugObject::recordSection( |
| StringRef Name, std::unique_ptr<ELFDebugObjectSection<ELFT>> Section) { |
| if (Error Err = Section->validateInBounds(this->getBuffer(), Name.data())) |
| return Err; |
| auto ItInserted = Sections.try_emplace(Name, std::move(Section)); |
| if (!ItInserted.second) |
| return make_error<StringError>("Duplicate section", |
| inconvertibleErrorCode()); |
| return Error::success(); |
| } |
| |
| DebugObjectSection *ELFDebugObject::getSection(StringRef Name) { |
| auto It = Sections.find(Name); |
| return It == Sections.end() ? nullptr : It->second.get(); |
| } |
| |
| /// Creates a debug object based on the input object file from |
| /// ObjectLinkingLayerJITLinkContext. |
| /// |
| static Expected<std::unique_ptr<DebugObject>> |
| createDebugObjectFromBuffer(ExecutionSession &ES, LinkGraph &G, |
| JITLinkContext &Ctx, MemoryBufferRef ObjBuffer) { |
| switch (G.getTargetTriple().getObjectFormat()) { |
| case Triple::ELF: |
| return ELFDebugObject::Create(ObjBuffer, Ctx, ES); |
| |
| default: |
| // TODO: Once we add support for other formats, we might want to split this |
| // into multiple files. |
| return nullptr; |
| } |
| } |
| |
| DebugObjectManagerPlugin::DebugObjectManagerPlugin( |
| ExecutionSession &ES, std::unique_ptr<DebugObjectRegistrar> Target) |
| : ES(ES), Target(std::move(Target)) {} |
| |
| DebugObjectManagerPlugin::~DebugObjectManagerPlugin() = default; |
| |
| void DebugObjectManagerPlugin::notifyMaterializing( |
| MaterializationResponsibility &MR, LinkGraph &G, JITLinkContext &Ctx, |
| MemoryBufferRef ObjBuffer) { |
| std::lock_guard<std::mutex> Lock(PendingObjsLock); |
| assert(PendingObjs.count(&MR) == 0 && |
| "Cannot have more than one pending debug object per " |
| "MaterializationResponsibility"); |
| |
| if (auto DebugObj = createDebugObjectFromBuffer(ES, G, Ctx, ObjBuffer)) { |
| // Not all link artifacts allow debugging. |
| if (*DebugObj != nullptr) |
| PendingObjs[&MR] = std::move(*DebugObj); |
| } else { |
| ES.reportError(DebugObj.takeError()); |
| } |
| } |
| |
| void DebugObjectManagerPlugin::modifyPassConfig( |
| MaterializationResponsibility &MR, LinkGraph &G, |
| PassConfiguration &PassConfig) { |
| // Not all link artifacts have associated debug objects. |
| std::lock_guard<std::mutex> Lock(PendingObjsLock); |
| auto It = PendingObjs.find(&MR); |
| if (It == PendingObjs.end()) |
| return; |
| |
| DebugObject &DebugObj = *It->second; |
| if (DebugObj.has(Requirement::ReportFinalSectionLoadAddresses)) { |
| PassConfig.PostAllocationPasses.push_back( |
| [&DebugObj](LinkGraph &Graph) -> Error { |
| for (const Section &GraphSection : Graph.sections()) |
| DebugObj.reportSectionTargetMemoryRange(GraphSection.getName(), |
| SectionRange(GraphSection)); |
| return Error::success(); |
| }); |
| } |
| } |
| |
| Error DebugObjectManagerPlugin::notifyEmitted( |
| MaterializationResponsibility &MR) { |
| std::lock_guard<std::mutex> Lock(PendingObjsLock); |
| auto It = PendingObjs.find(&MR); |
| if (It == PendingObjs.end()) |
| return Error::success(); |
| |
| // During finalization the debug object is registered with the target. |
| // Materialization must wait for this process to finish. Otherwise we might |
| // start running code before the debugger processed the corresponding debug |
| // info. |
| std::promise<MSVCPError> FinalizePromise; |
| std::future<MSVCPError> FinalizeErr = FinalizePromise.get_future(); |
| |
| It->second->finalizeAsync( |
| [this, &FinalizePromise, &MR](Expected<ExecutorAddrRange> TargetMem) { |
| // Any failure here will fail materialization. |
| if (!TargetMem) { |
| FinalizePromise.set_value(TargetMem.takeError()); |
| return; |
| } |
| if (Error Err = Target->registerDebugObject(*TargetMem)) { |
| FinalizePromise.set_value(std::move(Err)); |
| return; |
| } |
| |
| // Once our tracking info is updated, notifyEmitted() can return and |
| // finish materialization. |
| FinalizePromise.set_value(MR.withResourceKeyDo([&](ResourceKey K) { |
| assert(PendingObjs.count(&MR) && "We still hold PendingObjsLock"); |
| std::lock_guard<std::mutex> Lock(RegisteredObjsLock); |
| RegisteredObjs[K].push_back(std::move(PendingObjs[&MR])); |
| PendingObjs.erase(&MR); |
| })); |
| }); |
| |
| return FinalizeErr.get(); |
| } |
| |
| Error DebugObjectManagerPlugin::notifyFailed( |
| MaterializationResponsibility &MR) { |
| std::lock_guard<std::mutex> Lock(PendingObjsLock); |
| PendingObjs.erase(&MR); |
| return Error::success(); |
| } |
| |
| void DebugObjectManagerPlugin::notifyTransferringResources(ResourceKey DstKey, |
| ResourceKey SrcKey) { |
| // Debug objects are stored by ResourceKey only after registration. |
| // Thus, pending objects don't need to be updated here. |
| std::lock_guard<std::mutex> Lock(RegisteredObjsLock); |
| auto SrcIt = RegisteredObjs.find(SrcKey); |
| if (SrcIt != RegisteredObjs.end()) { |
| // Resources from distinct MaterializationResponsibilitys can get merged |
| // after emission, so we can have multiple debug objects per resource key. |
| for (std::unique_ptr<DebugObject> &DebugObj : SrcIt->second) |
| RegisteredObjs[DstKey].push_back(std::move(DebugObj)); |
| RegisteredObjs.erase(SrcIt); |
| } |
| } |
| |
| Error DebugObjectManagerPlugin::notifyRemovingResources(ResourceKey Key) { |
| // Removing the resource for a pending object fails materialization, so they |
| // get cleaned up in the notifyFailed() handler. |
| std::lock_guard<std::mutex> Lock(RegisteredObjsLock); |
| RegisteredObjs.erase(Key); |
| |
| // TODO: Implement unregister notifications. |
| return Error::success(); |
| } |
| |
| } // namespace orc |
| } // namespace llvm |