[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