[llvm] Reland: [ORC] Tailor ELF debugger support plugin to load-address patching only (PR #169482)
Stefan Gränitz via llvm-commits
llvm-commits at lists.llvm.org
Mon Dec 8 05:01:06 PST 2025
https://github.com/weliveindetail updated https://github.com/llvm/llvm-project/pull/169482
>From 6f2ea5d8238314442a8505b428ed9e918a8d7ef3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Fri, 21 Nov 2025 15:05:51 +0100
Subject: [PATCH 1/7] [ORC] Tailor ELF debugger support plugin to load-address
patching only (#168518)
In 4 years the ELF debugger support plugin wasn't adapted to other
object formats or debugging approaches. After the renaming NFC in
https://github.com/llvm/llvm-project/pull/168343, this patch tailors the
plugin to ELF and section load-address patching. It allows removal of
abstractions and consolidate processing steps with the newly enabled
AllocActions from https://github.com/llvm/llvm-project/pull/168343.
The key change is to process debug sections in one place in a
post-allocation pass. Since we can handle the endianness of the ELF file
the single `visitSectionLoadAddresses()` visitor function now, we don't
need to track debug objects and sections in template classes anymore. We
keep using the `DebugObject` class and drop `DebugObjectSection`,
`ELFDebugObjectSection<ELFT>` and `ELFDebugObject`.
Furthermore, we now use the allocation's working memory for load-address
fixups directly. We can drop the `WritableMemoryBuffer` from the debug
object and most of the `finalizeWorkingMemory()` step, which saves one
copy of the entire debug object buffer. Inlining `finalizeAsync()` into
the pre-fixup pass simplifies quite some logic.
We still track `RegisteredObjs` here, because we want to free memory
once the corresponding code is freed. There will be a follow-up patch
that turns it into a dealloc action.
---
.../Orc/Debugging/ELFDebugObjectPlugin.h | 34 +-
.../Orc/Debugging/ELFDebugObjectPlugin.cpp | 598 +++++++-----------
2 files changed, 227 insertions(+), 405 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 <functional>
#include <map>
#include <memory>
#include <mutex>
@@ -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 653645ff03f15..0e9b9a7ff76d3 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 <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;
-};
-
-template <typename ELFT>
-void ELFDebugObjectSection<ELFT>::setTargetMemoryRange(SectionRange Range) {
- // All recorded sections are candidates for load-address patching.
- Header->sh_addr =
- static_cast<typename ELFT::uint>(Range.getStart().getValue());
-}
-
-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 (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<DebugObjectFlags>(Flags | F);
- }
- void clearFlags(DebugObjectFlags F) {
- Flags = static_cast<DebugObjectFlags>(Flags & ~F);
- }
-
- using FinalizeContinuation = std::function<void(Expected<ExecutorAddrRange>)>;
- void finalizeAsync(FinalizeContinuation OnAsync);
-
- void failMaterialization(Error Err) {
- FinalizePromise.set_value(std::move(Err));
- }
-
- void reportTargetMem(ExecutorAddrRange TargetMem) {
- FinalizePromise.set_value(TargetMem);
- }
-
- Expected<ExecutorAddrRange> awaitTargetMem() { return FinalizeFuture.get(); }
-
- virtual ~DebugObject() {
+ ~DebugObject() {
if (Alloc) {
std::vector<FinalizedAlloc> 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<char> Buffer = getMutBuffer();
+ return StringRef(Buffer.data(), Buffer.size());
+ }
- virtual Expected<SimpleSegmentAlloc> finalizeWorkingMemory() = 0;
+ MutableArrayRef<char> getMutBuffer() {
+ auto SegInfo = WorkingMem.getSegInfo(MemProt::Read);
+ return SegInfo.WorkingMem;
+ }
- JITLinkMemoryManager &MemMgr;
- const JITLinkDylib *JD = nullptr;
- ExecutionSession &ES;
+ SimpleSegmentAlloc &getTargetAlloc() { return WorkingMem; }
- std::promise<MSVCPExpected<ExecutorAddrRange>> FinalizePromise;
- std::future<MSVCPExpected<ExecutorAddrRange>> FinalizeFuture;
+ void trackFinalizedAlloc(FinalizedAlloc FA) { Alloc = std::move(FA); }
-private:
- DebugObjectFlags Flags;
- FinalizedAlloc Alloc;
-};
+ Expected<ExecutorAddrRange> 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<FinalizedAlloc> 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<std::unique_ptr<DebugObject>>
- 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<SimpleSegmentAlloc> finalizeWorkingMemory() override;
+ using GetLoadAddressFn = llvm::unique_function<ExecutorAddr(StringRef)>;
+ void visitSections(GetLoadAddressFn Callback);
template <typename ELFT>
- Error recordSection(StringRef Name,
- std::unique_ptr<ELFDebugObjectSection<ELFT>> Section);
- DebugObjectSection *getSection(StringRef Name);
+ void visitSectionLoadAddresses(GetLoadAddressFn Callback);
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)) {
- setFlags(ReportFinalSectionLoadAddresses);
- }
+ std::string Name;
+ SimpleSegmentAlloc WorkingMem;
+ JITLinkMemoryManager &MemMgr;
+ ExecutionSession &ES;
- std::unique_ptr<WritableMemoryBuffer> Buffer;
- StringMap<std::unique_ptr<DebugObjectSection>> Sections;
-};
+ std::promise<MSVCPExpected<ExecutorAddrRange>> FinalizePromise;
+ std::future<MSVCPExpected<ExecutorAddrRange>> FinalizeFuture;
-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
+ FinalizedAlloc Alloc;
};
-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) {
+void DebugObject::visitSectionLoadAddresses(GetLoadAddressFn Callback) {
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();
+ Expected<ELFFile<ELFT>> ObjRef = ELFFile<ELFT>::create(getBuffer());
+ if (!ObjRef) {
+ reportError(ObjRef.takeError());
+ return;
+ }
Expected<ArrayRef<SectionHeader>> Sections = ObjRef->sections();
- if (!Sections)
- return Sections.takeError();
+ if (!Sections) {
+ reportError(Sections.takeError());
+ return;
+ }
for (const SectionHeader &Header : *Sections) {
Expected<StringRef> 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<ELFDebugObjectSection<ELFT>>(&Header);
- if (Error Err = DebugObj->recordSection(*Name, std::move(Wrapped)))
- return std::move(Err);
+ ExecutorAddr LoadAddress = Callback(*Name);
+ const_cast<SectionHeader &>(Header).sh_addr =
+ static_cast<typename ELFT::uint>(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<std::unique_ptr<DebugObject>>
-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<ELF32LE>(Buffer, Ctx.getMemoryManager(),
- Ctx.getJITLinkDylib(), ES);
+ return visitSectionLoadAddresses<ELF32LE>(std::move(Callback));
if (Endian == ELF::ELFDATA2MSB)
- return CreateArchType<ELF32BE>(Buffer, Ctx.getMemoryManager(),
- Ctx.getJITLinkDylib(), ES);
- return nullptr;
- }
- if (Class == ELF::ELFCLASS64) {
+ return visitSectionLoadAddresses<ELF32BE>(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<ELF64LE>(Buffer, Ctx.getMemoryManager(),
- Ctx.getJITLinkDylib(), ES);
+ return visitSectionLoadAddresses<ELF64LE>(std::move(Callback));
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, 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 <typename ELFT>
-Error ELFDebugObject::recordSection(
- StringRef Name, std::unique_ptr<ELFDebugObjectSection<ELFT>> 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<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);
+ return visitSectionLoadAddresses<ELF64BE>(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<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;
+}
+
void ELFDebugObjectPlugin::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");
+ 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<std::mutex> Lock(PendingObjsLock);
+ assert(PendingObjs.count(&MR) == 0 && "One debug object per materialization");
+ PendingObjs[&MR] = std::make_unique<DebugObject>(
+ InputObj.getBufferIdentifier(), std::move(*Alloc), Ctx, ES);
+
+ MutableArrayRef<char> Buffer = PendingObjs[&MR]->getMutBuffer();
+ memcpy(Buffer.data(), InputObj.getBufferStart(), Size);
+}
+
+DebugObject *
+ELFDebugObjectPlugin::getPendingDebugObj(MaterializationResponsibility &MR) {
+ std::lock_guard<std::mutex> Lock(PendingObjsLock);
+ auto It = PendingObjs.find(&MR);
+ return It == PendingObjs.end() ? nullptr : It->second.get();
}
void ELFDebugObjectPlugin::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())
+ 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<ExecutorAddrRange> TargetMem) {
- if (!TargetMem) {
- DebugObj.failMaterialization(TargetMem.takeError());
- return;
- }
- // Update tracking info
- Error Err = MR.withResourceKeyDo([&](ResourceKey K) {
- std::lock_guard<std::mutex> LockPending(PendingObjsLock);
- std::lock_guard<std::mutex> 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<ExecutorAddrRange> 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<SPSExecutorAddrRange, bool>>(
- 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<DebugObject::FinalizedAlloc> 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<ExecutorAddrRange> 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<std::mutex> LockPending(PendingObjsLock);
+ std::lock_guard<std::mutex> 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<SPSExecutorAddrRange, bool>>(
+ RegistrationAction, *R, AutoRegisterCode)),
+ {/* no deregistration */}});
+ return Error::success();
+ });
}
Error ELFDebugObjectPlugin::notifyFailed(MaterializationResponsibility &MR) {
>From 9148c10876c62978438fabf5a5ddbf867e0993c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Tue, 25 Nov 2025 09:33:53 +0100
Subject: [PATCH 2/7] Return synchronous errors from
DebugObject::visitSections()
---
.../Orc/Debugging/ELFDebugObjectPlugin.cpp | 49 +++++++++----------
1 file changed, 23 insertions(+), 26 deletions(-)
diff --git a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
index 0e9b9a7ff76d3..fca6b14f5768b 100644
--- a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
@@ -93,10 +93,10 @@ class DebugObject {
void reportError(Error Err) { ES.reportError(std::move(Err)); }
using GetLoadAddressFn = llvm::unique_function<ExecutorAddr(StringRef)>;
- void visitSections(GetLoadAddressFn Callback);
+ Error visitSections(GetLoadAddressFn Callback);
template <typename ELFT>
- void visitSectionLoadAddresses(GetLoadAddressFn Callback);
+ Error visitSectionLoadAddresses(GetLoadAddressFn Callback);
private:
std::string Name;
@@ -111,27 +111,21 @@ class DebugObject {
};
template <typename ELFT>
-void DebugObject::visitSectionLoadAddresses(GetLoadAddressFn Callback) {
+Error DebugObject::visitSectionLoadAddresses(GetLoadAddressFn Callback) {
using SectionHeader = typename ELFT::Shdr;
Expected<ELFFile<ELFT>> ObjRef = ELFFile<ELFT>::create(getBuffer());
- if (!ObjRef) {
- reportError(ObjRef.takeError());
- return;
- }
+ if (!ObjRef)
+ return ObjRef.takeError();
Expected<ArrayRef<SectionHeader>> Sections = ObjRef->sections();
- if (!Sections) {
- reportError(Sections.takeError());
- return;
- }
+ if (!Sections)
+ return Sections.takeError();
for (const SectionHeader &Header : *Sections) {
Expected<StringRef> Name = ObjRef->getSectionName(Header);
- if (!Name) {
- reportError(Name.takeError());
- return;
- }
+ if (!Name)
+ return Name.takeError();
if (Name->empty())
continue;
ExecutorAddr LoadAddress = Callback(*Name);
@@ -151,9 +145,11 @@ void DebugObject::visitSectionLoadAddresses(GetLoadAddressFn Callback) {
}
}
});
+
+ return Error::success();
}
-void DebugObject::visitSections(GetLoadAddressFn Callback) {
+Error DebugObject::visitSections(GetLoadAddressFn Callback) {
unsigned char Class, Endian;
std::tie(Class, Endian) = getElfArchType(getBuffer());
@@ -163,23 +159,22 @@ void DebugObject::visitSections(GetLoadAddressFn Callback) {
return visitSectionLoadAddresses<ELF32LE>(std::move(Callback));
if (Endian == ELF::ELFDATA2MSB)
return visitSectionLoadAddresses<ELF32BE>(std::move(Callback));
- return reportError(createStringError(
- object_error::invalid_file_type,
- "Invalid endian in 32-bit ELF object file: %x", Endian));
+ return createStringError(object_error::invalid_file_type,
+ "Invalid endian in 32-bit ELF object file: %x",
+ Endian);
case ELF::ELFCLASS64:
if (Endian == ELF::ELFDATA2LSB)
return visitSectionLoadAddresses<ELF64LE>(std::move(Callback));
if (Endian == ELF::ELFDATA2MSB)
return visitSectionLoadAddresses<ELF64BE>(std::move(Callback));
- return reportError(createStringError(
- object_error::invalid_file_type,
- "Invalid endian in 64-bit ELF object file: %x", Endian));
+ return createStringError(object_error::invalid_file_type,
+ "Invalid endian in 64-bit ELF object file: %x",
+ Endian);
default:
- return reportError(createStringError(object_error::invalid_file_type,
- "Invalid arch in ELF object file: %x",
- Class));
+ return createStringError(object_error::invalid_file_type,
+ "Invalid arch in ELF object file: %x", Class);
}
}
@@ -258,7 +253,7 @@ void ELFDebugObjectPlugin::modifyPassConfig(MaterializationResponsibility &MR,
// 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(
+ Error Err = DebugObj->visitSections(
[&G, &SectionsPatched, &HasDebugSections](StringRef Name) {
SectionsPatched += 1;
if (isDwarfSection(Name))
@@ -268,6 +263,8 @@ void ELFDebugObjectPlugin::modifyPassConfig(MaterializationResponsibility &MR,
return SectionRange(*S).getStart();
});
+ if (Err)
+ return Err;
if (!SectionsPatched) {
LLVM_DEBUG(dbgs() << "Skipping debug registration for LinkGraph '"
<< G.getName() << "': no debug info\n");
>From f1203f207722a9d95c5dd73b6724308a5ef81f5d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Tue, 25 Nov 2025 09:37:33 +0100
Subject: [PATCH 3/7] Make DebugObject alloc and FinalizePromise transactional
---
.../JITLink/JITLinkMemoryManager.h | 10 ++++++++++
.../Orc/Debugging/ELFDebugObjectPlugin.cpp | 15 ++++++++++-----
2 files changed, 20 insertions(+), 5 deletions(-)
diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h b/llvm/include/llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h
index c7b1f6e0b2c36..d095b661e822a 100644
--- a/llvm/include/llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h
+++ b/llvm/include/llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h
@@ -348,6 +348,16 @@ class SimpleSegmentAlloc {
return Alloc->finalize();
}
+ /// Free allocated memory if finalize won't be called.
+ Error abandon() {
+ Error Err = Error::success();
+ Alloc->abandon([&Err](Error E) {
+ ErrorAsOutParameter _(&Err);
+ Err = std::move(E);
+ });
+ return Err;
+ }
+
private:
SimpleSegmentAlloc(
std::unique_ptr<LinkGraph> G,
diff --git a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
index fca6b14f5768b..6422328e80c03 100644
--- a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
@@ -51,9 +51,7 @@ class DebugObject {
DebugObject(StringRef Name, SimpleSegmentAlloc Alloc, JITLinkContext &Ctx,
ExecutionSession &ES)
: Name(Name), WorkingMem(std::move(Alloc)),
- MemMgr(Ctx.getMemoryManager()), ES(ES) {
- FinalizeFuture = FinalizePromise.get_future();
- }
+ MemMgr(Ctx.getMemoryManager()), ES(ES) {}
~DebugObject() {
if (Alloc) {
@@ -61,6 +59,10 @@ class DebugObject {
Allocs.push_back(std::move(Alloc));
if (Error Err = MemMgr.deallocate(std::move(Allocs)))
ES.reportError(std::move(Err));
+ } else if (!FinalizeFuture.valid()) {
+ // WorkingMem was not finalized
+ if (Error Err = WorkingMem.abandon())
+ ES.reportError(std::move(Err));
}
}
@@ -76,7 +78,10 @@ class DebugObject {
return SegInfo.WorkingMem;
}
- SimpleSegmentAlloc &getTargetAlloc() { return WorkingMem; }
+ SimpleSegmentAlloc takeTargetAlloc() {
+ FinalizeFuture = FinalizePromise.get_future();
+ return std::move(WorkingMem);
+ }
void trackFinalizedAlloc(FinalizedAlloc FA) { Alloc = std::move(FA); }
@@ -278,7 +283,7 @@ void ELFDebugObjectPlugin::modifyPassConfig(MaterializationResponsibility &MR,
}
// Step 3: We start copying the debug object into target memory
- auto &Alloc = DebugObj->getTargetAlloc();
+ SimpleSegmentAlloc Alloc = DebugObj->takeTargetAlloc();
// FIXME: FA->getAddress() below is supposed to be the address of the memory
// range on the target, but InProcessMemoryManager returns the address of a
>From 4644fa6d0eefc7a04076f087d6bc8696b70f5132 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Tue, 25 Nov 2025 09:53:42 +0100
Subject: [PATCH 4/7] Drop unused functions from DebugObject class (NFC)
---
.../Orc/Debugging/ELFDebugObjectPlugin.cpp | 22 +++++++------------
1 file changed, 8 insertions(+), 14 deletions(-)
diff --git a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
index 6422328e80c03..c6ce4e5e71686 100644
--- a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
@@ -66,14 +66,7 @@ class DebugObject {
}
}
- StringRef getName() const { return Name; }
-
- StringRef getBuffer() {
- MutableArrayRef<char> Buffer = getMutBuffer();
- return StringRef(Buffer.data(), Buffer.size());
- }
-
- MutableArrayRef<char> getMutBuffer() {
+ MutableArrayRef<char> getBuffer() {
auto SegInfo = WorkingMem.getSegInfo(MemProt::Read);
return SegInfo.WorkingMem;
}
@@ -95,8 +88,6 @@ class DebugObject {
FinalizePromise.set_value(std::move(Err));
}
- void reportError(Error Err) { ES.reportError(std::move(Err)); }
-
using GetLoadAddressFn = llvm::unique_function<ExecutorAddr(StringRef)>;
Error visitSections(GetLoadAddressFn Callback);
@@ -119,7 +110,9 @@ template <typename ELFT>
Error DebugObject::visitSectionLoadAddresses(GetLoadAddressFn Callback) {
using SectionHeader = typename ELFT::Shdr;
- Expected<ELFFile<ELFT>> ObjRef = ELFFile<ELFT>::create(getBuffer());
+ MutableArrayRef<char> Buffer = getBuffer();
+ StringRef BufferRef(Buffer.data(), Buffer.size());
+ Expected<ELFFile<ELFT>> ObjRef = ELFFile<ELFT>::create(BufferRef);
if (!ObjRef)
return ObjRef.takeError();
@@ -139,7 +132,7 @@ Error DebugObject::visitSectionLoadAddresses(GetLoadAddressFn Callback) {
}
LLVM_DEBUG({
- dbgs() << "Section load-addresses in debug object for \"" << getName()
+ dbgs() << "Section load-addresses in debug object for \"" << Name
<< "\":\n";
for (const SectionHeader &Header : *Sections) {
StringRef Name = cantFail(ObjRef->getSectionName(Header));
@@ -156,7 +149,8 @@ Error DebugObject::visitSectionLoadAddresses(GetLoadAddressFn Callback) {
Error DebugObject::visitSections(GetLoadAddressFn Callback) {
unsigned char Class, Endian;
- std::tie(Class, Endian) = getElfArchType(getBuffer());
+ MutableArrayRef<char> Buf = getBuffer();
+ std::tie(Class, Endian) = getElfArchType(StringRef(Buf.data(), Buf.size()));
switch (Class) {
case ELF::ELFCLASS32:
@@ -232,7 +226,7 @@ void ELFDebugObjectPlugin::notifyMaterializing(
PendingObjs[&MR] = std::make_unique<DebugObject>(
InputObj.getBufferIdentifier(), std::move(*Alloc), Ctx, ES);
- MutableArrayRef<char> Buffer = PendingObjs[&MR]->getMutBuffer();
+ MutableArrayRef<char> Buffer = PendingObjs[&MR]->getBuffer();
memcpy(Buffer.data(), InputObj.getBufferStart(), Size);
}
>From 1618aab43c17a3e9337120692363844a727960c7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Tue, 25 Nov 2025 09:59:45 +0100
Subject: [PATCH 5/7] Move checks for ELF arch and endian before debug object
creation
---
.../Orc/Debugging/ELFDebugObjectPlugin.cpp | 27 +++++++++++++------
1 file changed, 19 insertions(+), 8 deletions(-)
diff --git a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
index c6ce4e5e71686..efc876d52ec29 100644
--- a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
@@ -158,23 +158,19 @@ Error DebugObject::visitSections(GetLoadAddressFn Callback) {
return visitSectionLoadAddresses<ELF32LE>(std::move(Callback));
if (Endian == ELF::ELFDATA2MSB)
return visitSectionLoadAddresses<ELF32BE>(std::move(Callback));
- return createStringError(object_error::invalid_file_type,
- "Invalid endian in 32-bit ELF object file: %x",
- Endian);
+ break;
case ELF::ELFCLASS64:
if (Endian == ELF::ELFDATA2LSB)
return visitSectionLoadAddresses<ELF64LE>(std::move(Callback));
if (Endian == ELF::ELFDATA2MSB)
return visitSectionLoadAddresses<ELF64BE>(std::move(Callback));
- return createStringError(object_error::invalid_file_type,
- "Invalid endian in 64-bit ELF object file: %x",
- Endian);
+ break;
default:
- return createStringError(object_error::invalid_file_type,
- "Invalid arch in ELF object file: %x", Class);
+ break;
}
+ llvm_unreachable("Checked class and endian in notifyMaterializing()");
}
ELFDebugObjectPlugin::ELFDebugObjectPlugin(ExecutionSession &ES,
@@ -207,6 +203,21 @@ void ELFDebugObjectPlugin::notifyMaterializing(
if (G.getTargetTriple().getObjectFormat() != Triple::ELF)
return;
+ unsigned char Class, Endian;
+ std::tie(Class, Endian) = getElfArchType(InputObj.getBuffer());
+ if (Class != ELF::ELFCLASS64 && Class != ELF::ELFCLASS32)
+ return ES.reportError(
+ createStringError(object_error::invalid_file_type,
+ "Skipping debug object registration: Invalid arch "
+ "0x%02x in ELF LinkGraph %s",
+ Class, G.getName().c_str()));
+ if (Endian != ELF::ELFDATA2LSB && Endian != ELF::ELFDATA2MSB)
+ return ES.reportError(
+ createStringError(object_error::invalid_file_type,
+ "Skipping debug object registration: Invalid endian "
+ "0x%02x in ELF LinkGraph %s",
+ Endian, G.getName().c_str()));
+
// Step 1: We copy the raw input object into the working memory of a
// single-segment read-only allocation
size_t Size = InputObj.getBufferSize();
>From 2c6313210dcb11103b6ec071db40aa3d6ef38c04 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Tue, 25 Nov 2025 11:52:13 +0100
Subject: [PATCH 6/7] Skip materializations for pure link graphs
---
llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp | 2 ++
1 file changed, 2 insertions(+)
diff --git a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
index efc876d52ec29..f8483287653a5 100644
--- a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
@@ -200,6 +200,8 @@ static bool isDwarfSection(StringRef SectionName) {
void ELFDebugObjectPlugin::notifyMaterializing(
MaterializationResponsibility &MR, LinkGraph &G, JITLinkContext &Ctx,
MemoryBufferRef InputObj) {
+ if (InputObj.getBufferSize() == 0)
+ return;
if (G.getTargetTriple().getObjectFormat() != Triple::ELF)
return;
>From 73ad9e900f7de09d84a150b07fbb96326b9224fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Mon, 8 Dec 2025 13:57:36 +0100
Subject: [PATCH 7/7] Use asynchronous abandon and report error through
captured ExecutionSession pointer
---
.../ExecutionEngine/JITLink/JITLinkMemoryManager.h | 11 ++++-------
.../Orc/Debugging/ELFDebugObjectPlugin.cpp | 5 +++--
2 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h b/llvm/include/llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h
index d095b661e822a..10406fe4ec5f6 100644
--- a/llvm/include/llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h
+++ b/llvm/include/llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h
@@ -321,6 +321,8 @@ class SimpleSegmentAlloc {
using OnFinalizedFunction =
JITLinkMemoryManager::InFlightAlloc::OnFinalizedFunction;
+ using OnAbandonedFunction = unique_function<void(Error)>;
+
LLVM_ABI static void Create(JITLinkMemoryManager &MemMgr,
std::shared_ptr<orc::SymbolStringPool> SSP,
Triple TT, const JITLinkDylib *JD,
@@ -349,13 +351,8 @@ class SimpleSegmentAlloc {
}
/// Free allocated memory if finalize won't be called.
- Error abandon() {
- Error Err = Error::success();
- Alloc->abandon([&Err](Error E) {
- ErrorAsOutParameter _(&Err);
- Err = std::move(E);
- });
- return Err;
+ void abandon(OnAbandonedFunction OnAbandoned) {
+ Alloc->abandon(std::move(OnAbandoned));
}
private:
diff --git a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
index f8483287653a5..61ed805585f1d 100644
--- a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
@@ -61,8 +61,9 @@ class DebugObject {
ES.reportError(std::move(Err));
} else if (!FinalizeFuture.valid()) {
// WorkingMem was not finalized
- if (Error Err = WorkingMem.abandon())
- ES.reportError(std::move(Err));
+ WorkingMem.abandon([ES = &this->ES](Error Err) {
+ ES->reportError(std::move(Err));
+ });
}
}
More information about the llvm-commits
mailing list