llvm-project/llvm/lib/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.cpp
Nikita Popov b963def2cb
[ELFDebugObjectPlugin] Gracefully handle missing section (#172622)
On ppc64le some sections like .toc get merged into other sections by
JITLink. As such, some sections in the object file may not be present in
the link graph. Skip those sections.
2025-12-17 15:04:59 +01:00

391 lines
14 KiB
C++

//===--------- ELFDebugObjectPlugin.cpp - JITLink debug objects -----------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// FIXME: Update Plugin to poke the debug object into a new JITLink section,
// rather than creating a new allocation.
//
//===----------------------------------------------------------------------===//
#include "llvm/ExecutionEngine/Orc/Debugging/ELFDebugObjectPlugin.h"
#include "llvm/ADT/ArrayRef.h"
#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"
#include "llvm/Support/raw_ostream.h"
#include <set>
#define DEBUG_TYPE "orc"
using namespace llvm::jitlink;
using namespace llvm::object;
namespace llvm {
namespace orc {
// Helper class to emit and fixup an individual debug object
class DebugObject {
public:
using FinalizedAlloc = JITLinkMemoryManager::FinalizedAlloc;
DebugObject(StringRef Name, SimpleSegmentAlloc Alloc, JITLinkContext &Ctx,
ExecutionSession &ES)
: Name(Name), WorkingMem(std::move(Alloc)),
MemMgr(Ctx.getMemoryManager()), ES(ES) {}
~DebugObject() {
if (Alloc) {
std::vector<FinalizedAlloc> Allocs;
Allocs.push_back(std::move(Alloc));
if (Error Err = MemMgr.deallocate(std::move(Allocs)))
ES.reportError(std::move(Err));
} else if (!FinalizeFuture.valid()) {
// WorkingMem was not finalized
WorkingMem.abandon(
[ES = &this->ES](Error Err) { ES->reportError(std::move(Err)); });
}
}
MutableArrayRef<char> getBuffer() {
auto SegInfo = WorkingMem.getSegInfo(MemProt::Read);
return SegInfo.WorkingMem;
}
SimpleSegmentAlloc takeTargetAlloc() {
FinalizeFuture = FinalizePromise.get_future();
return std::move(WorkingMem);
}
void trackFinalizedAlloc(FinalizedAlloc FA) { Alloc = std::move(FA); }
Expected<ExecutorAddrRange> awaitTargetMem() { return FinalizeFuture.get(); }
void reportTargetMem(ExecutorAddrRange TargetMem) {
FinalizePromise.set_value(TargetMem);
}
void failMaterialization(Error Err) {
FinalizePromise.set_value(std::move(Err));
}
using GetLoadAddressFn = llvm::unique_function<ExecutorAddr(StringRef)>;
Error visitSections(GetLoadAddressFn Callback);
template <typename ELFT>
Error visitSectionLoadAddresses(GetLoadAddressFn Callback);
private:
std::string Name;
SimpleSegmentAlloc WorkingMem;
JITLinkMemoryManager &MemMgr;
ExecutionSession &ES;
std::promise<MSVCPExpected<ExecutorAddrRange>> FinalizePromise;
std::future<MSVCPExpected<ExecutorAddrRange>> FinalizeFuture;
FinalizedAlloc Alloc;
};
template <typename ELFT>
Error DebugObject::visitSectionLoadAddresses(GetLoadAddressFn Callback) {
using SectionHeader = typename ELFT::Shdr;
MutableArrayRef<char> Buffer = getBuffer();
StringRef BufferRef(Buffer.data(), Buffer.size());
Expected<ELFFile<ELFT>> ObjRef = ELFFile<ELFT>::create(BufferRef);
if (!ObjRef)
return ObjRef.takeError();
Expected<ArrayRef<SectionHeader>> Sections = ObjRef->sections();
if (!Sections)
return Sections.takeError();
for (const SectionHeader &Header : *Sections) {
Expected<StringRef> Name = ObjRef->getSectionName(Header);
if (!Name)
return Name.takeError();
if (Name->empty())
continue;
ExecutorAddr LoadAddress = Callback(*Name);
if (LoadAddress)
const_cast<SectionHeader &>(Header).sh_addr =
static_cast<typename ELFT::uint>(LoadAddress.getValue());
}
LLVM_DEBUG({
dbgs() << "Section load-addresses in debug object for \"" << Name
<< "\":\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);
}
}
});
return Error::success();
}
Error DebugObject::visitSections(GetLoadAddressFn Callback) {
unsigned char Class, Endian;
MutableArrayRef<char> Buf = getBuffer();
std::tie(Class, Endian) = getElfArchType(StringRef(Buf.data(), Buf.size()));
switch (Class) {
case ELF::ELFCLASS32:
if (Endian == ELF::ELFDATA2LSB)
return visitSectionLoadAddresses<ELF32LE>(std::move(Callback));
if (Endian == ELF::ELFDATA2MSB)
return visitSectionLoadAddresses<ELF32BE>(std::move(Callback));
break;
case ELF::ELFCLASS64:
if (Endian == ELF::ELFDATA2LSB)
return visitSectionLoadAddresses<ELF64LE>(std::move(Callback));
if (Endian == ELF::ELFDATA2MSB)
return visitSectionLoadAddresses<ELF64BE>(std::move(Callback));
break;
default:
break;
}
llvm_unreachable("Checked class and endian in notifyMaterializing()");
}
ELFDebugObjectPlugin::ELFDebugObjectPlugin(ExecutionSession &ES,
bool RequireDebugSections,
bool AutoRegisterCode, Error &Err)
: ES(ES), RequireDebugSections(RequireDebugSections),
AutoRegisterCode(AutoRegisterCode) {
// Pass bootstrap symbol for registration function to enable debugging
ErrorAsOutParameter _(&Err);
Err = ES.getExecutorProcessControl().getBootstrapSymbols(
{{RegistrationAction, rt::RegisterJITLoaderGDBAllocActionName}});
}
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 InputObj) {
if (InputObj.getBufferSize() == 0)
return;
if (G.getTargetTriple().getObjectFormat() != Triple::ELF)
return;
unsigned char Class, Endian;
std::tie(Class, Endian) = getElfArchType(InputObj.getBuffer());
if (Class != ELF::ELFCLASS64 && Class != ELF::ELFCLASS32)
return ES.reportError(
createStringError(object_error::invalid_file_type,
"Skipping debug object registration: Invalid arch "
"0x%02x in ELF LinkGraph %s",
Class, G.getName().c_str()));
if (Endian != ELF::ELFDATA2LSB && Endian != ELF::ELFDATA2MSB)
return ES.reportError(
createStringError(object_error::invalid_file_type,
"Skipping debug object registration: Invalid endian "
"0x%02x in ELF LinkGraph %s",
Endian, G.getName().c_str()));
// 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]->getBuffer();
memcpy(Buffer.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.get();
}
void ELFDebugObjectPlugin::modifyPassConfig(MaterializationResponsibility &MR,
LinkGraph &G,
PassConfiguration &PassConfig) {
if (!getPendingDebugObj(MR))
return;
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
Error Err = DebugObj->visitSections(
[&G, &SectionsPatched, &HasDebugSections](StringRef Name) {
Section *S = G.findSectionByName(Name);
if (!S) {
// The section may have been merged into a different one during
// linking, ignore it.
return ExecutorAddr();
}
SectionsPatched += 1;
if (isDwarfSection(Name))
HasDebugSections = true;
return SectionRange(*S).getStart();
});
if (Err)
return Err;
if (!SectionsPatched) {
LLVM_DEBUG(dbgs() << "Skipping debug registration for LinkGraph '"
<< G.getName() << "': no debug info\n");
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
SimpleSegmentAlloc Alloc = DebugObj->takeTargetAlloc();
// 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) {
std::lock_guard<std::mutex> Lock(PendingObjsLock);
PendingObjs.erase(&MR);
return Error::success();
}
void ELFDebugObjectPlugin::notifyTransferringResources(JITDylib &JD,
ResourceKey DstKey,
ResourceKey SrcKey) {
// Debug objects are stored by ResourceKey only after registration.
// Thus, pending objects don't need to be updated here.
std::lock_guard<std::mutex> Lock(RegisteredObjsLock);
auto SrcIt = RegisteredObjs.find(SrcKey);
if (SrcIt != RegisteredObjs.end()) {
// Resources from distinct MaterializationResponsibilitys can get merged
// after emission, so we can have multiple debug objects per resource key.
for (std::unique_ptr<DebugObject> &DebugObj : SrcIt->second)
RegisteredObjs[DstKey].push_back(std::move(DebugObj));
RegisteredObjs.erase(SrcIt);
}
}
Error ELFDebugObjectPlugin::notifyRemovingResources(JITDylib &JD,
ResourceKey Key) {
// Removing the resource for a pending object fails materialization, so they
// get cleaned up in the notifyFailed() handler.
std::lock_guard<std::mutex> Lock(RegisteredObjsLock);
RegisteredObjs.erase(Key);
// TODO: Implement unregister notifications.
return Error::success();
}
} // namespace orc
} // namespace llvm