1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-11-23 03:02:36 +01:00
llvm-mirror/lib/ExecutionEngine/Orc/DebugObjectManagerPlugin.cpp
2021-03-09 14:01:50 +01:00

518 lines
18 KiB
C++

//===---- DebugObjectManagerPlugin.h - JITLink debug objects ---*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "llvm/ExecutionEngine/Orc/DebugObjectManagerPlugin.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/JITLinkDylib.h"
#include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h"
#include "llvm/ExecutionEngine/JITSymbol.h"
#include "llvm/Object/ELFObjectFile.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/Support/Errc.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 {
class DebugObjectSection {
public:
virtual void setTargetMemoryRange(SectionRange Range) = 0;
virtual void dump(raw_ostream &OS, StringRef Name) {}
virtual ~DebugObjectSection() {}
};
template <typename ELFT>
class ELFDebugObjectSection : public DebugObjectSection {
public:
// BinaryFormat ELF is not meant as a mutable format. We can only make changes
// that don't invalidate the file structure.
ELFDebugObjectSection(const typename ELFT::Shdr *Header)
: Header(const_cast<typename ELFT::Shdr *>(Header)) {}
void setTargetMemoryRange(SectionRange Range) override;
void dump(raw_ostream &OS, StringRef Name) override;
Error validateInBounds(StringRef Buffer, const char *Name) const;
private:
typename ELFT::Shdr *Header;
bool isTextOrDataSection() const;
};
template <typename ELFT>
void ELFDebugObjectSection<ELFT>::setTargetMemoryRange(SectionRange Range) {
// Only patch load-addresses for executable and data sections.
if (isTextOrDataSection()) {
Header->sh_addr = static_cast<typename ELFT::uint>(Range.getStart());
}
}
template <typename ELFT>
bool ELFDebugObjectSection<ELFT>::isTextOrDataSection() const {
switch (Header->sh_type) {
case ELF::SHT_PROGBITS:
case ELF::SHT_X86_64_UNWIND:
return Header->sh_flags & (ELF::SHF_EXECINSTR | ELF::SHF_ALLOC);
}
return false;
}
template <typename ELFT>
Error ELFDebugObjectSection<ELFT>::validateInBounds(StringRef Buffer,
const char *Name) const {
const uint8_t *Start = Buffer.bytes_begin();
const uint8_t *End = Buffer.bytes_end();
const uint8_t *HeaderPtr = reinterpret_cast<uint8_t *>(Header);
if (HeaderPtr < Start || HeaderPtr + sizeof(typename ELFT::Shdr) > End)
return make_error<StringError>(
formatv("{0} section header at {1:x16} not within bounds of the "
"given debug object buffer [{2:x16} - {3:x16}]",
Name, &Header->sh_addr, Start, End),
inconvertibleErrorCode());
if (Header->sh_offset + Header->sh_size > Buffer.size())
return make_error<StringError>(
formatv("{0} section data [{1:x16} - {2:x16}] not within bounds of "
"the given debug object buffer [{3:x16} - {4:x16}]",
Name, Start + Header->sh_offset,
Start + Header->sh_offset + Header->sh_size, Start, End),
inconvertibleErrorCode());
return Error::success();
}
template <typename ELFT>
void ELFDebugObjectSection<ELFT>::dump(raw_ostream &OS, StringRef Name) {
if (auto Addr = static_cast<JITTargetAddress>(Header->sh_addr)) {
OS << formatv(" {0:x16} {1}\n", Addr, Name);
} else {
OS << formatv(" {0}\n", Name);
}
}
static constexpr sys::Memory::ProtectionFlags ReadOnly =
static_cast<sys::Memory::ProtectionFlags>(sys::Memory::MF_READ);
enum class Requirement {
// Request final target memory load-addresses for all sections.
ReportFinalSectionLoadAddresses,
};
/// The plugin creates a debug object from JITLinkContext when JITLink starts
/// processing the corresponding LinkGraph. It provides access to the pass
/// configuration of the LinkGraph and calls the finalization function, once
/// the resulting link artifact was emitted.
///
class DebugObject {
public:
DebugObject(JITLinkContext &Ctx) : Ctx(Ctx) {}
void set(Requirement Req) { Reqs.insert(Req); }
bool has(Requirement Req) const { return Reqs.count(Req) > 0; }
using FinalizeContinuation = std::function<void(Expected<sys::MemoryBlock>)>;
void finalizeAsync(FinalizeContinuation OnFinalize);
virtual void reportSectionTargetMemoryRange(StringRef Name,
SectionRange TargetMem) {}
virtual ~DebugObject() {}
protected:
using Allocation = JITLinkMemoryManager::Allocation;
virtual Expected<std::unique_ptr<Allocation>>
finalizeWorkingMemory(JITLinkContext &Ctx) = 0;
private:
JITLinkContext &Ctx;
std::set<Requirement> Reqs;
std::unique_ptr<Allocation> Alloc{nullptr};
};
// Finalize working memory and take ownership of the resulting allocation. Start
// copying memory over to the target and pass on the result once we're done.
// Ownership of the allocation remains with us for the rest of our lifetime.
void DebugObject::finalizeAsync(FinalizeContinuation OnFinalize) {
assert(Alloc == nullptr && "Cannot finalize more than once");
auto AllocOrErr = finalizeWorkingMemory(Ctx);
if (!AllocOrErr)
OnFinalize(AllocOrErr.takeError());
Alloc = std::move(*AllocOrErr);
Alloc->finalizeAsync([this, OnFinalize](Error Err) {
if (Err)
OnFinalize(std::move(Err));
else
OnFinalize(sys::MemoryBlock(
jitTargetAddressToPointer<void *>(Alloc->getTargetMemory(ReadOnly)),
Alloc->getWorkingMemory(ReadOnly).size()));
});
}
/// The current implementation of ELFDebugObject replicates the approach used in
/// RuntimeDyld: It patches executable and data section headers in the given
/// object buffer with load-addresses of their corresponding sections in target
/// memory.
///
class ELFDebugObject : public DebugObject {
public:
static Expected<std::unique_ptr<DebugObject>> Create(MemoryBufferRef Buffer,
JITLinkContext &Ctx);
void reportSectionTargetMemoryRange(StringRef Name,
SectionRange TargetMem) override;
StringRef getBuffer() const { return Buffer->getMemBufferRef().getBuffer(); }
protected:
Expected<std::unique_ptr<Allocation>>
finalizeWorkingMemory(JITLinkContext &Ctx) override;
template <typename ELFT>
Error recordSection(StringRef Name,
std::unique_ptr<ELFDebugObjectSection<ELFT>> Section);
DebugObjectSection *getSection(StringRef Name);
private:
template <typename ELFT>
static Expected<std::unique_ptr<ELFDebugObject>>
CreateArchType(MemoryBufferRef Buffer, JITLinkContext &Ctx);
static std::unique_ptr<WritableMemoryBuffer>
CopyBuffer(MemoryBufferRef Buffer, Error &Err);
ELFDebugObject(std::unique_ptr<WritableMemoryBuffer> Buffer,
JITLinkContext &Ctx)
: DebugObject(Ctx), Buffer(std::move(Buffer)) {
set(Requirement::ReportFinalSectionLoadAddresses);
}
std::unique_ptr<WritableMemoryBuffer> Buffer;
StringMap<std::unique_ptr<DebugObjectSection>> Sections;
};
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;
}
std::unique_ptr<WritableMemoryBuffer>
ELFDebugObject::CopyBuffer(MemoryBufferRef Buffer, Error &Err) {
ErrorAsOutParameter _(&Err);
size_t Size = Buffer.getBufferSize();
StringRef Name = Buffer.getBufferIdentifier();
if (auto Copy = WritableMemoryBuffer::getNewUninitMemBuffer(Size, Name)) {
memcpy(Copy->getBufferStart(), Buffer.getBufferStart(), Size);
return Copy;
}
Err = errorCodeToError(make_error_code(errc::not_enough_memory));
return nullptr;
}
template <typename ELFT>
Expected<std::unique_ptr<ELFDebugObject>>
ELFDebugObject::CreateArchType(MemoryBufferRef Buffer, JITLinkContext &Ctx) {
using SectionHeader = typename ELFT::Shdr;
Error Err = Error::success();
std::unique_ptr<ELFDebugObject> DebugObj(
new ELFDebugObject(CopyBuffer(Buffer, Err), Ctx));
if (Err)
return std::move(Err);
Expected<ELFFile<ELFT>> ObjRef = ELFFile<ELFT>::create(DebugObj->getBuffer());
if (!ObjRef)
return ObjRef.takeError();
// TODO: Add support for other architectures.
uint16_t TargetMachineArch = ObjRef->getHeader().e_machine;
if (TargetMachineArch != ELF::EM_X86_64)
return nullptr;
Expected<ArrayRef<SectionHeader>> Sections = ObjRef->sections();
if (!Sections)
return Sections.takeError();
bool HasDwarfSection = false;
for (const SectionHeader &Header : *Sections) {
Expected<StringRef> Name = ObjRef->getSectionName(Header);
if (!Name)
return Name.takeError();
if (Name->empty())
continue;
HasDwarfSection |= isDwarfSection(*Name);
auto Wrapped = std::make_unique<ELFDebugObjectSection<ELFT>>(&Header);
if (Error Err = DebugObj->recordSection(*Name, std::move(Wrapped)))
return std::move(Err);
}
if (!HasDwarfSection) {
LLVM_DEBUG(dbgs() << "Aborting debug registration for LinkGraph \""
<< DebugObj->Buffer->getBufferIdentifier()
<< "\": input object contains no debug info\n");
return nullptr;
}
return std::move(DebugObj);
}
Expected<std::unique_ptr<DebugObject>>
ELFDebugObject::Create(MemoryBufferRef Buffer, JITLinkContext &Ctx) {
unsigned char Class, Endian;
std::tie(Class, Endian) = getElfArchType(Buffer.getBuffer());
if (Class == ELF::ELFCLASS32) {
if (Endian == ELF::ELFDATA2LSB)
return CreateArchType<ELF32LE>(Buffer, Ctx);
if (Endian == ELF::ELFDATA2MSB)
return CreateArchType<ELF32BE>(Buffer, Ctx);
return nullptr;
}
if (Class == ELF::ELFCLASS64) {
if (Endian == ELF::ELFDATA2LSB)
return CreateArchType<ELF64LE>(Buffer, Ctx);
if (Endian == ELF::ELFDATA2MSB)
return CreateArchType<ELF64BE>(Buffer, Ctx);
return nullptr;
}
return nullptr;
}
Expected<std::unique_ptr<DebugObject::Allocation>>
ELFDebugObject::finalizeWorkingMemory(JITLinkContext &Ctx) {
LLVM_DEBUG({
dbgs() << "Section load-addresses in debug object for \""
<< Buffer->getBufferIdentifier() << "\":\n";
for (const auto &KV : Sections)
KV.second->dump(dbgs(), KV.first());
});
// TODO: This works, but what actual alignment requirements do we have?
unsigned Alignment = sys::Process::getPageSizeEstimate();
JITLinkMemoryManager &MemMgr = Ctx.getMemoryManager();
const JITLinkDylib *JD = Ctx.getJITLinkDylib();
size_t Size = Buffer->getBufferSize();
// Allocate working memory for debug object in read-only segment.
JITLinkMemoryManager::SegmentsRequestMap SingleReadOnlySegment;
SingleReadOnlySegment[ReadOnly] =
JITLinkMemoryManager::SegmentRequest(Alignment, Size, 0);
auto AllocOrErr = MemMgr.allocate(JD, SingleReadOnlySegment);
if (!AllocOrErr)
return AllocOrErr.takeError();
// Initialize working memory with a copy of our object buffer.
// TODO: Use our buffer as working memory directly.
std::unique_ptr<Allocation> Alloc = std::move(*AllocOrErr);
MutableArrayRef<char> WorkingMem = Alloc->getWorkingMemory(ReadOnly);
memcpy(WorkingMem.data(), Buffer->getBufferStart(), Size);
Buffer.reset();
return std::move(Alloc);
}
void ELFDebugObject::reportSectionTargetMemoryRange(StringRef Name,
SectionRange TargetMem) {
if (auto *DebugObjSection = getSection(Name))
DebugObjSection->setTargetMemoryRange(TargetMem);
}
template <typename ELFT>
Error ELFDebugObject::recordSection(
StringRef Name, std::unique_ptr<ELFDebugObjectSection<ELFT>> Section) {
if (Error Err = Section->validateInBounds(this->getBuffer(), Name.data()))
return Err;
auto ItInserted = Sections.try_emplace(Name, std::move(Section));
if (!ItInserted.second)
return make_error<StringError>("Duplicate section",
inconvertibleErrorCode());
return Error::success();
}
DebugObjectSection *ELFDebugObject::getSection(StringRef Name) {
auto It = Sections.find(Name);
return It == Sections.end() ? nullptr : It->second.get();
}
static ResourceKey getResourceKey(MaterializationResponsibility &MR) {
ResourceKey Key;
if (auto Err = MR.withResourceKeyDo([&](ResourceKey K) { Key = K; })) {
MR.getExecutionSession().reportError(std::move(Err));
return ResourceKey{};
}
assert(Key && "Invalid key");
return Key;
}
/// Creates a debug object based on the input object file from
/// ObjectLinkingLayerJITLinkContext.
///
static Expected<std::unique_ptr<DebugObject>>
createDebugObjectFromBuffer(LinkGraph &G, JITLinkContext &Ctx,
MemoryBufferRef ObjBuffer) {
switch (G.getTargetTriple().getObjectFormat()) {
case Triple::ELF:
return ELFDebugObject::Create(ObjBuffer, Ctx);
default:
// TODO: Once we add support for other formats, we might want to split this
// into multiple files.
return nullptr;
}
}
DebugObjectManagerPlugin::DebugObjectManagerPlugin(
ExecutionSession &ES, std::unique_ptr<DebugObjectRegistrar> Target)
: ES(ES), Target(std::move(Target)) {}
DebugObjectManagerPlugin::~DebugObjectManagerPlugin() {}
void DebugObjectManagerPlugin::notifyMaterializing(
MaterializationResponsibility &MR, LinkGraph &G, JITLinkContext &Ctx,
MemoryBufferRef ObjBuffer) {
assert(PendingObjs.count(getResourceKey(MR)) == 0 &&
"Cannot have more than one pending debug object per "
"MaterializationResponsibility");
std::lock_guard<std::mutex> Lock(PendingObjsLock);
if (auto DebugObj = createDebugObjectFromBuffer(G, Ctx, ObjBuffer)) {
// Not all link artifacts allow debugging.
if (*DebugObj != nullptr) {
ResourceKey Key = getResourceKey(MR);
PendingObjs[Key] = std::move(*DebugObj);
}
} else {
ES.reportError(DebugObj.takeError());
}
}
void DebugObjectManagerPlugin::modifyPassConfig(
MaterializationResponsibility &MR, const Triple &TT,
PassConfiguration &PassConfig) {
// Not all link artifacts have associated debug objects.
std::lock_guard<std::mutex> Lock(PendingObjsLock);
auto It = PendingObjs.find(getResourceKey(MR));
if (It == PendingObjs.end())
return;
DebugObject &DebugObj = *It->second;
if (DebugObj.has(Requirement::ReportFinalSectionLoadAddresses)) {
PassConfig.PostAllocationPasses.push_back(
[&DebugObj](LinkGraph &Graph) -> Error {
for (const Section &GraphSection : Graph.sections())
DebugObj.reportSectionTargetMemoryRange(GraphSection.getName(),
SectionRange(GraphSection));
return Error::success();
});
}
}
Error DebugObjectManagerPlugin::notifyEmitted(
MaterializationResponsibility &MR) {
ResourceKey Key = getResourceKey(MR);
std::lock_guard<std::mutex> Lock(PendingObjsLock);
auto It = PendingObjs.find(Key);
if (It == PendingObjs.end())
return Error::success();
DebugObject *UnownedDebugObj = It->second.release();
PendingObjs.erase(It);
// FIXME: We released ownership of the DebugObject, so we can easily capture
// the raw pointer in the continuation function, which re-owns it immediately.
if (UnownedDebugObj)
UnownedDebugObj->finalizeAsync(
[this, Key, UnownedDebugObj](Expected<sys::MemoryBlock> TargetMem) {
std::unique_ptr<DebugObject> ReownedDebugObj(UnownedDebugObj);
if (!TargetMem) {
ES.reportError(TargetMem.takeError());
return;
}
if (Error Err = Target->registerDebugObject(*TargetMem)) {
ES.reportError(std::move(Err));
return;
}
std::lock_guard<std::mutex> Lock(RegisteredObjsLock);
RegisteredObjs[Key].push_back(std::move(ReownedDebugObj));
});
return Error::success();
}
Error DebugObjectManagerPlugin::notifyFailed(
MaterializationResponsibility &MR) {
std::lock_guard<std::mutex> Lock(PendingObjsLock);
PendingObjs.erase(getResourceKey(MR));
return Error::success();
}
void DebugObjectManagerPlugin::notifyTransferringResources(ResourceKey DstKey,
ResourceKey SrcKey) {
{
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);
}
}
{
std::lock_guard<std::mutex> Lock(PendingObjsLock);
auto SrcIt = PendingObjs.find(SrcKey);
if (SrcIt != PendingObjs.end()) {
assert(PendingObjs.count(DstKey) == 0 &&
"Cannot have more than one pending debug object per "
"MaterializationResponsibility");
PendingObjs[DstKey] = std::move(SrcIt->second);
PendingObjs.erase(SrcIt);
}
}
}
Error DebugObjectManagerPlugin::notifyRemovingResources(ResourceKey K) {
{
std::lock_guard<std::mutex> Lock(RegisteredObjsLock);
RegisteredObjs.erase(K);
// TODO: Implement unregister notifications.
}
std::lock_guard<std::mutex> Lock(PendingObjsLock);
PendingObjs.erase(K);
return Error::success();
}
} // namespace orc
} // namespace llvm