[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
Fri Nov 21 02:57:54 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:
There's a substantial further simplification that could be made here: If we're ok with the debug object living for as long as the corresponding JIT'd code then the debug object could be embedded in a special section in the graph, rather than having its own allocation. That would allow you to avoid managing the memory lifetime entirely, and you could just focus on populating the debug object bits and calling the registration and deregistration functions.
https://github.com/llvm/llvm-project/pull/168522
More information about the llvm-commits
mailing list