//===---- DebugObjectManagerPlugin.h - JITLink debug objects ---*- 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 // //===----------------------------------------------------------------------===// #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/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()); } } 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); } } static constexpr sys::Memory::ProtectionFlags ReadOnly = static_cast(sys::Memory::MF_READ); enum class Requirement { // Request final target memory load-addresses for all sections. ReportFinalSectionLoadAddresses, }; /// The plugin creates a debug object from JITLinkContext 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(JITLinkContext &Ctx) : Ctx(Ctx) {} virtual ~DebugObject() = default; 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); Error deallocate() { if (Alloc) return Alloc->deallocate(); return Error::success(); } virtual void reportSectionTargetMemoryRange(StringRef Name, SectionRange TargetMem) {} protected: using Allocation = JITLinkMemoryManager::Allocation; virtual Expected> finalizeWorkingMemory(JITLinkContext &Ctx) = 0; private: JITLinkContext &Ctx; std::set Reqs; std::unique_ptr Alloc{nullptr}; }; // 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 == nullptr && "Cannot finalize more than once"); auto AllocOrErr = finalizeWorkingMemory(Ctx); if (!AllocOrErr) OnFinalize(AllocOrErr.takeError()); Alloc = std::move(*AllocOrErr); Alloc->finalizeAsync([this, OnFinalize](Error Err) { if (Err) OnFinalize(std::move(Err)); else OnFinalize(sys::MemoryBlock( jitTargetAddressToPointer(Alloc->getTargetMemory(ReadOnly)), Alloc->getWorkingMemory(ReadOnly).size())); }); } /// 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); void reportSectionTargetMemoryRange(StringRef Name, SectionRange TargetMem) override; StringRef getBuffer() const { return Buffer->getMemBufferRef().getBuffer(); } protected: Expected> finalizeWorkingMemory(JITLinkContext &Ctx) override; template Error recordSection(StringRef Name, std::unique_ptr> Section); DebugObjectSection *getSection(StringRef Name); private: template static Expected> CreateArchType(MemoryBufferRef Buffer, JITLinkContext &Ctx); static std::unique_ptr CopyBuffer(MemoryBufferRef Buffer, Error &Err); ELFDebugObject(std::unique_ptr Buffer, JITLinkContext &Ctx) : DebugObject(Ctx), 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, JITLinkContext &Ctx) { using SectionHeader = typename ELFT::Shdr; Error Err = Error::success(); std::unique_ptr DebugObj( new ELFDebugObject(CopyBuffer(Buffer, Err), Ctx)); 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) { unsigned char Class, Endian; std::tie(Class, Endian) = getElfArchType(Buffer.getBuffer()); if (Class == ELF::ELFCLASS32) { if (Endian == ELF::ELFDATA2LSB) return CreateArchType(Buffer, Ctx); if (Endian == ELF::ELFDATA2MSB) return CreateArchType(Buffer, Ctx); return nullptr; } if (Class == ELF::ELFCLASS64) { if (Endian == ELF::ELFDATA2LSB) return CreateArchType(Buffer, Ctx); if (Endian == ELF::ELFDATA2MSB) return CreateArchType(Buffer, Ctx); return nullptr; } return nullptr; } Expected> ELFDebugObject::finalizeWorkingMemory(JITLinkContext &Ctx) { 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 Alignment = sys::Process::getPageSizeEstimate(); JITLinkMemoryManager &MemMgr = Ctx.getMemoryManager(); const JITLinkDylib *JD = Ctx.getJITLinkDylib(); size_t Size = Buffer->getBufferSize(); // Allocate working memory for debug object in read-only segment. JITLinkMemoryManager::SegmentsRequestMap SingleReadOnlySegment; SingleReadOnlySegment[ReadOnly] = JITLinkMemoryManager::SegmentRequest(Alignment, Size, 0); auto AllocOrErr = MemMgr.allocate(JD, SingleReadOnlySegment); if (!AllocOrErr) return AllocOrErr.takeError(); // Initialize working memory with a copy of our object buffer. // TODO: Use our buffer as working memory directly. std::unique_ptr Alloc = std::move(*AllocOrErr); MutableArrayRef WorkingMem = Alloc->getWorkingMemory(ReadOnly); memcpy(WorkingMem.data(), Buffer->getBufferStart(), Size); Buffer.reset(); return std::move(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(); } static ResourceKey getResourceKey(MaterializationResponsibility &MR) { ResourceKey Key; if (auto Err = MR.withResourceKeyDo([&](ResourceKey K) { Key = K; })) { MR.getExecutionSession().reportError(std::move(Err)); return ResourceKey{}; } assert(Key && "Invalid key"); return Key; } /// Creates a debug object based on the input object file from /// ObjectLinkingLayerJITLinkContext. /// static Expected> createDebugObjectFromBuffer(LinkGraph &G, JITLinkContext &Ctx, MemoryBufferRef ObjBuffer) { switch (G.getTargetTriple().getObjectFormat()) { case Triple::ELF: return ELFDebugObject::Create(ObjBuffer, Ctx); 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() { for (auto &KV : PendingObjs) { std::unique_ptr &DebugObj = KV.second; if (Error Err = DebugObj->deallocate()) ES.reportError(std::move(Err)); } for (auto &KV : RegisteredObjs) { for (std::unique_ptr &DebugObj : KV.second) if (Error Err = DebugObj->deallocate()) ES.reportError(std::move(Err)); } } void DebugObjectManagerPlugin::notifyMaterializing( MaterializationResponsibility &MR, LinkGraph &G, JITLinkContext &Ctx, MemoryBufferRef ObjBuffer) { assert(PendingObjs.count(getResourceKey(MR)) == 0 && "Cannot have more than one pending debug object per " "MaterializationResponsibility"); std::lock_guard Lock(PendingObjsLock); if (auto DebugObj = createDebugObjectFromBuffer(G, Ctx, ObjBuffer)) { // Not all link artifacts allow debugging. if (*DebugObj != nullptr) { ResourceKey Key = getResourceKey(MR); PendingObjs[Key] = std::move(*DebugObj); } } else { ES.reportError(DebugObj.takeError()); } } void DebugObjectManagerPlugin::modifyPassConfig( MaterializationResponsibility &MR, const Triple &TT, PassConfiguration &PassConfig) { // Not all link artifacts have associated debug objects. std::lock_guard Lock(PendingObjsLock); auto It = PendingObjs.find(getResourceKey(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) { ResourceKey Key = getResourceKey(MR); std::lock_guard Lock(PendingObjsLock); auto It = PendingObjs.find(Key); if (It == PendingObjs.end()) return Error::success(); DebugObject *UnownedDebugObj = It->second.release(); PendingObjs.erase(It); // FIXME: We released ownership of the DebugObject, so we can easily capture // the raw pointer in the continuation function, which re-owns it immediately. if (UnownedDebugObj) UnownedDebugObj->finalizeAsync( [this, Key, UnownedDebugObj](Expected TargetMem) { std::unique_ptr ReownedDebugObj(UnownedDebugObj); if (!TargetMem) { ES.reportError(TargetMem.takeError()); return; } if (Error Err = Target->registerDebugObject(*TargetMem)) { ES.reportError(std::move(Err)); return; } std::lock_guard Lock(RegisteredObjsLock); RegisteredObjs[Key].push_back(std::move(ReownedDebugObj)); }); return Error::success(); } Error DebugObjectManagerPlugin::notifyFailed( MaterializationResponsibility &MR) { std::lock_guard Lock(PendingObjsLock); PendingObjs.erase(getResourceKey(MR)); return Error::success(); } void DebugObjectManagerPlugin::notifyTransferringResources(ResourceKey DstKey, ResourceKey SrcKey) { { 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); } } { std::lock_guard Lock(PendingObjsLock); auto SrcIt = PendingObjs.find(SrcKey); if (SrcIt != PendingObjs.end()) { assert(PendingObjs.count(DstKey) == 0 && "Cannot have more than one pending debug object per " "MaterializationResponsibility"); PendingObjs[DstKey] = std::move(SrcIt->second); PendingObjs.erase(SrcIt); } } } Error DebugObjectManagerPlugin::notifyRemovingResources(ResourceKey K) { { std::lock_guard Lock(RegisteredObjsLock); RegisteredObjs.erase(K); // TODO: Implement unregister notifications. } std::lock_guard Lock(PendingObjsLock); PendingObjs.erase(K); return Error::success(); } } // namespace orc } // namespace llvm