[llvm] [ORC] Free ELF debug objects in dealloc action of corresponding code (wip) (PR #168522)
Stefan Gränitz via llvm-commits
llvm-commits at lists.llvm.org
Tue Nov 25 03:51:17 PST 2025
================
@@ -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.
----------------
weliveindetail wrote:
Yes, that's an interesting idea. It is the status quo that the debug object lives as long as the corresponding JIT'd code. It was a conservative decision and I hadn't thought about changing it. If we put it into the graph, we cannot change it anymore.
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.
I think it's best to wait a bit with this patch. When I find the time, I will check the behavior in GDB and LLDB. There might be room for a new flag to free the debug object memory right after registration. This is connected to the open question of cleaning up the list of debug objects, which I wanted to figure out anyway.
Thanks for you feedback!
https://github.com/llvm/llvm-project/pull/168522
More information about the llvm-commits
mailing list