123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 |
- //===------- DebugObjectManagerPlugin.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/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/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 {
- class DebugObjectSection {
- public:
- virtual void setTargetMemoryRange(SectionRange Range) = 0;
- virtual void dump(raw_ostream &OS, StringRef Name) {}
- virtual ~DebugObjectSection() = default;
- };
- 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().getValue());
- }
- 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);
- }
- }
- enum class Requirement {
- // Request final target memory load-addresses for all sections.
- ReportFinalSectionLoadAddresses,
- };
- /// The plugin creates a debug object from 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(JITLinkMemoryManager &MemMgr, const JITLinkDylib *JD,
- ExecutionSession &ES)
- : MemMgr(MemMgr), JD(JD), ES(ES) {}
- void set(Requirement Req) { Reqs.insert(Req); }
- bool has(Requirement Req) const { return Reqs.count(Req) > 0; }
- using FinalizeContinuation = std::function<void(Expected<ExecutorAddrRange>)>;
- void finalizeAsync(FinalizeContinuation OnFinalize);
- virtual ~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));
- }
- }
- virtual void reportSectionTargetMemoryRange(StringRef Name,
- SectionRange TargetMem) {}
- protected:
- using InFlightAlloc = JITLinkMemoryManager::InFlightAlloc;
- using FinalizedAlloc = JITLinkMemoryManager::FinalizedAlloc;
- virtual Expected<SimpleSegmentAlloc> finalizeWorkingMemory() = 0;
- JITLinkMemoryManager &MemMgr;
- const JITLinkDylib *JD = nullptr;
- private:
- ExecutionSession &ES;
- std::set<Requirement> Reqs;
- FinalizedAlloc Alloc;
- };
- // 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 && "Cannot finalize more than once");
- if (auto SimpleSegAlloc = finalizeWorkingMemory()) {
- auto ROSeg = SimpleSegAlloc->getSegInfo(MemProt::Read);
- ExecutorAddrRange DebugObjRange(ExecutorAddr(ROSeg.Addr),
- ExecutorAddrDiff(ROSeg.WorkingMem.size()));
- SimpleSegAlloc->finalize(
- [this, DebugObjRange,
- OnFinalize = std::move(OnFinalize)](Expected<FinalizedAlloc> FA) {
- if (FA) {
- Alloc = std::move(*FA);
- OnFinalize(DebugObjRange);
- } else
- OnFinalize(FA.takeError());
- });
- } else
- OnFinalize(SimpleSegAlloc.takeError());
- }
- /// 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, ExecutionSession &ES);
- void reportSectionTargetMemoryRange(StringRef Name,
- SectionRange TargetMem) override;
- StringRef getBuffer() const { return Buffer->getMemBufferRef().getBuffer(); }
- protected:
- Expected<SimpleSegmentAlloc> finalizeWorkingMemory() 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, JITLinkMemoryManager &MemMgr,
- const JITLinkDylib *JD, ExecutionSession &ES);
- static std::unique_ptr<WritableMemoryBuffer>
- CopyBuffer(MemoryBufferRef Buffer, Error &Err);
- ELFDebugObject(std::unique_ptr<WritableMemoryBuffer> Buffer,
- JITLinkMemoryManager &MemMgr, const JITLinkDylib *JD,
- ExecutionSession &ES)
- : DebugObject(MemMgr, JD, ES), 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,
- JITLinkMemoryManager &MemMgr,
- const JITLinkDylib *JD, ExecutionSession &ES) {
- using SectionHeader = typename ELFT::Shdr;
- Error Err = Error::success();
- std::unique_ptr<ELFDebugObject> DebugObj(
- new ELFDebugObject(CopyBuffer(Buffer, Err), MemMgr, JD, ES));
- 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);
- if (!(Header.sh_flags & ELF::SHF_ALLOC))
- continue;
- 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,
- ExecutionSession &ES) {
- unsigned char Class, Endian;
- std::tie(Class, Endian) = getElfArchType(Buffer.getBuffer());
- if (Class == ELF::ELFCLASS32) {
- if (Endian == ELF::ELFDATA2LSB)
- return CreateArchType<ELF32LE>(Buffer, Ctx.getMemoryManager(),
- Ctx.getJITLinkDylib(), ES);
- if (Endian == ELF::ELFDATA2MSB)
- return CreateArchType<ELF32BE>(Buffer, Ctx.getMemoryManager(),
- Ctx.getJITLinkDylib(), ES);
- return nullptr;
- }
- if (Class == ELF::ELFCLASS64) {
- if (Endian == ELF::ELFDATA2LSB)
- return CreateArchType<ELF64LE>(Buffer, Ctx.getMemoryManager(),
- Ctx.getJITLinkDylib(), ES);
- if (Endian == ELF::ELFDATA2MSB)
- return CreateArchType<ELF64BE>(Buffer, Ctx.getMemoryManager(),
- Ctx.getJITLinkDylib(), ES);
- return nullptr;
- }
- return nullptr;
- }
- Expected<SimpleSegmentAlloc> ELFDebugObject::finalizeWorkingMemory() {
- 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 PageSize = sys::Process::getPageSizeEstimate();
- size_t Size = Buffer->getBufferSize();
- // Allocate working memory for debug object in read-only segment.
- auto Alloc = SimpleSegmentAlloc::Create(
- MemMgr, JD, {{MemProt::Read, {Size, Align(PageSize)}}});
- if (!Alloc)
- return Alloc;
- // Initialize working memory with a copy of our object buffer.
- auto SegInfo = Alloc->getSegInfo(MemProt::Read);
- memcpy(SegInfo.WorkingMem.data(), Buffer->getBufferStart(), Size);
- Buffer.reset();
- return 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>("In " + Buffer->getBufferIdentifier() +
- ", encountered duplicate section \"" +
- Name + "\" while building debug object",
- inconvertibleErrorCode());
- return Error::success();
- }
- DebugObjectSection *ELFDebugObject::getSection(StringRef Name) {
- auto It = Sections.find(Name);
- return It == Sections.end() ? nullptr : It->second.get();
- }
- /// Creates a debug object based on the input object file from
- /// ObjectLinkingLayerJITLinkContext.
- ///
- static Expected<std::unique_ptr<DebugObject>>
- createDebugObjectFromBuffer(ExecutionSession &ES, LinkGraph &G,
- JITLinkContext &Ctx, MemoryBufferRef ObjBuffer) {
- switch (G.getTargetTriple().getObjectFormat()) {
- case Triple::ELF:
- return ELFDebugObject::Create(ObjBuffer, Ctx, ES);
- 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() = default;
- void DebugObjectManagerPlugin::notifyMaterializing(
- MaterializationResponsibility &MR, LinkGraph &G, JITLinkContext &Ctx,
- MemoryBufferRef ObjBuffer) {
- std::lock_guard<std::mutex> Lock(PendingObjsLock);
- assert(PendingObjs.count(&MR) == 0 &&
- "Cannot have more than one pending debug object per "
- "MaterializationResponsibility");
- if (auto DebugObj = createDebugObjectFromBuffer(ES, G, Ctx, ObjBuffer)) {
- // Not all link artifacts allow debugging.
- if (*DebugObj != nullptr)
- PendingObjs[&MR] = std::move(*DebugObj);
- } else {
- ES.reportError(DebugObj.takeError());
- }
- }
- void DebugObjectManagerPlugin::modifyPassConfig(
- MaterializationResponsibility &MR, LinkGraph &G,
- PassConfiguration &PassConfig) {
- // Not all link artifacts have associated debug objects.
- std::lock_guard<std::mutex> Lock(PendingObjsLock);
- auto It = PendingObjs.find(&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) {
- std::lock_guard<std::mutex> Lock(PendingObjsLock);
- auto It = PendingObjs.find(&MR);
- if (It == PendingObjs.end())
- return Error::success();
- // During finalization the debug object is registered with the target.
- // Materialization must wait for this process to finish. Otherwise we might
- // start running code before the debugger processed the corresponding debug
- // info.
- std::promise<MSVCPError> FinalizePromise;
- std::future<MSVCPError> FinalizeErr = FinalizePromise.get_future();
- It->second->finalizeAsync(
- [this, &FinalizePromise, &MR](Expected<ExecutorAddrRange> TargetMem) {
- // Any failure here will fail materialization.
- if (!TargetMem) {
- FinalizePromise.set_value(TargetMem.takeError());
- return;
- }
- if (Error Err = Target->registerDebugObject(*TargetMem)) {
- FinalizePromise.set_value(std::move(Err));
- return;
- }
- // Once our tracking info is updated, notifyEmitted() can return and
- // finish materialization.
- FinalizePromise.set_value(MR.withResourceKeyDo([&](ResourceKey K) {
- assert(PendingObjs.count(&MR) && "We still hold PendingObjsLock");
- std::lock_guard<std::mutex> Lock(RegisteredObjsLock);
- RegisteredObjs[K].push_back(std::move(PendingObjs[&MR]));
- PendingObjs.erase(&MR);
- }));
- });
- return FinalizeErr.get();
- }
- Error DebugObjectManagerPlugin::notifyFailed(
- MaterializationResponsibility &MR) {
- std::lock_guard<std::mutex> Lock(PendingObjsLock);
- PendingObjs.erase(&MR);
- return Error::success();
- }
- void DebugObjectManagerPlugin::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 DebugObjectManagerPlugin::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
|