Use SymbolStringPtr for Symbol names in LinkGraph. This reduces string interning on the boundary between JITLink and ORC, and allows pointer comparisons (rather than string comparisons) between Symbol names. This should improve the performance and readability of code that bridges between JITLink and ORC (e.g. ObjectLinkingLayer and ObjectLinkingLayer::Plugins). To enable use of SymbolStringPtr a std::shared_ptr<SymbolStringPool> is added to LinkGraph and threaded through to its construction sites in LLVM and Bolt. All LinkGraphs that are to have symbol names compared by pointer equality must point to the same SymbolStringPool instance, which in ORC sessions should be the pool attached to the ExecutionSession. --------- Co-authored-by: Lang Hames <lhames@gmail.com>
424 lines
14 KiB
C++
424 lines
14 KiB
C++
//===------- DebuggerSupportPlugin.cpp - Utils for debugger support -------===//
|
|
//
|
|
// 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/Debugging/DebuggerSupportPlugin.h"
|
|
#include "llvm/ExecutionEngine/Orc/MachOBuilder.h"
|
|
|
|
#include "llvm/ADT/SmallSet.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/BinaryFormat/MachO.h"
|
|
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
|
|
#include "llvm/DebugInfo/DWARF/DWARFDebugLine.h"
|
|
|
|
#include <chrono>
|
|
|
|
#define DEBUG_TYPE "orc"
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::jitlink;
|
|
using namespace llvm::orc;
|
|
|
|
static const char *SynthDebugSectionName = "__jitlink_synth_debug_object";
|
|
|
|
namespace {
|
|
|
|
class MachODebugObjectSynthesizerBase
|
|
: public GDBJITDebugInfoRegistrationPlugin::DebugSectionSynthesizer {
|
|
public:
|
|
static bool isDebugSection(Section &Sec) {
|
|
return Sec.getName().starts_with("__DWARF,");
|
|
}
|
|
|
|
MachODebugObjectSynthesizerBase(LinkGraph &G, ExecutorAddr RegisterActionAddr)
|
|
: G(G), RegisterActionAddr(RegisterActionAddr) {}
|
|
virtual ~MachODebugObjectSynthesizerBase() = default;
|
|
|
|
Error preserveDebugSections() {
|
|
if (G.findSectionByName(SynthDebugSectionName)) {
|
|
LLVM_DEBUG({
|
|
dbgs() << "MachODebugObjectSynthesizer skipping graph " << G.getName()
|
|
<< " which contains an unexpected existing "
|
|
<< SynthDebugSectionName << " section.\n";
|
|
});
|
|
return Error::success();
|
|
}
|
|
|
|
LLVM_DEBUG({
|
|
dbgs() << "MachODebugObjectSynthesizer visiting graph " << G.getName()
|
|
<< "\n";
|
|
});
|
|
for (auto &Sec : G.sections()) {
|
|
if (!isDebugSection(Sec))
|
|
continue;
|
|
// Preserve blocks in this debug section by marking one existing symbol
|
|
// live for each block, and introducing a new live, anonymous symbol for
|
|
// each currently unreferenced block.
|
|
LLVM_DEBUG({
|
|
dbgs() << " Preserving debug section " << Sec.getName() << "\n";
|
|
});
|
|
SmallSet<Block *, 8> PreservedBlocks;
|
|
for (auto *Sym : Sec.symbols()) {
|
|
bool NewPreservedBlock =
|
|
PreservedBlocks.insert(&Sym->getBlock()).second;
|
|
if (NewPreservedBlock)
|
|
Sym->setLive(true);
|
|
}
|
|
for (auto *B : Sec.blocks())
|
|
if (!PreservedBlocks.count(B))
|
|
G.addAnonymousSymbol(*B, 0, 0, false, true);
|
|
}
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
protected:
|
|
LinkGraph &G;
|
|
ExecutorAddr RegisterActionAddr;
|
|
};
|
|
|
|
template <typename MachOTraits>
|
|
class MachODebugObjectSynthesizer : public MachODebugObjectSynthesizerBase {
|
|
public:
|
|
MachODebugObjectSynthesizer(ExecutionSession &ES, LinkGraph &G,
|
|
ExecutorAddr RegisterActionAddr)
|
|
: MachODebugObjectSynthesizerBase(G, RegisterActionAddr),
|
|
Builder(ES.getPageSize()) {}
|
|
|
|
using MachODebugObjectSynthesizerBase::MachODebugObjectSynthesizerBase;
|
|
|
|
Error startSynthesis() override {
|
|
LLVM_DEBUG({
|
|
dbgs() << "Creating " << SynthDebugSectionName << " for " << G.getName()
|
|
<< "\n";
|
|
});
|
|
|
|
for (auto &Sec : G.sections()) {
|
|
if (Sec.blocks().empty())
|
|
continue;
|
|
|
|
// Skip sections whose name's don't fit the MachO standard.
|
|
if (Sec.getName().empty() || Sec.getName().size() > 33 ||
|
|
Sec.getName().find(',') > 16)
|
|
continue;
|
|
|
|
if (isDebugSection(Sec))
|
|
DebugSections.push_back({&Sec, nullptr});
|
|
else if (Sec.getMemLifetime() != MemLifetime::NoAlloc)
|
|
NonDebugSections.push_back({&Sec, nullptr});
|
|
}
|
|
|
|
// Bail out early if no debug sections.
|
|
if (DebugSections.empty())
|
|
return Error::success();
|
|
|
|
// Write MachO header and debug section load commands.
|
|
Builder.Header.filetype = MachO::MH_OBJECT;
|
|
switch (G.getTargetTriple().getArch()) {
|
|
case Triple::x86_64:
|
|
Builder.Header.cputype = MachO::CPU_TYPE_X86_64;
|
|
Builder.Header.cpusubtype = MachO::CPU_SUBTYPE_X86_64_ALL;
|
|
break;
|
|
case Triple::aarch64:
|
|
Builder.Header.cputype = MachO::CPU_TYPE_ARM64;
|
|
Builder.Header.cpusubtype = MachO::CPU_SUBTYPE_ARM64_ALL;
|
|
break;
|
|
default:
|
|
llvm_unreachable("Unsupported architecture");
|
|
}
|
|
|
|
Seg = &Builder.addSegment("");
|
|
|
|
StringMap<std::unique_ptr<MemoryBuffer>> DebugSectionMap;
|
|
StringRef DebugLineSectionData;
|
|
for (auto &DSec : DebugSections) {
|
|
auto [SegName, SecName] = DSec.GraphSec->getName().split(',');
|
|
DSec.BuilderSec = &Seg->addSection(SecName, SegName);
|
|
|
|
SectionRange SR(*DSec.GraphSec);
|
|
DSec.BuilderSec->Content.Size = SR.getSize();
|
|
if (!SR.empty()) {
|
|
DSec.BuilderSec->align = Log2_64(SR.getFirstBlock()->getAlignment());
|
|
StringRef SectionData(SR.getFirstBlock()->getContent().data(),
|
|
SR.getFirstBlock()->getSize());
|
|
DebugSectionMap[SecName] =
|
|
MemoryBuffer::getMemBuffer(SectionData, G.getName(), false);
|
|
if (SecName == "__debug_line")
|
|
DebugLineSectionData = SectionData;
|
|
}
|
|
}
|
|
|
|
std::optional<StringRef> FileName;
|
|
if (!DebugLineSectionData.empty()) {
|
|
assert((G.getEndianness() == llvm::endianness::big ||
|
|
G.getEndianness() == llvm::endianness::little) &&
|
|
"G.getEndianness() must be either big or little");
|
|
auto DWARFCtx =
|
|
DWARFContext::create(DebugSectionMap, G.getPointerSize(),
|
|
G.getEndianness() == llvm::endianness::little);
|
|
DWARFDataExtractor DebugLineData(
|
|
DebugLineSectionData, G.getEndianness() == llvm::endianness::little,
|
|
G.getPointerSize());
|
|
uint64_t Offset = 0;
|
|
DWARFDebugLine::LineTable LineTable;
|
|
|
|
// Try to parse line data. Consume error on failure.
|
|
if (auto Err = LineTable.parse(DebugLineData, &Offset, *DWARFCtx, nullptr,
|
|
consumeError)) {
|
|
handleAllErrors(std::move(Err), [&](ErrorInfoBase &EIB) {
|
|
LLVM_DEBUG({
|
|
dbgs() << "Cannot parse line table for \"" << G.getName() << "\": ";
|
|
EIB.log(dbgs());
|
|
dbgs() << "\n";
|
|
});
|
|
});
|
|
} else {
|
|
if (!LineTable.Prologue.FileNames.empty())
|
|
FileName = *dwarf::toString(LineTable.Prologue.FileNames[0].Name);
|
|
}
|
|
}
|
|
|
|
// If no line table (or unable to use) then use graph name.
|
|
// FIXME: There are probably other debug sections we should look in first.
|
|
if (!FileName)
|
|
FileName = StringRef(G.getName());
|
|
|
|
Builder.addSymbol("", MachO::N_SO, 0, 0, 0);
|
|
Builder.addSymbol(*FileName, MachO::N_SO, 0, 0, 0);
|
|
auto TimeStamp = std::chrono::duration_cast<std::chrono::seconds>(
|
|
std::chrono::system_clock::now().time_since_epoch())
|
|
.count();
|
|
Builder.addSymbol("", MachO::N_OSO, 3, 1, TimeStamp);
|
|
|
|
for (auto &NDSP : NonDebugSections) {
|
|
auto [SegName, SecName] = NDSP.GraphSec->getName().split(',');
|
|
NDSP.BuilderSec = &Seg->addSection(SecName, SegName);
|
|
SectionRange SR(*NDSP.GraphSec);
|
|
if (!SR.empty())
|
|
NDSP.BuilderSec->align = Log2_64(SR.getFirstBlock()->getAlignment());
|
|
|
|
// Add stabs.
|
|
for (auto *Sym : NDSP.GraphSec->symbols()) {
|
|
// Skip anonymous symbols.
|
|
if (!Sym->hasName())
|
|
continue;
|
|
|
|
uint8_t SymType = Sym->isCallable() ? MachO::N_FUN : MachO::N_GSYM;
|
|
|
|
Builder.addSymbol("", MachO::N_BNSYM, 1, 0, 0);
|
|
StabSymbols.push_back(
|
|
{*Sym, Builder.addSymbol(*Sym->getName(), SymType, 1, 0, 0),
|
|
Builder.addSymbol(*Sym->getName(), SymType, 0, 0, 0)});
|
|
Builder.addSymbol("", MachO::N_ENSYM, 1, 0, 0);
|
|
}
|
|
}
|
|
|
|
Builder.addSymbol("", MachO::N_SO, 1, 0, 0);
|
|
|
|
// Lay out the debug object, create a section and block for it.
|
|
size_t DebugObjectSize = Builder.layout();
|
|
|
|
auto &SDOSec = G.createSection(SynthDebugSectionName, MemProt::Read);
|
|
MachOContainerBlock = &G.createMutableContentBlock(
|
|
SDOSec, G.allocateBuffer(DebugObjectSize), orc::ExecutorAddr(), 8, 0);
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
Error completeSynthesisAndRegister() override {
|
|
if (!MachOContainerBlock) {
|
|
LLVM_DEBUG({
|
|
dbgs() << "Not writing MachO debug object header for " << G.getName()
|
|
<< " since createDebugSection failed\n";
|
|
});
|
|
|
|
return Error::success();
|
|
}
|
|
ExecutorAddr MaxAddr;
|
|
for (auto &NDSec : NonDebugSections) {
|
|
SectionRange SR(*NDSec.GraphSec);
|
|
NDSec.BuilderSec->addr = SR.getStart().getValue();
|
|
NDSec.BuilderSec->size = SR.getSize();
|
|
NDSec.BuilderSec->offset = SR.getStart().getValue();
|
|
if (SR.getEnd() > MaxAddr)
|
|
MaxAddr = SR.getEnd();
|
|
}
|
|
|
|
for (auto &DSec : DebugSections) {
|
|
if (DSec.GraphSec->blocks_size() != 1)
|
|
return make_error<StringError>(
|
|
"Unexpected number of blocks in debug info section",
|
|
inconvertibleErrorCode());
|
|
|
|
if (ExecutorAddr(DSec.BuilderSec->addr) + DSec.BuilderSec->size > MaxAddr)
|
|
MaxAddr = ExecutorAddr(DSec.BuilderSec->addr) + DSec.BuilderSec->size;
|
|
|
|
auto &B = **DSec.GraphSec->blocks().begin();
|
|
DSec.BuilderSec->Content.Data = B.getContent().data();
|
|
DSec.BuilderSec->Content.Size = B.getContent().size();
|
|
DSec.BuilderSec->flags |= MachO::S_ATTR_DEBUG;
|
|
}
|
|
|
|
LLVM_DEBUG({
|
|
dbgs() << "Writing MachO debug object header for " << G.getName() << "\n";
|
|
});
|
|
|
|
// Update stab symbol addresses.
|
|
for (auto &SS : StabSymbols) {
|
|
SS.StartStab.nlist().n_value = SS.Sym.getAddress().getValue();
|
|
SS.EndStab.nlist().n_value = SS.Sym.getSize();
|
|
}
|
|
|
|
Builder.write(MachOContainerBlock->getAlreadyMutableContent());
|
|
|
|
static constexpr bool AutoRegisterCode = true;
|
|
SectionRange R(MachOContainerBlock->getSection());
|
|
G.allocActions().push_back(
|
|
{cantFail(shared::WrapperFunctionCall::Create<
|
|
shared::SPSArgList<shared::SPSExecutorAddrRange, bool>>(
|
|
RegisterActionAddr, R.getRange(), AutoRegisterCode)),
|
|
{}});
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
private:
|
|
struct SectionPair {
|
|
Section *GraphSec = nullptr;
|
|
typename MachOBuilder<MachOTraits>::Section *BuilderSec = nullptr;
|
|
};
|
|
|
|
struct StabSymbolsEntry {
|
|
using RelocTarget = typename MachOBuilder<MachOTraits>::RelocTarget;
|
|
|
|
StabSymbolsEntry(Symbol &Sym, RelocTarget StartStab, RelocTarget EndStab)
|
|
: Sym(Sym), StartStab(StartStab), EndStab(EndStab) {}
|
|
|
|
Symbol &Sym;
|
|
RelocTarget StartStab, EndStab;
|
|
};
|
|
|
|
using BuilderType = MachOBuilder<MachOTraits>;
|
|
|
|
Block *MachOContainerBlock = nullptr;
|
|
MachOBuilder<MachOTraits> Builder;
|
|
typename MachOBuilder<MachOTraits>::Segment *Seg = nullptr;
|
|
std::vector<StabSymbolsEntry> StabSymbols;
|
|
SmallVector<SectionPair, 16> DebugSections;
|
|
SmallVector<SectionPair, 16> NonDebugSections;
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
namespace llvm {
|
|
namespace orc {
|
|
|
|
Expected<std::unique_ptr<GDBJITDebugInfoRegistrationPlugin>>
|
|
GDBJITDebugInfoRegistrationPlugin::Create(ExecutionSession &ES,
|
|
JITDylib &ProcessJD,
|
|
const Triple &TT) {
|
|
auto RegisterActionAddr =
|
|
TT.isOSBinFormatMachO()
|
|
? ES.intern("_llvm_orc_registerJITLoaderGDBAllocAction")
|
|
: ES.intern("llvm_orc_registerJITLoaderGDBAllocAction");
|
|
|
|
if (auto RegisterSym = ES.lookup({&ProcessJD}, RegisterActionAddr))
|
|
return std::make_unique<GDBJITDebugInfoRegistrationPlugin>(
|
|
RegisterSym->getAddress());
|
|
else
|
|
return RegisterSym.takeError();
|
|
}
|
|
|
|
Error GDBJITDebugInfoRegistrationPlugin::notifyFailed(
|
|
MaterializationResponsibility &MR) {
|
|
return Error::success();
|
|
}
|
|
|
|
Error GDBJITDebugInfoRegistrationPlugin::notifyRemovingResources(
|
|
JITDylib &JD, ResourceKey K) {
|
|
return Error::success();
|
|
}
|
|
|
|
void GDBJITDebugInfoRegistrationPlugin::notifyTransferringResources(
|
|
JITDylib &JD, ResourceKey DstKey, ResourceKey SrcKey) {}
|
|
|
|
void GDBJITDebugInfoRegistrationPlugin::modifyPassConfig(
|
|
MaterializationResponsibility &MR, LinkGraph &LG,
|
|
PassConfiguration &PassConfig) {
|
|
|
|
if (LG.getTargetTriple().getObjectFormat() == Triple::MachO)
|
|
modifyPassConfigForMachO(MR, LG, PassConfig);
|
|
else {
|
|
LLVM_DEBUG({
|
|
dbgs() << "GDBJITDebugInfoRegistrationPlugin skipping unspported graph "
|
|
<< LG.getName() << "(triple = " << LG.getTargetTriple().str()
|
|
<< "\n";
|
|
});
|
|
}
|
|
}
|
|
|
|
void GDBJITDebugInfoRegistrationPlugin::modifyPassConfigForMachO(
|
|
MaterializationResponsibility &MR, jitlink::LinkGraph &LG,
|
|
jitlink::PassConfiguration &PassConfig) {
|
|
|
|
switch (LG.getTargetTriple().getArch()) {
|
|
case Triple::x86_64:
|
|
case Triple::aarch64:
|
|
// Supported, continue.
|
|
assert(LG.getPointerSize() == 8 && "Graph has incorrect pointer size");
|
|
assert(LG.getEndianness() == llvm::endianness::little &&
|
|
"Graph has incorrect endianness");
|
|
break;
|
|
default:
|
|
// Unsupported.
|
|
LLVM_DEBUG({
|
|
dbgs() << "GDBJITDebugInfoRegistrationPlugin skipping unsupported "
|
|
<< "MachO graph " << LG.getName()
|
|
<< "(triple = " << LG.getTargetTriple().str()
|
|
<< ", pointer size = " << LG.getPointerSize() << ", endianness = "
|
|
<< (LG.getEndianness() == llvm::endianness::big ? "big" : "little")
|
|
<< ")\n";
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Scan for debug sections. If we find one then install passes.
|
|
bool HasDebugSections = false;
|
|
for (auto &Sec : LG.sections())
|
|
if (MachODebugObjectSynthesizerBase::isDebugSection(Sec)) {
|
|
HasDebugSections = true;
|
|
break;
|
|
}
|
|
|
|
if (HasDebugSections) {
|
|
LLVM_DEBUG({
|
|
dbgs() << "GDBJITDebugInfoRegistrationPlugin: Graph " << LG.getName()
|
|
<< " contains debug info. Installing debugger support passes.\n";
|
|
});
|
|
|
|
auto MDOS = std::make_shared<MachODebugObjectSynthesizer<MachO64LE>>(
|
|
MR.getTargetJITDylib().getExecutionSession(), LG, RegisterActionAddr);
|
|
PassConfig.PrePrunePasses.push_back(
|
|
[=](LinkGraph &G) { return MDOS->preserveDebugSections(); });
|
|
PassConfig.PostPrunePasses.push_back(
|
|
[=](LinkGraph &G) { return MDOS->startSynthesis(); });
|
|
PassConfig.PostFixupPasses.push_back(
|
|
[=](LinkGraph &G) { return MDOS->completeSynthesisAndRegister(); });
|
|
} else {
|
|
LLVM_DEBUG({
|
|
dbgs() << "GDBJITDebugInfoRegistrationPlugin: Graph " << LG.getName()
|
|
<< " contains no debug info. Skipping.\n";
|
|
});
|
|
}
|
|
}
|
|
|
|
} // namespace orc
|
|
} // namespace llvm
|