diff options
Diffstat (limited to 'llvm/lib/ExecutionEngine/Orc/Debugging/DebuggerSupportPlugin.cpp')
| -rw-r--r-- | llvm/lib/ExecutionEngine/Orc/Debugging/DebuggerSupportPlugin.cpp | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/llvm/lib/ExecutionEngine/Orc/Debugging/DebuggerSupportPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/Debugging/DebuggerSupportPlugin.cpp new file mode 100644 index 000000000000..c6ffd9f7c2e3 --- /dev/null +++ b/llvm/lib/ExecutionEngine/Orc/Debugging/DebuggerSupportPlugin.cpp @@ -0,0 +1,423 @@ +//===------- 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().startswith("__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 |
