//===------- 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 #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 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(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 void ELFDebugObjectSection::setTargetMemoryRange(SectionRange Range) { // Only patch load-addresses for executable and data sections. if (isTextOrDataSection()) Header->sh_addr = static_cast(Range.getStart().getValue()); } template bool ELFDebugObjectSection::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 Error ELFDebugObjectSection::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(Header); if (HeaderPtr < Start || HeaderPtr + sizeof(typename ELFT::Shdr) > End) return make_error( 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( 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 void ELFDebugObjectSection::dump(raw_ostream &OS, StringRef Name) { if (auto Addr = static_cast(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 finalizeAsync(FinalizeContinuation OnFinalize); virtual ~DebugObject() { if (Alloc) { std::vector 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 finalizeWorkingMemory() = 0; JITLinkMemoryManager &MemMgr; const JITLinkDylib *JD = nullptr; private: ExecutionSession &ES; std::set 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 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> Create(MemoryBufferRef Buffer, JITLinkContext &Ctx, ExecutionSession &ES); void reportSectionTargetMemoryRange(StringRef Name, SectionRange TargetMem) override; StringRef getBuffer() const { return Buffer->getMemBufferRef().getBuffer(); } protected: Expected finalizeWorkingMemory() override; template Error recordSection(StringRef Name, std::unique_ptr> Section); DebugObjectSection *getSection(StringRef Name); private: template static Expected> CreateArchType(MemoryBufferRef Buffer, JITLinkMemoryManager &MemMgr, const JITLinkDylib *JD, ExecutionSession &ES); static std::unique_ptr CopyBuffer(MemoryBufferRef Buffer, Error &Err); ELFDebugObject(std::unique_ptr Buffer, JITLinkMemoryManager &MemMgr, const JITLinkDylib *JD, ExecutionSession &ES) : DebugObject(MemMgr, JD, ES), Buffer(std::move(Buffer)) { set(Requirement::ReportFinalSectionLoadAddresses); } std::unique_ptr Buffer; StringMap> Sections; }; static const std::set 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 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 Expected> ELFDebugObject::CreateArchType(MemoryBufferRef Buffer, JITLinkMemoryManager &MemMgr, const JITLinkDylib *JD, ExecutionSession &ES) { using SectionHeader = typename ELFT::Shdr; Error Err = Error::success(); std::unique_ptr DebugObj( new ELFDebugObject(CopyBuffer(Buffer, Err), MemMgr, JD, ES)); if (Err) return std::move(Err); Expected> ObjRef = ELFFile::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> Sections = ObjRef->sections(); if (!Sections) return Sections.takeError(); bool HasDwarfSection = false; for (const SectionHeader &Header : *Sections) { Expected Name = ObjRef->getSectionName(Header); if (!Name) return Name.takeError(); if (Name->empty()) continue; HasDwarfSection |= isDwarfSection(*Name); auto Wrapped = std::make_unique>(&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> 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(Buffer, Ctx.getMemoryManager(), Ctx.getJITLinkDylib(), ES); if (Endian == ELF::ELFDATA2MSB) return CreateArchType(Buffer, Ctx.getMemoryManager(), Ctx.getJITLinkDylib(), ES); return nullptr; } if (Class == ELF::ELFCLASS64) { if (Endian == ELF::ELFDATA2LSB) return CreateArchType(Buffer, Ctx.getMemoryManager(), Ctx.getJITLinkDylib(), ES); if (Endian == ELF::ELFDATA2MSB) return CreateArchType(Buffer, Ctx.getMemoryManager(), Ctx.getJITLinkDylib(), ES); return nullptr; } return nullptr; } Expected 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 Error ELFDebugObject::recordSection( StringRef Name, std::unique_ptr> 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("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> 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 Target) : ES(ES), Target(std::move(Target)) {} DebugObjectManagerPlugin::~DebugObjectManagerPlugin() = default; void DebugObjectManagerPlugin::notifyMaterializing( MaterializationResponsibility &MR, LinkGraph &G, JITLinkContext &Ctx, MemoryBufferRef ObjBuffer) { std::lock_guard 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 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 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 FinalizePromise; std::future FinalizeErr = FinalizePromise.get_future(); It->second->finalizeAsync( [this, &FinalizePromise, &MR](Expected 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 Lock(RegisteredObjsLock); RegisteredObjs[K].push_back(std::move(PendingObjs[&MR])); PendingObjs.erase(&MR); })); }); return FinalizeErr.get(); } Error DebugObjectManagerPlugin::notifyFailed( MaterializationResponsibility &MR) { std::lock_guard 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 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 &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 Lock(RegisteredObjsLock); RegisteredObjs.erase(Key); // TODO: Implement unregister notifications. return Error::success(); } } // namespace orc } // namespace llvm