[llvm] [ORC] Free ELF debug objects in dealloc action of corresponding code (wip) (PR #168522)
Lang Hames via llvm-commits
llvm-commits at lists.llvm.org
Sun Dec 7 16:58:41 PST 2025
Stefan =?utf-8?q?Gränitz?= <stefan.graenitz at gmail.com>,
Stefan =?utf-8?q?Gränitz?= <stefan.graenitz at gmail.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/168522 at github.com>
================
@@ -413,129 +177,165 @@ ELFDebugObjectPlugin::ELFDebugObjectPlugin(ExecutionSession &ES,
AutoRegisterCode(AutoRegisterCode) {
// Pass bootstrap symbol for registration function to enable debugging
ErrorAsOutParameter _(&Err);
- Err = ES.getExecutorProcessControl().getBootstrapSymbols(
- {{RegistrationAction, rt::RegisterJITLoaderGDBAllocActionName}});
+ Err = ES.getExecutorProcessControl().getBootstrapSymbols({
+ {RegistrationAction, rt::RegisterJITLoaderGDBAllocActionName},
+ {DeallocAction, rt::SimpleExecutorMemoryManagerReleaseWrapperName},
+ {TargetMemMgr, rt::SimpleExecutorMemoryManagerInstanceName},
+ });
}
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);
+ auto [It, Inserted] = PendingObjs.try_emplace(
+ &MR, InputObj.getBufferIdentifier(), std::move(*Alloc), Ctx, ES);
+ assert(Inserted && "One debug object per materialization");
+ memcpy(It->second.getMutBuffer().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;
}
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: The lookup in the segment info here is a workaround. The below
+ // FA->release() is supposed to provide the base address in target memory,
+ // but InProcessMemoryManager returns the address of a FinalizedAllocInfo
+ // helper instead.
----------------
lhames wrote:
> Maybe it's time to investigate the life-time requirements of the debug object. I would be surprised if debuggers don't copy these objects into their own address space. Eventually, they need mutable access to resolve relocations. The question is when this is happening, i.e. guaranteed to be done -- especially in LLDB as it attempts to do everything lazily.
There's GDB JIT dealloc option -- if there's a corresponding dealloc action attached to the graph then the debuggers shouldn't use info from it after the graph object itself is deallocated. (I suspect that the debuggers honour this already, but if they don't we can fix LLDB easily enough, and hopefully GDB will follow suit).
https://github.com/llvm/llvm-project/pull/168522
More information about the llvm-commits
mailing list