From 5d5a338b5a98bf17dcf5ac0d47ed26f5a18a2cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Tue, 18 Nov 2025 10:34:31 +0100 Subject: [PATCH 1/2] [ORC] Tailor ELF debugger support plugin to load-address patching only --- .../Orc/Debugging/ELFDebugObjectPlugin.h | 34 +- .../Orc/Debugging/ELFDebugObjectPlugin.cpp | 600 +++++++----------- 2 files changed, 228 insertions(+), 406 deletions(-) diff --git a/llvm/include/llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h b/llvm/include/llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h index d946a029fd2ec..92dbfe1c79e6e 100644 --- a/llvm/include/llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h +++ b/llvm/include/llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h @@ -23,7 +23,6 @@ #include "llvm/Support/MemoryBufferRef.h" #include "llvm/TargetParser/Triple.h" -#include #include #include #include @@ -33,35 +32,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,7 +57,7 @@ class LLVM_ABI ELFDebugObjectPlugin : public ObjectLinkingLayer::Plugin { void notifyMaterializing(MaterializationResponsibility &MR, jitlink::LinkGraph &G, jitlink::JITLinkContext &Ctx, - MemoryBufferRef InputObject) override; + MemoryBufferRef InputObj) override; Error notifyFailed(MaterializationResponsibility &MR) override; Error notifyRemovingResources(JITDylib &JD, ResourceKey K) override; @@ -94,6 +82,8 @@ class LLVM_ABI ELFDebugObjectPlugin : public ObjectLinkingLayer::Plugin { ExecutorAddr RegistrationAction; bool RequireDebugSections; bool AutoRegisterCode; + + DebugObject *getPendingDebugObj(MaterializationResponsibility &MR); }; } // namespace orc diff --git a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp index 9f556b0d07a8b..6a8e11a7bbd49 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,111 +43,19 @@ 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); - } - void clearFlags(DebugObjectFlags F) { - Flags = static_cast(Flags & ~F); - } - - using FinalizeContinuation = std::function)>; - void finalizeAsync(FinalizeContinuation OnAsync); - - void failMaterialization(Error Err) { - FinalizePromise.set_value(std::move(Err)); - } - - void reportTargetMem(ExecutorAddrRange TargetMem) { - FinalizePromise.set_value(TargetMem); - } - - Expected awaitTargetMem() { return FinalizeFuture.get(); } - - virtual ~DebugObject() { + ~DebugObject() { if (Alloc) { std::vector Allocs; Allocs.push_back(std::move(Alloc)); @@ -150,259 +64,122 @@ class DebugObject { } } - virtual void reportSectionTargetMemoryRange(StringRef Name, - SectionRange TargetMem) {} + StringRef getName() const { return Name; } -protected: - using InFlightAlloc = JITLinkMemoryManager::InFlightAlloc; - using FinalizedAlloc = JITLinkMemoryManager::FinalizedAlloc; + StringRef getBuffer() { + MutableArrayRef Buffer = getMutBuffer(); + return StringRef(Buffer.data(), Buffer.size()); + } - virtual Expected finalizeWorkingMemory() = 0; + MutableArrayRef getMutBuffer() { + auto SegInfo = WorkingMem.getSegInfo(MemProt::Read); + return SegInfo.WorkingMem; + } - JITLinkMemoryManager &MemMgr; - const JITLinkDylib *JD = nullptr; - ExecutionSession &ES; + SimpleSegmentAlloc &getTargetAlloc() { return WorkingMem; } - std::promise> FinalizePromise; - std::future> FinalizeFuture; + void trackFinalizedAlloc(FinalizedAlloc FA) { Alloc = std::move(FA); } -private: - DebugObjectFlags Flags; - FinalizedAlloc Alloc; -}; + Expected awaitTargetMem() { return FinalizeFuture.get(); } -// 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()); + void reportTargetMem(ExecutorAddrRange TargetMem) { + FinalizePromise.set_value(TargetMem); } -} -/// 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; + void failMaterialization(Error Err) { + FinalizePromise.set_value(std::move(Err)); + } - StringRef getBuffer() const { return Buffer->getMemBufferRef().getBuffer(); } + void reportError(Error Err) { ES.reportError(std::move(Err)); } -protected: - Expected finalizeWorkingMemory() override; + using GetLoadAddressFn = llvm::unique_function; + void visitSections(GetLoadAddressFn Callback); template - Error recordSection(StringRef Name, - std::unique_ptr> Section); - DebugObjectSection *getSection(StringRef Name); + void visitSectionLoadAddresses(GetLoadAddressFn Callback); 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::string Name; + SimpleSegmentAlloc WorkingMem; + JITLinkMemoryManager &MemMgr; + ExecutionSession &ES; - std::unique_ptr Buffer; - StringMap> Sections; -}; + std::promise> FinalizePromise; + std::future> FinalizeFuture; -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 + FinalizedAlloc Alloc; }; -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)); } } @@ -419,91 +196,146 @@ ELFDebugObjectPlugin::ELFDebugObjectPlugin(ExecutionSession &ES, 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); + assert(PendingObjs.count(&MR) == 0 && "One debug object per materialization"); + PendingObjs[&MR] = std::make_unique( + InputObj.getBufferIdentifier(), std::move(*Alloc), Ctx, ES); + + MutableArrayRef Buffer = PendingObjs[&MR]->getMutBuffer(); + memcpy(Buffer.data(), InputObj.getBufferStart(), Size); } -void ELFDebugObjectPlugin::modifyPassConfig(MaterializationResponsibility &MR, - LinkGraph &G, - PassConfiguration &PassConfig) { - // Not all link artifacts have associated debug objects. +DebugObject * +ELFDebugObjectPlugin::getPendingDebugObj(MaterializationResponsibility &MR) { std::lock_guard Lock(PendingObjsLock); auto It = PendingObjs.find(&MR); - if (It == PendingObjs.end()) + return It == PendingObjs.end() ? nullptr : It->second.get(); +} + +void ELFDebugObjectPlugin::modifyPassConfig( + MaterializationResponsibility &MR, LinkGraph &G, + PassConfiguration &PassConfig) { + 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: FA->getAddress() below is supposed to be the address of the memory + // range on the target, 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()); + + // Keep allocation alive until the corresponding code is removed + DebugObj->trackFinalizedAlloc(std::move(*FA)); + + // 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(); + + // Step 5: We have to keep the allocation alive until the corresponding + // code is removed + 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) + return Err; + + 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(); + }); } Error ELFDebugObjectPlugin::notifyFailed(MaterializationResponsibility &MR) { From aceef1987c93ff01a791a26a0ca332af166d81a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Tue, 18 Nov 2025 12:47:03 +0100 Subject: [PATCH 2/2] Address clang-format detail --- .../ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp index 6a8e11a7bbd49..966482fef32a5 100644 --- a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp +++ b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp @@ -243,9 +243,9 @@ ELFDebugObjectPlugin::getPendingDebugObj(MaterializationResponsibility &MR) { return It == PendingObjs.end() ? nullptr : It->second.get(); } -void ELFDebugObjectPlugin::modifyPassConfig( - MaterializationResponsibility &MR, LinkGraph &G, - PassConfiguration &PassConfig) { +void ELFDebugObjectPlugin::modifyPassConfig(MaterializationResponsibility &MR, + LinkGraph &G, + PassConfiguration &PassConfig) { if (!getPendingDebugObj(MR)) return;