If there is no debug information, we wouldn't call `DebugObject::collectTargetAlloc` in the post-allocation phase. Therefore, when it's in the post-fixup phase, `DebugObject::awaitTargetMem` will fail with _"std::future_error: No associated state"_ because the std::future was not even populated.
425 lines
15 KiB
C++
425 lines
15 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() {
|
|
assert(!FinalizeFuture.valid());
|
|
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));
|
|
}
|
|
}
|
|
|
|
MutableArrayRef<char> getBuffer() {
|
|
auto SegInfo = WorkingMem.getSegInfo(MemProt::Read);
|
|
return SegInfo.WorkingMem;
|
|
}
|
|
|
|
SimpleSegmentAlloc collectTargetAlloc() {
|
|
FinalizeFuture = FinalizePromise.get_future();
|
|
return std::move(WorkingMem);
|
|
}
|
|
|
|
void trackFinalizedAlloc(FinalizedAlloc FA) { Alloc = std::move(FA); }
|
|
|
|
bool hasPendingTargetMem() const { return FinalizeFuture.valid(); }
|
|
|
|
Expected<ExecutorAddrRange> awaitTargetMem() {
|
|
assert(FinalizeFuture.valid() &&
|
|
"FinalizeFuture is not valid. Perhaps there is no pending target "
|
|
"memory transaction?");
|
|
return FinalizeFuture.get();
|
|
}
|
|
|
|
void reportTargetMem(ExecutorAddrRange TargetMem) {
|
|
FinalizePromise.set_value(TargetMem);
|
|
}
|
|
|
|
void failMaterialization(Error Err) {
|
|
FinalizePromise.set_value(std::move(Err));
|
|
}
|
|
|
|
void releasePendingResources() {
|
|
if (FinalizeFuture.valid()) {
|
|
// Error before step 4: Finalization error was not reported
|
|
Expected<ExecutorAddrRange> TargetMem = FinalizeFuture.get();
|
|
if (!TargetMem)
|
|
ES.reportError(TargetMem.takeError());
|
|
} else {
|
|
// Error before step 3: WorkingMem was not collected
|
|
WorkingMem.abandon(
|
|
[ES = &this->ES](Error Err) { ES->reportError(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->collectTargetAlloc();
|
|
|
|
// 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) {
|
|
// Bail out if materialization failed in the meantime
|
|
std::lock_guard<std::mutex> Lock(PendingObjsLock);
|
|
auto It = PendingObjs.find(&MR);
|
|
if (It == PendingObjs.end()) {
|
|
if (!FA)
|
|
ES.reportError(FA.takeError());
|
|
return;
|
|
}
|
|
|
|
DebugObject *DebugObj = It->second.get();
|
|
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);
|
|
assert(DebugObj && "Don't inject passes if we have no debug object");
|
|
// Post-allocation phases would bail out if there is no debug section,
|
|
// in which case we wouldn't collect target memory and therefore shouldn't
|
|
// wait for the transaction to finish.
|
|
if (!DebugObj->hasPendingTargetMem())
|
|
return Error::success();
|
|
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);
|
|
auto It = PendingObjs.find(&MR);
|
|
It->second->releasePendingResources();
|
|
PendingObjs.erase(It);
|
|
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
|