[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