diff --git a/llvm/include/llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h b/llvm/include/llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h index d946a029fd2ec..7a6e02d910dfa 100644 --- a/llvm/include/llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h +++ b/llvm/include/llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h @@ -16,6 +16,7 @@ #include "llvm/ExecutionEngine/JITLink/JITLink.h" #include "llvm/ExecutionEngine/Orc/Core.h" #include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" +#include "llvm/ExecutionEngine/Orc/Shared/AllocationActions.h" #include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/Error.h" @@ -23,7 +24,6 @@ #include "llvm/Support/MemoryBufferRef.h" #include "llvm/TargetParser/Triple.h" -#include #include #include #include @@ -33,35 +33,24 @@ namespace orc { class DebugObject; -/// Creates and manages DebugObjects for JITLink artifacts. -/// -/// DebugObjects are created when linking for a MaterializationResponsibility -/// starts. They are pending as long as materialization is in progress. -/// -/// There can only be one pending DebugObject per MaterializationResponsibility. -/// If materialization fails, pending DebugObjects are discarded. -/// -/// Once executable code for the MaterializationResponsibility is emitted, the -/// corresponding DebugObject is finalized to target memory and the provided -/// DebugObjectRegistrar is notified. Ownership of DebugObjects remains with the -/// plugin. +/// Debugger support for ELF platforms with the GDB JIT Interface. The plugin +/// emits and manages a separate debug object allocation in addition to the +/// LinkGraph's own allocation and it notifies the debugger when necessary. /// class LLVM_ABI ELFDebugObjectPlugin : public ObjectLinkingLayer::Plugin { public: - /// Create the plugin to submit DebugObjects for JITLink artifacts. For all - /// options the recommended setting is true. + /// Create the plugin for the given session and set additional options /// /// RequireDebugSections: - /// Submit debug objects to the executor only if they contain actual debug - /// info. Turning this off may allow minimal debugging based on raw symbol - /// names. Note that this may cause significant memory and transport - /// overhead for objects built with a release configuration. + /// Emit debug objects only if the LinkGraph contains debug info. Turning + /// this off allows minimal debugging based on raw symbol names, but it + /// comes with significant overhead for release configurations. /// /// AutoRegisterCode: /// Notify the debugger for each new debug object. This is a good default /// mode, but it may cause significant overhead when adding many modules in - /// sequence. When turning this off, the user has to issue the call to - /// __jit_debug_register_code() on the executor side manually. + /// sequence. Otherwise the user must call __jit_debug_register_code() in + /// the debug session manually. /// ELFDebugObjectPlugin(ExecutionSession &ES, bool RequireDebugSections, bool AutoRegisterCode, Error &Err); @@ -69,31 +58,36 @@ class LLVM_ABI ELFDebugObjectPlugin : public ObjectLinkingLayer::Plugin { void notifyMaterializing(MaterializationResponsibility &MR, jitlink::LinkGraph &G, jitlink::JITLinkContext &Ctx, - MemoryBufferRef InputObject) override; + MemoryBufferRef InputObj) override; + Error notifyEmitted(MaterializationResponsibility &MR) override; Error notifyFailed(MaterializationResponsibility &MR) override; - Error notifyRemovingResources(JITDylib &JD, ResourceKey K) override; - - void notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, - ResourceKey SrcKey) override; void modifyPassConfig(MaterializationResponsibility &MR, jitlink::LinkGraph &LG, jitlink::PassConfiguration &PassConfig) override; + Error notifyRemovingResources(JITDylib &JD, ResourceKey K) override { + return Error::success(); + } + + void notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, + ResourceKey SrcKey) override {} + private: ExecutionSession &ES; - - using OwnedDebugObject = std::unique_ptr; - std::map PendingObjs; - std::map> RegisteredObjs; + ExecutorAddr RegistrationAction; + ExecutorAddr DeallocAction; + ExecutorAddr TargetMemMgr; std::mutex PendingObjsLock; - std::mutex RegisteredObjsLock; + std::map PendingObjs; - ExecutorAddr RegistrationAction; bool RequireDebugSections; bool AutoRegisterCode; + + DebugObject *getPendingDebugObj(MaterializationResponsibility &MR); + shared::AllocActionCallPair createAllocActions(ExecutorAddrRange R); }; } // namespace orc diff --git a/llvm/lib/ExecutionEngine/Orc/Debugging/DebuggerSupport.cpp b/llvm/lib/ExecutionEngine/Orc/Debugging/DebuggerSupport.cpp index 7be58871ff57b..2c36447ca884f 100644 --- a/llvm/lib/ExecutionEngine/Orc/Debugging/DebuggerSupport.cpp +++ b/llvm/lib/ExecutionEngine/Orc/Debugging/DebuggerSupport.cpp @@ -36,8 +36,10 @@ Error enableDebuggerSupport(LLJIT &J) { switch (TT.getObjectFormat()) { case Triple::ELF: { Error TargetSymErr = Error::success(); - ObjLinkingLayer->addPlugin( - std::make_unique(ES, false, true, TargetSymErr)); + auto P = + std::make_unique(ES, false, true, TargetSymErr); + if (!TargetSymErr) + ObjLinkingLayer->addPlugin(std::move(P)); return TargetSymErr; } case Triple::MachO: { diff --git a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp index 9f556b0d07a8b..26126adb594aa 100644 --- a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp +++ b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp @@ -17,11 +17,17 @@ #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/BinaryFormat/ELF.h" +#include "llvm/ExecutionEngine/JITLink/JITLink.h" #include "llvm/ExecutionEngine/JITLink/JITLinkDylib.h" #include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h" +#include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" +#include "llvm/ExecutionEngine/Orc/Shared/MemoryFlags.h" #include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h" +#include "llvm/IR/Instructions.h" #include "llvm/Object/ELFObjectFile.h" +#include "llvm/Object/Error.h" #include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" #include "llvm/Support/MSVCErrorWorkarounds.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Process.h" @@ -37,372 +43,130 @@ 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() = default; -}; - -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; -}; - -template -void ELFDebugObjectSection::setTargetMemoryRange(SectionRange Range) { - // All recorded sections are candidates for load-address patching. - Header->sh_addr = - static_cast(Range.getStart().getValue()); -} - -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 (uint64_t Addr = Header->sh_addr) { - OS << formatv(" {0:x16} {1}\n", Addr, Name); - } else { - OS << formatv(" {0}\n", Name); - } -} - -enum DebugObjectFlags : int { - // Request final target memory load-addresses for all sections. - ReportFinalSectionLoadAddresses = 1 << 0, - - // We found sections with debug information when processing the input object. - HasDebugSections = 1 << 1, -}; - -/// 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. -/// +// Helper class to emit and fixup an individual debug object class DebugObject { public: - DebugObject(JITLinkMemoryManager &MemMgr, const JITLinkDylib *JD, + using FinalizedAlloc = JITLinkMemoryManager::FinalizedAlloc; + + DebugObject(StringRef Name, SimpleSegmentAlloc Alloc, JITLinkContext &Ctx, ExecutionSession &ES) - : MemMgr(MemMgr), JD(JD), ES(ES), Flags(DebugObjectFlags{}) { + : Name(Name), WorkingMem(std::move(Alloc)), + MemMgr(Ctx.getMemoryManager()), ES(ES) { FinalizeFuture = FinalizePromise.get_future(); } - bool hasFlags(DebugObjectFlags F) const { return Flags & F; } - void setFlags(DebugObjectFlags F) { - Flags = static_cast(Flags | F); + StringRef getName() const { return Name; } + + StringRef getBuffer() { + MutableArrayRef Buffer = getMutBuffer(); + return StringRef(Buffer.data(), Buffer.size()); } - void clearFlags(DebugObjectFlags F) { - Flags = static_cast(Flags & ~F); + + MutableArrayRef getMutBuffer() { + auto SegInfo = WorkingMem.getSegInfo(MemProt::Read); + return SegInfo.WorkingMem; } - using FinalizeContinuation = std::function)>; - void finalizeAsync(FinalizeContinuation OnAsync); + SimpleSegmentAlloc &getTargetAlloc() { return WorkingMem; } - void failMaterialization(Error Err) { - FinalizePromise.set_value(std::move(Err)); - } + Expected awaitTargetMem() { return FinalizeFuture.get(); } void reportTargetMem(ExecutorAddrRange TargetMem) { FinalizePromise.set_value(TargetMem); } - Expected awaitTargetMem() { return FinalizeFuture.get(); } - - 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)); - } + void failMaterialization(Error Err) { + FinalizePromise.set_value(std::move(Err)); } - virtual void reportSectionTargetMemoryRange(StringRef Name, - SectionRange TargetMem) {} + void reportError(Error Err) { ES.reportError(std::move(Err)); } -protected: - using InFlightAlloc = JITLinkMemoryManager::InFlightAlloc; - using FinalizedAlloc = JITLinkMemoryManager::FinalizedAlloc; + using GetLoadAddressFn = llvm::unique_function; + void visitSections(GetLoadAddressFn Callback); - virtual Expected finalizeWorkingMemory() = 0; + template + void visitSectionLoadAddresses(GetLoadAddressFn Callback); +private: + std::string Name; + SimpleSegmentAlloc WorkingMem; JITLinkMemoryManager &MemMgr; - const JITLinkDylib *JD = nullptr; ExecutionSession &ES; std::promise> FinalizePromise; std::future> FinalizeFuture; - -private: - DebugObjectFlags Flags; - 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(!this->Alloc && "Cannot finalize more than once"); - if (auto SimpleSegAlloc = finalizeWorkingMemory()) { - auto ROSeg = SimpleSegAlloc->getSegInfo(MemProt::Read); - ExecutorAddrRange DebugObjRange(ROSeg.Addr, ROSeg.WorkingMem.size()); - SimpleSegAlloc->finalize( - [this, DebugObjRange, - OnFinalize = std::move(OnFinalize)](Expected FA) { - if (FA) { - // Note: FA->getAddress() is supposed to be the address of the - // memory range on the target, but InProcessMemoryManager returns - // the address of a FinalizedAllocInfo helper instead. - this->Alloc = std::move(*FA); - OnFinalize(DebugObjRange); - } else - OnFinalize(FA.takeError()); - }); - } else { - // We could report this error synchronously, but it's easier this way, - // because the FinalizePromise will be triggered unconditionally. - 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)) { - setFlags(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) { +void DebugObject::visitSectionLoadAddresses(GetLoadAddressFn Callback) { 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(); + Expected> ObjRef = ELFFile::create(getBuffer()); + if (!ObjRef) { + reportError(ObjRef.takeError()); + return; + } Expected> Sections = ObjRef->sections(); - if (!Sections) - return Sections.takeError(); + if (!Sections) { + reportError(Sections.takeError()); + return; + } for (const SectionHeader &Header : *Sections) { Expected Name = ObjRef->getSectionName(Header); - if (!Name) - return Name.takeError(); + if (!Name) { + reportError(Name.takeError()); + return; + } if (Name->empty()) continue; - if (isDwarfSection(*Name)) - DebugObj->setFlags(HasDebugSections); - - // Only record text and data sections (i.e. no bss, comments, rel, etc.) - if (Header.sh_type != ELF::SHT_PROGBITS && - Header.sh_type != ELF::SHT_X86_64_UNWIND) - continue; - if (!(Header.sh_flags & ELF::SHF_ALLOC)) - continue; - - auto Wrapped = std::make_unique>(&Header); - if (Error Err = DebugObj->recordSection(*Name, std::move(Wrapped))) - return std::move(Err); + ExecutorAddr LoadAddress = Callback(*Name); + const_cast(Header).sh_addr = + static_cast(LoadAddress.getValue()); } - return std::move(DebugObj); + LLVM_DEBUG({ + dbgs() << "Section load-addresses in debug object for \"" << getName() + << "\":\n"; + for (const SectionHeader &Header : *Sections) { + StringRef Name = cantFail(ObjRef->getSectionName(Header)); + if (uint64_t Addr = Header.sh_addr) { + dbgs() << formatv(" {0:x16} {1}\n", Addr, Name); + } else { + dbgs() << formatv(" {0}\n", Name); + } + } + }); } -Expected> -ELFDebugObject::Create(MemoryBufferRef Buffer, JITLinkContext &Ctx, - ExecutionSession &ES) { +void DebugObject::visitSections(GetLoadAddressFn Callback) { unsigned char Class, Endian; - std::tie(Class, Endian) = getElfArchType(Buffer.getBuffer()); + std::tie(Class, Endian) = getElfArchType(getBuffer()); - if (Class == ELF::ELFCLASS32) { + switch (Class) { + case ELF::ELFCLASS32: if (Endian == ELF::ELFDATA2LSB) - return CreateArchType(Buffer, Ctx.getMemoryManager(), - Ctx.getJITLinkDylib(), ES); + return visitSectionLoadAddresses(std::move(Callback)); if (Endian == ELF::ELFDATA2MSB) - return CreateArchType(Buffer, Ctx.getMemoryManager(), - Ctx.getJITLinkDylib(), ES); - return nullptr; - } - if (Class == ELF::ELFCLASS64) { + return visitSectionLoadAddresses(std::move(Callback)); + return reportError(createStringError( + object_error::invalid_file_type, + "Invalid endian in 32-bit ELF object file: %x", Endian)); + + case ELF::ELFCLASS64: if (Endian == ELF::ELFDATA2LSB) - return CreateArchType(Buffer, Ctx.getMemoryManager(), - Ctx.getJITLinkDylib(), ES); + return visitSectionLoadAddresses(std::move(Callback)); 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, ES.getSymbolStringPool(), ES.getTargetTriple(), 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; - bool Inserted = Sections.try_emplace(Name, std::move(Section)).second; - if (!Inserted) - LLVM_DEBUG(dbgs() << "Skipping debug registration for section '" << Name - << "' in object " << Buffer->getBufferIdentifier() - << " (duplicate name)\n"); - 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); + return visitSectionLoadAddresses(std::move(Callback)); + return reportError(createStringError( + object_error::invalid_file_type, + "Invalid endian in 64-bit ELF object file: %x", Endian)); default: - // TODO: Once we add support for other formats, we might want to split this - // into multiple files. - return nullptr; + return reportError(createStringError(object_error::invalid_file_type, + "Invalid arch in ELF object file: %x", + Class)); } } @@ -413,129 +177,165 @@ ELFDebugObjectPlugin::ELFDebugObjectPlugin(ExecutionSession &ES, AutoRegisterCode(AutoRegisterCode) { // Pass bootstrap symbol for registration function to enable debugging ErrorAsOutParameter _(&Err); - Err = ES.getExecutorProcessControl().getBootstrapSymbols( - {{RegistrationAction, rt::RegisterJITLoaderGDBAllocActionName}}); + Err = ES.getExecutorProcessControl().getBootstrapSymbols({ + {RegistrationAction, rt::RegisterJITLoaderGDBAllocActionName}, + {DeallocAction, rt::SimpleExecutorMemoryManagerReleaseWrapperName}, + {TargetMemMgr, rt::SimpleExecutorMemoryManagerInstanceName}, + }); } ELFDebugObjectPlugin::~ELFDebugObjectPlugin() = default; +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; +} + void ELFDebugObjectPlugin::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"); + MemoryBufferRef InputObj) { + if (G.getTargetTriple().getObjectFormat() != Triple::ELF) + return; - if (auto DebugObj = createDebugObjectFromBuffer(ES, G, Ctx, ObjBuffer)) { - // Not all link artifacts allow debugging. - if (*DebugObj == nullptr) - return; - if (RequireDebugSections && !(**DebugObj).hasFlags(HasDebugSections)) { - LLVM_DEBUG(dbgs() << "Skipping debug registration for LinkGraph '" - << G.getName() << "': no debug info\n"); - return; - } - PendingObjs[&MR] = std::move(*DebugObj); - } else { - ES.reportError(DebugObj.takeError()); + // Step 1: We copy the raw input object into the working memory of a + // single-segment read-only allocation + size_t Size = InputObj.getBufferSize(); + auto Alignment = sys::Process::getPageSizeEstimate(); + SimpleSegmentAlloc::Segment Segment{Size, Align(Alignment)}; + + auto Alloc = SimpleSegmentAlloc::Create( + Ctx.getMemoryManager(), ES.getSymbolStringPool(), ES.getTargetTriple(), + Ctx.getJITLinkDylib(), {{MemProt::Read, Segment}}); + if (!Alloc) { + ES.reportError(Alloc.takeError()); + return; } + + std::lock_guard Lock(PendingObjsLock); + auto [It, Inserted] = PendingObjs.try_emplace( + &MR, InputObj.getBufferIdentifier(), std::move(*Alloc), Ctx, ES); + assert(Inserted && "One debug object per materialization"); + memcpy(It->second.getMutBuffer().data(), InputObj.getBufferStart(), Size); +} + +DebugObject * +ELFDebugObjectPlugin::getPendingDebugObj(MaterializationResponsibility &MR) { + std::lock_guard Lock(PendingObjsLock); + auto It = PendingObjs.find(&MR); + return It == PendingObjs.end() ? nullptr : &It->second; } void ELFDebugObjectPlugin::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()) + if (!getPendingDebugObj(MR)) return; - DebugObject &DebugObj = *It->second; - if (DebugObj.hasFlags(ReportFinalSectionLoadAddresses)) { - PassConfig.PostAllocationPasses.push_back( - [&DebugObj](LinkGraph &Graph) -> Error { - for (const Section &GraphSection : Graph.sections()) - DebugObj.reportSectionTargetMemoryRange(GraphSection.getName(), - SectionRange(GraphSection)); - return Error::success(); + PassConfig.PostAllocationPasses.push_back([this, &MR](LinkGraph &G) -> Error { + size_t SectionsPatched = 0; + bool HasDebugSections = false; + DebugObject *DebugObj = getPendingDebugObj(MR); + assert(DebugObj && "Don't inject passes if we have no debug object"); + + // Step 2: Once the target memory layout is ready, we write the + // addresses of the LinkGraph sections into the load-address fields of the + // section headers in our debug object allocation + DebugObj->visitSections( + [&G, &SectionsPatched, &HasDebugSections](StringRef Name) { + SectionsPatched += 1; + if (isDwarfSection(Name)) + HasDebugSections = true; + Section *S = G.findSectionByName(Name); + assert(S && "No graph section for object section"); + return SectionRange(*S).getStart(); }); - PassConfig.PreFixupPasses.push_back( - [this, &DebugObj, &MR](LinkGraph &G) -> Error { - DebugObj.finalizeAsync([this, &DebugObj, - &MR](Expected TargetMem) { - if (!TargetMem) { - DebugObj.failMaterialization(TargetMem.takeError()); - return; - } - // Update tracking info - Error Err = MR.withResourceKeyDo([&](ResourceKey K) { - std::lock_guard LockPending(PendingObjsLock); - std::lock_guard LockRegistered(RegisteredObjsLock); - auto It = PendingObjs.find(&MR); - RegisteredObjs[K].push_back(std::move(It->second)); - PendingObjs.erase(It); - }); - - if (Err) - DebugObj.failMaterialization(std::move(Err)); - - // Unblock post-fixup pass - DebugObj.reportTargetMem(*TargetMem); - }); - return Error::success(); - }); + if (!SectionsPatched) { + LLVM_DEBUG(dbgs() << "Skipping debug registration for LinkGraph '" + << G.getName() << "': no debug info\n"); + return Error::success(); + } - PassConfig.PostFixupPasses.push_back( - [this, &DebugObj](LinkGraph &G) -> Error { - Expected R = DebugObj.awaitTargetMem(); - if (!R) - return R.takeError(); - if (R->empty()) - return Error::success(); - - using namespace shared; - G.allocActions().push_back( - {cantFail(WrapperFunctionCall::Create< - SPSArgList>( - RegistrationAction, *R, AutoRegisterCode)), - {/* no deregistration */}}); - return Error::success(); - }); - } + if (RequireDebugSections && !HasDebugSections) { + LLVM_DEBUG(dbgs() << "Skipping debug registration for LinkGraph '" + << G.getName() << "': no debug info\n"); + return Error::success(); + } + + // Step 3: We start copying the debug object into target memory + auto &Alloc = DebugObj->getTargetAlloc(); + + // FIXME: The lookup in the segment info here is a workaround. The below + // FA->release() is supposed to provide the base address in target memory, + // but InProcessMemoryManager returns the address of a FinalizedAllocInfo + // helper instead. + auto ROSeg = Alloc.getSegInfo(MemProt::Read); + ExecutorAddrRange R(ROSeg.Addr, ROSeg.WorkingMem.size()); + Alloc.finalize([this, R, &MR](Expected FA) { + DebugObject *DebugObj = getPendingDebugObj(MR); + if (!FA) + DebugObj->failMaterialization(FA.takeError()); + + // Dealloc action from the LinkGraph's allocation will free target memory + FA->release(); + + // Unblock post-fixup pass + DebugObj->reportTargetMem(R); + }); + + return Error::success(); + }); + + PassConfig.PostFixupPasses.push_back([this, &MR](LinkGraph &G) -> Error { + // Step 4: We wait for the debug object copy to finish, so we can + // register the memory range with the GDB JIT Interface in an allocation + // action of the LinkGraph's own allocation + DebugObject *DebugObj = getPendingDebugObj(MR); + Expected R = DebugObj->awaitTargetMem(); + if (!R) + return R.takeError(); + if (R->empty()) + return Error::success(); + + // Step 5: Use allocation actions to (1) register the debug object with the + // GDB JIT Interface and (2) free the debug object when the corresponding + // code is removed + using namespace shared; + G.allocActions().push_back(createAllocActions(*R)); + return Error::success(); + }); } -Error ELFDebugObjectPlugin::notifyFailed(MaterializationResponsibility &MR) { +shared::AllocActionCallPair +ELFDebugObjectPlugin::createAllocActions(ExecutorAddrRange R) { + using namespace shared; + // Add the target memory range to __jit_debug_descriptor + auto Init = cantFail( + WrapperFunctionCall::Create>( + RegistrationAction, R, AutoRegisterCode)); + // Free the debug object's target memory block + auto Fini = + cantFail(WrapperFunctionCall::Create< + SPSArgList>>( + DeallocAction, TargetMemMgr, ArrayRef(R.Start))); + return {Init, Fini}; +} + +Error ELFDebugObjectPlugin::notifyEmitted(MaterializationResponsibility &MR) { std::lock_guard Lock(PendingObjsLock); PendingObjs.erase(&MR); return Error::success(); } -void ELFDebugObjectPlugin::notifyTransferringResources(JITDylib &JD, - 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 ELFDebugObjectPlugin::notifyRemovingResources(JITDylib &JD, - 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. +Error ELFDebugObjectPlugin::notifyFailed(MaterializationResponsibility &MR) { + std::lock_guard Lock(PendingObjsLock); + PendingObjs.erase(&MR); return Error::success(); }