[llvm] [ORC] Tailor ELF debugger support plugin to load-address patching only (PR #168518)

Stefan Gränitz via llvm-commits llvm-commits at lists.llvm.org
Tue Nov 18 03:48:06 PST 2025


https://github.com/weliveindetail updated https://github.com/llvm/llvm-project/pull/168518

>From 5d5a338b5a98bf17dcf5ac0d47ed26f5a18a2cd1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Tue, 18 Nov 2025 10:34:31 +0100
Subject: [PATCH 1/2] [ORC] Tailor ELF debugger support plugin to load-address
 patching only

---
 .../Orc/Debugging/ELFDebugObjectPlugin.h      |  34 +-
 .../Orc/Debugging/ELFDebugObjectPlugin.cpp    | 600 +++++++-----------
 2 files changed, 228 insertions(+), 406 deletions(-)

diff --git a/llvm/include/llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h b/llvm/include/llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h
index d946a029fd2ec..92dbfe1c79e6e 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h
@@ -23,7 +23,6 @@
 #include "llvm/Support/MemoryBufferRef.h"
 #include "llvm/TargetParser/Triple.h"
 
-#include <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 9f556b0d07a8b..6a8e11a7bbd49 100644
--- a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
@@ -17,11 +17,17 @@
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/BinaryFormat/ELF.h"
+#include "llvm/ExecutionEngine/JITLink/JITLink.h"
 #include "llvm/ExecutionEngine/JITLink/JITLinkDylib.h"
 #include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h"
+#include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h"
+#include "llvm/ExecutionEngine/Orc/Shared/MemoryFlags.h"
 #include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h"
+#include "llvm/IR/Instructions.h"
 #include "llvm/Object/ELFObjectFile.h"
+#include "llvm/Object/Error.h"
 #include "llvm/Support/Errc.h"
+#include "llvm/Support/Error.h"
 #include "llvm/Support/MSVCErrorWorkarounds.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Process.h"
@@ -37,111 +43,19 @@ using namespace llvm::object;
 namespace llvm {
 namespace orc {
 
-class DebugObjectSection {
-public:
-  virtual void setTargetMemoryRange(SectionRange Range) = 0;
-  virtual void dump(raw_ostream &OS, StringRef Name) {}
-  virtual ~DebugObjectSection() = default;
-};
-
-template <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);
 }
 
-void ELFDebugObjectPlugin::modifyPassConfig(MaterializationResponsibility &MR,
-                                            LinkGraph &G,
-                                            PassConfiguration &PassConfig) {
-  // Not all link artifacts have associated debug objects.
+DebugObject *
+ELFDebugObjectPlugin::getPendingDebugObj(MaterializationResponsibility &MR) {
   std::lock_guard<std::mutex> Lock(PendingObjsLock);
   auto It = PendingObjs.find(&MR);
-  if (It == PendingObjs.end())
+  return It == PendingObjs.end() ? nullptr : It->second.get();
+}
+
+void ELFDebugObjectPlugin::modifyPassConfig(
+    MaterializationResponsibility &MR, LinkGraph &G,
+    PassConfiguration &PassConfig) {
+  if (!getPendingDebugObj(MR))
     return;
 
-  DebugObject &DebugObj = *It->second;
-  if (DebugObj.hasFlags(ReportFinalSectionLoadAddresses)) {
-    PassConfig.PostAllocationPasses.push_back(
-        [&DebugObj](LinkGraph &Graph) -> Error {
-          for (const Section &GraphSection : Graph.sections())
-            DebugObj.reportSectionTargetMemoryRange(GraphSection.getName(),
-                                                    SectionRange(GraphSection));
-          return Error::success();
+  PassConfig.PostAllocationPasses.push_back([this, &MR](LinkGraph &G) -> Error {
+    size_t SectionsPatched = 0;
+    bool HasDebugSections = false;
+    DebugObject *DebugObj = getPendingDebugObj(MR);
+    assert(DebugObj && "Don't inject passes if we have no debug object");
+
+    // Step 2: Once the target memory layout is ready, we write the
+    // addresses of the LinkGraph sections into the load-address fields of the
+    // section headers in our debug object allocation
+    DebugObj->visitSections(
+        [&G, &SectionsPatched, &HasDebugSections](StringRef Name) {
+          SectionsPatched += 1;
+          if (isDwarfSection(Name))
+            HasDebugSections = true;
+          Section *S = G.findSectionByName(Name);
+          assert(S && "No graph section for object section");
+          return SectionRange(*S).getStart();
         });
 
-    PassConfig.PreFixupPasses.push_back(
-        [this, &DebugObj, &MR](LinkGraph &G) -> Error {
-          DebugObj.finalizeAsync([this, &DebugObj,
-                                  &MR](Expected<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 aceef1987c93ff01a791a26a0ca332af166d81a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Tue, 18 Nov 2025 12:47:03 +0100
Subject: [PATCH 2/2] Address clang-format detail

---
 .../ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp  | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
index 6a8e11a7bbd49..966482fef32a5 100644
--- a/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
@@ -243,9 +243,9 @@ ELFDebugObjectPlugin::getPendingDebugObj(MaterializationResponsibility &MR) {
   return It == PendingObjs.end() ? nullptr : It->second.get();
 }
 
-void ELFDebugObjectPlugin::modifyPassConfig(
-    MaterializationResponsibility &MR, LinkGraph &G,
-    PassConfiguration &PassConfig) {
+void ELFDebugObjectPlugin::modifyPassConfig(MaterializationResponsibility &MR,
+                                            LinkGraph &G,
+                                            PassConfiguration &PassConfig) {
   if (!getPendingDebugObj(MR))
     return;
 



More information about the llvm-commits mailing list