diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/DebugSupport.h b/llvm/include/llvm/ExecutionEngine/JITLink/DebugSupport.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/JITLink/DebugSupport.h @@ -0,0 +1,59 @@ +//===------------ DebugSupport.h - JITLink debug utils ----------*- 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 +// +//===----------------------------------------------------------------------===// +// +// Create companion objects for consumption by a debugger. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_JITLINK_DEBUGSUPPORT_H +#define LLVM_EXECUTIONENGINE_JITLINK_DEBUGSUPPORT_H + +#include "llvm/ADT/Triple.h" +#include "llvm/ExecutionEngine/JITLink/JITLink.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/Memory.h" +#include "llvm/Support/MemoryBuffer.h" + +#include +#include + +namespace llvm { +namespace jitlink { + +/// Abstract interface for debug objects in the DebugObjectManagerPlugin. +/// +/// The plugin requests 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: + using FinalizeContinuation = std::function)>; + + virtual void modifyPassConfig(const Triple &TT, + PassConfiguration &PassConfig) = 0; + virtual void finalizeAsync(FinalizeContinuation OnFinalize) = 0; + virtual ~DebugObject() {} +}; + +/// Creates a debug object based on the input object file from +/// ObjectLinkingLayerJITLinkContext. +/// +/// It replicates the approach used in RuntimeDyld: Patching executable and data +/// section headers in the given object buffer with load-addresses of their +/// corresponding LinkGraph segments in target memory. +/// +Expected> +createDebugObjectFromBuffer(const Triple &TT, JITLinkContext &Ctx, + std::unique_ptr Buffer); + +} // namespace jitlink +} // namespace llvm + +#endif // LLVM_EXECUTIONENGINE_JITLINK_DEBUGSUPPORT_H diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/JITLink.h b/llvm/include/llvm/ExecutionEngine/JITLink/JITLink.h --- a/llvm/include/llvm/ExecutionEngine/JITLink/JITLink.h +++ b/llvm/include/llvm/ExecutionEngine/JITLink/JITLink.h @@ -1309,6 +1309,8 @@ return std::make_unique(std::move(Cont)); } +class DebugObject; + /// Holds context for a single jitLink invocation. class JITLinkContext { public: @@ -1326,6 +1328,9 @@ /// Return the MemoryManager to be used for this link. virtual JITLinkMemoryManager &getMemoryManager() = 0; + /// Notify this context that linking is about to start. + virtual void notifyMaterializing(jitlink::LinkGraph &G) = 0; + /// Notify this context that linking failed. /// Called by JITLink if linking cannot be completed. virtual void notifyFailed(Error Err) = 0; @@ -1371,6 +1376,11 @@ /// The default version performs no modification. virtual Error modifyPassConfig(const Triple &TT, PassConfiguration &Config); + /// Returns a debug object for the given link graph or nullptr if it's not + /// supported. + virtual Expected> + createDebugObject(LinkGraph &G); + private: const JITLinkDylib *JD = nullptr; }; diff --git a/llvm/include/llvm/ExecutionEngine/Orc/DebugObjectManagerPlugin.h b/llvm/include/llvm/ExecutionEngine/Orc/DebugObjectManagerPlugin.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/Orc/DebugObjectManagerPlugin.h @@ -0,0 +1,82 @@ +//===---- 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 +// +//===----------------------------------------------------------------------===// +// +// ObjectLinkingLayer plugin for emitting debug objects. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_ORC_DEBUGOBJECTMANAGERPLUGIN_H +#define LLVM_EXECUTIONENGINE_ORC_DEBUGOBJECTMANAGERPLUGIN_H + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/Triple.h" +#include "llvm/ExecutionEngine/JITLink/DebugSupport.h" +#include "llvm/ExecutionEngine/JITLink/JITLink.h" +#include "llvm/ExecutionEngine/Orc/Core.h" +#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" +#include "llvm/ExecutionEngine/Orc/TPCDebugObjectRegistrar.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/Memory.h" + +#include +#include +#include + +namespace llvm { +namespace orc { + +/// Requests and manages DebugObjects for JITLink artifacts. +/// +/// For each processed MaterializationResponsibility a DebugObject is requested +/// from JITLinkContext. DebugObjects are pending as long as their corresponding +/// MaterializationResponsibility did not finish loading. +/// +/// There can only be one pending DebugObject per MaterializationResponsibility. +/// Pending DebugObjects for failed MaterializationResponsibility are discarded. +/// +/// Once loading finished, DebugObjects are finalized and their target memory +/// is reported to the provided DebugObjectRegistrar. Ownership of DebugObjects +/// remains with the plugin. +/// +class DebugObjectManagerPlugin : public ObjectLinkingLayer::Plugin { +public: + DebugObjectManagerPlugin(ExecutionSession &ES, + std::unique_ptr Target) + : ES(ES), Target(std::move(Target)) {} + + void notifyMaterializing(MaterializationResponsibility &MR, + jitlink::LinkGraph &G, + jitlink::JITLinkContext &Ctx) override; + + void notifyLoaded(MaterializationResponsibility &MR) override; + Error notifyFailed(MaterializationResponsibility &MR) override; + Error notifyRemovingResources(ResourceKey K) override; + + void notifyTransferringResources(ResourceKey DstKey, + ResourceKey SrcKey) override; + + void modifyPassConfig(MaterializationResponsibility &MR, const Triple &TT, + jitlink::PassConfiguration &PassConfig) override; + +private: + ExecutionSession &ES; + + using DebugObjectPtr = std::unique_ptr; + DenseMap> RegisteredObjs; + DenseMap PendingObjs; + + std::mutex RegisteredObjsLock; + std::mutex PendingObjsLock; + + std::unique_ptr Target; +}; + +} // namespace orc +} // namespace llvm + +#endif // LLVM_EXECUTIONENGINE_ORC_DEBUGOBJECTMANAGERPLUGIN_H diff --git a/llvm/include/llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h b/llvm/include/llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h --- a/llvm/include/llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h +++ b/llvm/include/llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h @@ -69,6 +69,10 @@ const Triple &TT, jitlink::PassConfiguration &Config) {} + virtual void notifyMaterializing(MaterializationResponsibility &MR, + jitlink::LinkGraph &G, + jitlink::JITLinkContext &Ctx) {} + virtual void notifyLoaded(MaterializationResponsibility &MR) {} virtual Error notifyEmitted(MaterializationResponsibility &MR) { return Error::success(); diff --git a/llvm/include/llvm/ExecutionEngine/Orc/TPCDebugObjectRegistrar.h b/llvm/include/llvm/ExecutionEngine/Orc/TPCDebugObjectRegistrar.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/Orc/TPCDebugObjectRegistrar.h @@ -0,0 +1,67 @@ +//===- TPCDebugObjectRegistrar.h - TPC-based debug registration -*- 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 +// +//===----------------------------------------------------------------------===// +// +// TargetProcessControl based registration of debug objects. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_ORC_TPCDEBUGOBJECTREGISTRAR_H +#define LLVM_EXECUTIONENGINE_ORC_TPCDEBUGOBJECTREGISTRAR_H + +#include "llvm/ExecutionEngine/JITSymbol.h" +#include "llvm/ExecutionEngine/Orc/TargetProcessControl.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/Memory.h" + +#include +#include +#include + +namespace llvm { +namespace orc { + +/// Abstract interface used with the DebugObjectManagerPlugin for registering +/// debug objects in the target process. +class DebugObjectRegistrar { +public: + virtual Error registerDebugObject(sys::MemoryBlock) = 0; + virtual ~DebugObjectRegistrar() {} +}; + +/// Register debug objects via TargetProcessControl. +class TPCDebugObjectRegistrar : public DebugObjectRegistrar { +public: + using SerializeBlockInfoFn = + std::vector (*)(sys::MemoryBlock TargetMemBlock); + + TPCDebugObjectRegistrar(TargetProcessControl &TPC, + JITTargetAddress RegisterFn, + SerializeBlockInfoFn SerializeBlockInfo) + : TPC(TPC), RegisterFn(RegisterFn), + SerializeBlockInfo(SerializeBlockInfo) {} + + Error registerDebugObject(sys::MemoryBlock TargetMem) override { + return TPC.runWrapper(RegisterFn, SerializeBlockInfo(TargetMem)) + .takeError(); + } + +private: + TargetProcessControl &TPC; + JITTargetAddress RegisterFn; + SerializeBlockInfoFn SerializeBlockInfo; +}; + +/// Creates a TargetProcessControl-based DebugObjectRegistrar that emits debug +/// objects to the GDB JIT interface. +Expected> +createJITLoaderGDBRegistrar(TargetProcessControl &TPC); + +} // end namespace orc +} // end namespace llvm + +#endif // LLVM_EXECUTIONENGINE_ORC_TDEBUGOBJECTREGISTRAR_H diff --git a/llvm/include/llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h b/llvm/include/llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h @@ -0,0 +1,22 @@ +//===- JITLoaderGDB.h - Register objects via GDB JIT interface -*- 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 +// +//===----------------------------------------------------------------------===// +// +// Register objects for access by debuggers via the GDB JIT interface. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_ORC_TARGETPROCESS_JITLOADERGDB_H +#define LLVM_EXECUTIONENGINE_ORC_TARGETPROCESS_JITLOADERGDB_H + +#include "llvm/ExecutionEngine/Orc/Shared/TargetProcessControlTypes.h" +#include + +extern "C" llvm::orc::tpctypes::CWrapperFunctionResult +llvm_orc_registerJITLoaderGDBWrapper(uint8_t *Data, uint64_t Size); + +#endif // LLVM_EXECUTIONENGINE_ORC_TARGETPROCESS_JITLOADERGDB_H diff --git a/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt b/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt --- a/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt +++ b/llvm/lib/ExecutionEngine/JITLink/CMakeLists.txt @@ -2,6 +2,7 @@ JITLink.cpp JITLinkGeneric.cpp JITLinkMemoryManager.cpp + DebugSupport.cpp EHFrameSupport.cpp #macho MachO.cpp diff --git a/llvm/lib/ExecutionEngine/JITLink/DebugSupport.cpp b/llvm/lib/ExecutionEngine/JITLink/DebugSupport.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/ExecutionEngine/JITLink/DebugSupport.cpp @@ -0,0 +1,277 @@ +//===----------- DebugSupport.cpp - JITLink debug utils ---------*- 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/JITLink/DebugSupport.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/Process.h" +#include "llvm/Support/raw_ostream.h" + +#include + +#define DEBUG_TYPE "jitlink" + +using namespace llvm::object; + +namespace llvm { +namespace jitlink { + +static constexpr sys::Memory::ProtectionFlags ReadOnlyFlag = + static_cast(sys::Memory::MF_READ); + +static const std::set 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; +} + +class ReadOnlySegment { + using Allocation = JITLinkMemoryManager::Allocation; + +public: + ReadOnlySegment(JITLinkMemoryManager &MemMgr, const JITLinkDylib *JD) + : MemMgr(MemMgr), JD(JD), Alloc(nullptr) {} + + Error initialize(std::unique_ptr DebugObj) { + // TODO: This works, but what actual alignment requirements do we have? + unsigned Alignment = sys::Process::getPageSizeEstimate(); + size_t Size = DebugObj->getBufferSize(); + + // Allocate working memory for debug object in read-only segment. + // TODO: Could we use the given buffer as working memory directly + // and avoid the unnecessary copy below? + Expected> AllocOrErr = + MemMgr.allocate(JD, {{ReadOnlyFlag, {Alignment, Size, 0}}}); + if (!AllocOrErr) + return AllocOrErr.takeError(); + Alloc = std::move(*AllocOrErr); + + // Initialize working memory with a copy of the debug object. + MutableArrayRef DebugObjMem = Alloc->getWorkingMemory(ReadOnlyFlag); + memcpy(DebugObjMem.data(), DebugObj->getBufferStart(), Size); + + return Error::success(); + } + + void finalizeAsync(Allocation::FinalizeContinuation OnFinalize) { + Alloc->finalizeAsync(std::move(OnFinalize)); + } + + sys::MemoryBlock getTargetMemBlock() const { + return sys::MemoryBlock( + jitTargetAddressToPointer(Alloc->getTargetMemory(ReadOnlyFlag)), + Alloc->getWorkingMemory(ReadOnlyFlag).size()); + } + +private: + JITLinkMemoryManager &MemMgr; + const JITLinkDylib *JD; + std::unique_ptr Alloc; +}; + +class DebugObjectSection { +public: + virtual void setTargetMemoryRange(SectionRange Range) = 0; + virtual void dump(raw_ostream &OS, StringRef Name) {} + virtual ~DebugObjectSection() {} +}; + +template +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(Header)) {} + + void setTargetMemoryRange(SectionRange Range) override { + // Only patch load-addresses for executable and data sections. + if (isTextOrDataSection()) { + Header->sh_addr = static_cast(Range.getStart()); + } + } + + void dump(raw_ostream &OS, StringRef Name) override { + if (auto Addr = static_cast(Header->sh_addr)) { + OS << formatv(" {0:x16} {1}\n", Addr, Name); + } else { + OS << formatv(" {0}\n", Name); + } + } + +private: + typename ELFT::Shdr *Header; + + bool 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 class ELFDebugObject : public DebugObject { + using SectionHeader = typename ELFT::Shdr; + + ELFDebugObject(std::unique_ptr Buffer, + ReadOnlySegment TargetSegment) + : Buffer(std::move(Buffer)), Segment(std::move(TargetSegment)) {} + +public: + static Expected>> + + Create(std::unique_ptr Buffer, + ReadOnlySegment TargetSegment) { + + MutableArrayRef B = Buffer->getBuffer(); + Expected> ObjRef = + ELFFile::create(StringRef(B.data(), B.size())); + if (!ObjRef) + return ObjRef.takeError(); + + Expected> Sections = ObjRef->sections(); + if (!Sections) + return Sections.takeError(); + + std::unique_ptr> DebugObj( + new ELFDebugObject(std::move(Buffer), std::move(TargetSegment))); + + bool HasDwarfSection = false; + for (const SectionHeader &Header : *Sections) { + Expected Name = ObjRef->getSectionName(Header); + if (!Name) + return Name.takeError(); + if (Name->empty()) + continue; + HasDwarfSection |= isDwarfSection(*Name); + if (Error Err = DebugObj->recordSection(*Name, Header)) + return std::move(Err); + } + + if (!HasDwarfSection) { + LLVM_DEBUG(dbgs() << "Aborting debug registration for LinkGraph \"" + << Buffer->getBufferIdentifier() + "\": " + << "input object contains no debug info\n"); + return nullptr; + } + + return std::move(DebugObj); + } + + Error recordSection(StringRef Name, const SectionHeader &Header) { + auto WrappedHeader = std::make_unique>(&Header); + auto ItInserted = Sections.try_emplace(Name, std::move(WrappedHeader)); + if (!ItInserted.second) + return make_error("Duplicate section", + inconvertibleErrorCode()); + return Error::success(); + } + + DebugObjectSection *getSection(StringRef Name) { + auto It = Sections.find(Name); + return It == Sections.end() ? nullptr : It->second.get(); + } + + void modifyPassConfig(const Triple &TT, + PassConfiguration &PassConfig) override { + PassConfig.PostAllocationPasses.push_back( + [this](LinkGraph &Graph) -> Error { + // Once target memory is allocated, report the final load-addresses + // for all sections to the debug object. + for (const Section &GraphSection : Graph.sections()) + if (auto *DebugObjSection = getSection(GraphSection.getName())) + DebugObjSection->setTargetMemoryRange(GraphSection); + + return Error::success(); + }); + } + + void finalizeAsync(FinalizeContinuation OnFinalize) override { + LLVM_DEBUG({ + dbgs() << "Section load-addresses in debug object for \"" + << Buffer->getBufferIdentifier() << "\":\n"; + for (const auto &KV : Sections) + KV.second->dump(dbgs(), KV.first()); + }); + + // Allocate target memory for the debug object, start copying over our + // buffer and pass on the result once we're done. + if (Error Err = Segment.initialize(std::move(Buffer))) { + OnFinalize(std::move(Err)); + return; + } + + Segment.finalizeAsync([this, OnFinalize](Error Err) { + if (Err) + OnFinalize(std::move(Err)); + else + OnFinalize(Segment.getTargetMemBlock()); + }); + } + +private: + StringMap> Sections; + std::unique_ptr Buffer; + ReadOnlySegment Segment; +}; + +static Expected> +createDebugObjectELF(std::unique_ptr Buf, + JITLinkContext &Ctx) { + unsigned char Class, Endian; + MutableArrayRef B = Buf->getBuffer(); + std::tie(Class, Endian) = getElfArchType(StringRef(B.data(), B.size())); + ReadOnlySegment Seg(Ctx.getMemoryManager(), Ctx.getJITLinkDylib()); + + if (Class == ELF::ELFCLASS32) { + if (Endian == ELF::ELFDATA2LSB) + return ELFDebugObject::Create(std::move(Buf), std::move(Seg)); + if (Endian == ELF::ELFDATA2MSB) + return ELFDebugObject::Create(std::move(Buf), std::move(Seg)); + return nullptr; + } + if (Class == ELF::ELFCLASS64) { + if (Endian == ELF::ELFDATA2LSB) + return ELFDebugObject::Create(std::move(Buf), std::move(Seg)); + if (Endian == ELF::ELFDATA2MSB) + return ELFDebugObject::Create(std::move(Buf), std::move(Seg)); + return nullptr; + } + return nullptr; +} + +Expected> +createDebugObjectFromBuffer(const Triple &TT, JITLinkContext &Ctx, + std::unique_ptr Buf) { + switch (TT.getObjectFormat()) { + case Triple::ELF: + return createDebugObjectELF(std::move(Buf), Ctx); + + default: + // TODO: When adding support for other formats, make a new file for each. + return nullptr; + } +} + +} // namespace jitlink +} // namespace llvm diff --git a/llvm/lib/ExecutionEngine/JITLink/JITLink.cpp b/llvm/lib/ExecutionEngine/JITLink/JITLink.cpp --- a/llvm/lib/ExecutionEngine/JITLink/JITLink.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/JITLink.cpp @@ -10,6 +10,7 @@ #include "llvm/ExecutionEngine/JITLink/JITLink.h" #include "llvm/BinaryFormat/Magic.h" +#include "llvm/ExecutionEngine/JITLink/DebugSupport.h" #include "llvm/ExecutionEngine/JITLink/ELF.h" #include "llvm/ExecutionEngine/JITLink/MachO.h" #include "llvm/Support/Format.h" @@ -316,6 +317,11 @@ return Error::success(); } +Expected> +JITLinkContext::createDebugObject(LinkGraph &G) { + return nullptr; +} + Error markAllSymbolsLive(LinkGraph &G) { for (auto *Sym : G.defined_symbols()) Sym->setLive(true); diff --git a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt --- a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt +++ b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt @@ -2,6 +2,7 @@ CompileOnDemandLayer.cpp CompileUtils.cpp Core.cpp + DebugObjectManagerPlugin.cpp DebugUtils.cpp ExecutionUtils.cpp IndirectionUtils.cpp @@ -22,6 +23,7 @@ SpeculateAnalyses.cpp TargetProcessControl.cpp ThreadSafeModule.cpp + TPCDebugObjectRegistrar.cpp TPCDynamicLibrarySearchGenerator.cpp TPCEHFrameRegistrar.cpp TPCIndirectionUtils.cpp diff --git a/llvm/lib/ExecutionEngine/Orc/DebugObjectManagerPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/DebugObjectManagerPlugin.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/ExecutionEngine/Orc/DebugObjectManagerPlugin.cpp @@ -0,0 +1,153 @@ +//===---- 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/Support/Memory.h" + +#define DEBUG_TYPE "orc" + +using namespace llvm::jitlink; + +namespace llvm { +namespace orc { + +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; +} + +void DebugObjectManagerPlugin::notifyMaterializing( + MaterializationResponsibility &MR, LinkGraph &G, JITLinkContext &Ctx) { + std::lock_guard Lock(PendingObjsLock); + + // We should never have more than one pending debug object. + ResourceKey Key = getResourceKey(MR); + auto It = PendingObjs.find(Key); + if (It != PendingObjs.end()) { + ES.reportError(make_error( + formatv("Materializing new LinkGraph '{0}' for pending resource: {1}", + G.getName(), Key), + inconvertibleErrorCode())); + return; + } + + Expected> DebugObj = Ctx.createDebugObject(G); + if (!DebugObj) { + ES.reportError(DebugObj.takeError()); + return; + } + + // Not all link artifacts allow debugging. + if (*DebugObj) + PendingObjs[Key] = std::move(*DebugObj); +} + +void DebugObjectManagerPlugin::modifyPassConfig( + MaterializationResponsibility &MR, const Triple &TT, + PassConfiguration &PassConfig) { + // Not all link artifacts have associated debug objects. + DebugObject *DebugObj = nullptr; + { + std::lock_guard Lock(PendingObjsLock); + auto It = PendingObjs.find(getResourceKey(MR)); + if (It == PendingObjs.end()) + return; + DebugObj = It->second.get(); + } + DebugObj->modifyPassConfig(TT, PassConfig); +} + +void DebugObjectManagerPlugin::notifyLoaded(MaterializationResponsibility &MR) { + ResourceKey Key = getResourceKey(MR); + DebugObject *UnownedDebugObj = nullptr; + { + std::lock_guard Lock(PendingObjsLock); + auto It = PendingObjs.find(Key); + if (It != PendingObjs.end()) { + UnownedDebugObj = It->second.release(); + PendingObjs.erase(It); + } + } + + // We released ownership of DebugObj, 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 TargetMem) { + std::unique_ptr ReownedDebugObj(UnownedDebugObj); + if (!TargetMem) { + ES.reportError(TargetMem.takeError()); + return; + } + if (Error Err = Target->registerDebugObject(*TargetMem)) { + ES.reportError(std::move(Err)); + return; + } + + std::lock_guard Lock(RegisteredObjsLock); + RegisteredObjs[Key].push_back(std::move(ReownedDebugObj)); + }); +} + +Error DebugObjectManagerPlugin::notifyFailed( + MaterializationResponsibility &MR) { + std::lock_guard Lock(PendingObjsLock); + PendingObjs.erase(getResourceKey(MR)); + return Error::success(); +} + +void DebugObjectManagerPlugin::notifyTransferringResources(ResourceKey DstKey, + ResourceKey SrcKey) { + { + std::lock_guard Lock(RegisteredObjsLock); + auto SrcIt = RegisteredObjs.find(SrcKey); + if (SrcIt != RegisteredObjs.end()) { + for (std::unique_ptr &Alloc : SrcIt->second) + RegisteredObjs[DstKey].push_back(std::move(Alloc)); + RegisteredObjs.erase(SrcIt); + } + } + { + std::lock_guard Lock(PendingObjsLock); + auto SrcIt = PendingObjs.find(SrcKey); + if (SrcIt != PendingObjs.end()) { + if (PendingObjs.count(DstKey)) { + ES.reportError(make_error( + formatv( + "Destination '{0}' for transferring pending debug object has " + "a pending resource already: {1}", + DstKey, SrcKey), + inconvertibleErrorCode())); + } else { + PendingObjs[DstKey] = std::move(SrcIt->second); + PendingObjs.erase(SrcIt); + } + } + } +} + +Error DebugObjectManagerPlugin::notifyRemovingResources(ResourceKey K) { + { + std::lock_guard Lock(RegisteredObjsLock); + RegisteredObjs.erase(K); + // TODO: Implement unregister notifications. + } + std::lock_guard Lock(PendingObjsLock); + PendingObjs.erase(K); + + return Error::success(); +} + +} // namespace orc +} // namespace llvm diff --git a/llvm/lib/ExecutionEngine/Orc/ObjectLinkingLayer.cpp b/llvm/lib/ExecutionEngine/Orc/ObjectLinkingLayer.cpp --- a/llvm/lib/ExecutionEngine/Orc/ObjectLinkingLayer.cpp +++ b/llvm/lib/ExecutionEngine/Orc/ObjectLinkingLayer.cpp @@ -9,7 +9,10 @@ #include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" #include "llvm/ADT/Optional.h" +#include "llvm/ExecutionEngine/JITLink/DebugSupport.h" #include "llvm/ExecutionEngine/JITLink/EHFrameSupport.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/MemoryBuffer.h" #include @@ -40,6 +43,11 @@ JITLinkMemoryManager &getMemoryManager() override { return Layer.MemMgr; } + void notifyMaterializing(LinkGraph &G) override { + for (auto &P : Layer.Plugins) + P->notifyMaterializing(*MR, G, *this); + } + void notifyFailed(Error Err) override { for (auto &P : Layer.Plugins) Err = joinErrors(std::move(Err), P->notifyFailed(*MR)); @@ -450,6 +458,20 @@ } } + Expected> + createDebugObject(LinkGraph &G) override { + StringRef Obj = ObjBuffer->getBuffer(); + + std::unique_ptr DebugObj = + WritableMemoryBuffer::getNewUninitMemBuffer(Obj.size(), G.getName()); + if (DebugObj == nullptr) + return errorCodeToError(make_error_code(errc::not_enough_memory)); + + memcpy(DebugObj->getBufferStart(), Obj.data(), Obj.size()); + return jitlink::createDebugObjectFromBuffer(G.getTargetTriple(), *this, + std::move(DebugObj)); + } + ObjectLinkingLayer &Layer; std::unique_ptr MR; std::unique_ptr ObjBuffer; @@ -479,19 +501,24 @@ void ObjectLinkingLayer::emit(std::unique_ptr R, std::unique_ptr O) { assert(O && "Object must not be null"); - auto ObjBuffer = O->getMemBufferRef(); + MemoryBufferRef ObjBuffer = O->getMemBufferRef(); + auto Ctx = std::make_unique( *this, std::move(R), std::move(O)); - if (auto G = createLinkGraphFromObject(std::move(ObjBuffer))) + if (auto G = createLinkGraphFromObject(ObjBuffer)) { + Ctx->notifyMaterializing(**G); link(std::move(*G), std::move(Ctx)); - else + } else { Ctx->notifyFailed(G.takeError()); + } } void ObjectLinkingLayer::emit(std::unique_ptr R, std::unique_ptr G) { - link(std::move(G), std::make_unique( - *this, std::move(R), nullptr)); + auto Ctx = std::make_unique( + *this, std::move(R), nullptr); + Ctx->notifyMaterializing(*G); + link(std::move(G), std::move(Ctx)); } void ObjectLinkingLayer::modifyPassConfig(MaterializationResponsibility &MR, diff --git a/llvm/lib/ExecutionEngine/Orc/TPCDebugObjectRegistrar.cpp b/llvm/lib/ExecutionEngine/Orc/TPCDebugObjectRegistrar.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/ExecutionEngine/Orc/TPCDebugObjectRegistrar.cpp @@ -0,0 +1,62 @@ +//===----- TPCDebugObjectRegistrar.cpp - TPC-based debug registration -----===// +// +// 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/TPCDebugObjectRegistrar.h" + +#include "llvm/ExecutionEngine/Orc/Core.h" +#include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h" +#include "llvm/Support/BinaryStreamWriter.h" + +namespace llvm { +namespace orc { + +// Counterpart of readDebugObjectInfo() function in TargetProcess/JITLoaderGDB.h +static std::vector +writeDebugObjectInfo(sys::MemoryBlock TargetMemBlock) { + auto DebugObjAddr = pointerToJITTargetAddress(TargetMemBlock.base()); + uint64_t DebugObjSize = TargetMemBlock.allocatedSize(); + + std::vector ArgBuffer; + ArgBuffer.resize(sizeof(decltype(DebugObjAddr)) + + sizeof(decltype(DebugObjSize))); + + BinaryStreamWriter ArgWriter(ArgBuffer, support::endianness::big); + cantFail(ArgWriter.writeInteger(DebugObjAddr)); + cantFail(ArgWriter.writeInteger(DebugObjSize)); + + return ArgBuffer; +} + +Expected> +createJITLoaderGDBRegistrar(TargetProcessControl &TPC) { + auto ProcessHandle = TPC.loadDylib(nullptr); + if (!ProcessHandle) + return ProcessHandle.takeError(); + + SymbolStringPtr RegisterFn = + TPC.getTargetTriple().isOSBinFormatMachO() + ? TPC.intern("_llvm_orc_registerJITLoaderGDBWrapper") + : TPC.intern("llvm_orc_registerJITLoaderGDBWrapper"); + + SymbolLookupSet RegistrationSymbols; + RegistrationSymbols.add(RegisterFn); + + auto Result = TPC.lookupSymbols({{*ProcessHandle, RegistrationSymbols}}); + if (!Result) + return Result.takeError(); + + assert(Result->size() == 1 && "Unexpected number of dylibs in result"); + assert((*Result)[0].size() == 1 && + "Unexpected number of addresses in result"); + + return std::make_unique(TPC, (*Result)[0][0], + &writeDebugObjectInfo); +} + +} // namespace orc +} // namespace llvm diff --git a/llvm/lib/ExecutionEngine/Orc/TargetProcess/CMakeLists.txt b/llvm/lib/ExecutionEngine/Orc/TargetProcess/CMakeLists.txt --- a/llvm/lib/ExecutionEngine/Orc/TargetProcess/CMakeLists.txt +++ b/llvm/lib/ExecutionEngine/Orc/TargetProcess/CMakeLists.txt @@ -1,4 +1,5 @@ add_llvm_component_library(LLVMOrcTargetProcess + JITLoaderGDB.cpp RegisterEHFrames.cpp TargetExecutionUtils.cpp diff --git a/llvm/lib/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.cpp b/llvm/lib/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.cpp @@ -0,0 +1,112 @@ +//===- JITLoaderGDB.h - Register objects via GDB JIT interface -*- 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/TargetProcess/JITLoaderGDB.h" + +#include "llvm/ExecutionEngine/JITSymbol.h" +#include "llvm/Support/BinaryStreamReader.h" +#include "llvm/Support/ManagedStatic.h" + +#include +#include +#include + +#define DEBUG_TYPE "orc" + +// First version as landed in August 2009 +static constexpr uint32_t JitDescriptorVersion = 1; + +// Keep in sync with gdb/gdb/jit.h +extern "C" { + +typedef enum { + JIT_NOACTION = 0, + JIT_REGISTER_FN, + JIT_UNREGISTER_FN +} jit_actions_t; + +struct jit_code_entry { + struct jit_code_entry *next_entry; + struct jit_code_entry *prev_entry; + const char *symfile_addr; + uint64_t symfile_size; +}; + +struct jit_descriptor { + uint32_t version; + // This should be jit_actions_t, but we want to be specific about the + // bit-width. + uint32_t action_flag; + struct jit_code_entry *relevant_entry; + struct jit_code_entry *first_entry; +}; + +// We put information about the JITed function in this global, which the +// debugger reads. Make sure to specify the version statically, because the +// debugger checks the version before we can set it during runtime. +struct jit_descriptor __jit_debug_descriptor = {JitDescriptorVersion, 0, + nullptr, nullptr}; + +// Debuggers that implement the GDB JIT interface put a special breakpoint in +// this function. +LLVM_ATTRIBUTE_NOINLINE void __jit_debug_register_code() { + // The noinline and the asm prevent calls to this function from being + // optimized out. +#if !defined(_MSC_VER) + asm volatile("" ::: "memory"); +#endif +} +} + +using namespace llvm; +using namespace llvm::orc; +using namespace llvm::orc::tpctypes; + +// Serialize rendezvous with the debugger as well as access to shared data. +ManagedStatic JITDebugLock; + +static std::pair readDebugObjectInfo(uint8_t *ArgData, + uint64_t ArgSize) { + BinaryStreamReader ArgReader(ArrayRef(ArgData, ArgSize), + support::endianness::big); + uint64_t Addr, Size; + cantFail(ArgReader.readInteger(Addr)); + cantFail(ArgReader.readInteger(Size)); + + return std::make_pair(jitTargetAddressToPointer(Addr), Size); +} + +extern "C" CWrapperFunctionResult +llvm_orc_registerJITLoaderGDBWrapper(uint8_t *Data, uint64_t Size) { + if (Size != sizeof(uint64_t) + sizeof(uint64_t)) + return WrapperFunctionResult::from( + "Invalid arguments to llvm_orc_registerJITLoaderGDBWrapper") + .release(); + + jit_code_entry *E = new jit_code_entry; + std::tie(E->symfile_addr, E->symfile_size) = readDebugObjectInfo(Data, Size); + E->prev_entry = nullptr; + + std::lock_guard Lock(*JITDebugLock); + + // Insert this entry at the head of the list. + jit_code_entry *NextEntry = __jit_debug_descriptor.first_entry; + E->next_entry = NextEntry; + if (NextEntry) { + NextEntry->prev_entry = E; + } + + __jit_debug_descriptor.first_entry = E; + __jit_debug_descriptor.relevant_entry = E; + + // Run into the rendezvous breakpoint. + __jit_debug_descriptor.action_flag = JIT_REGISTER_FN; + __jit_debug_register_code(); + + return WrapperFunctionResult().release(); +} diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink-executor/llvm-jitlink-executor.cpp b/llvm/tools/llvm-jitlink/llvm-jitlink-executor/llvm-jitlink-executor.cpp --- a/llvm/tools/llvm-jitlink/llvm-jitlink-executor/llvm-jitlink-executor.cpp +++ b/llvm/tools/llvm-jitlink/llvm-jitlink-executor/llvm-jitlink-executor.cpp @@ -12,6 +12,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/ExecutionEngine/Orc/Shared/FDRawByteChannel.h" +#include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h" #include "llvm/ExecutionEngine/Orc/TargetProcess/OrcRPCTPCServer.h" #include "llvm/ExecutionEngine/Orc/TargetProcess/RegisterEHFrames.h" #include "llvm/Support/DynamicLibrary.h" @@ -33,7 +34,8 @@ LLVM_ATTRIBUTE_USED void linkComponents() { errs() << (void *)&llvm_orc_registerEHFrameSectionWrapper - << (void *)&llvm_orc_deregisterEHFrameSectionWrapper; + << (void *)&llvm_orc_deregisterEHFrameSectionWrapper + << (void *)&llvm_orc_registerJITLoaderGDBWrapper; } void printErrorAndExit(Twine ErrMsg) { diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp --- a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp +++ b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp @@ -15,9 +15,13 @@ #include "llvm-jitlink.h" #include "llvm/BinaryFormat/Magic.h" +#include "llvm/ExecutionEngine/Orc/DebugObjectManagerPlugin.h" #include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" +#include "llvm/ExecutionEngine/Orc/TPCDebugObjectRegistrar.h" #include "llvm/ExecutionEngine/Orc/TPCDynamicLibrarySearchGenerator.h" #include "llvm/ExecutionEngine/Orc/TPCEHFrameRegistrar.h" +#include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h" +#include "llvm/ExecutionEngine/Orc/TargetProcess/RegisterEHFrames.h" #include "llvm/MC/MCAsmInfo.h" #include "llvm/MC/MCContext.h" #include "llvm/MC/MCDisassembler/MCDisassembler.h" @@ -29,6 +33,7 @@ #include "llvm/Object/COFF.h" #include "llvm/Object/MachO.h" #include "llvm/Object/ObjectFile.h" +#include "llvm/Support/BinaryStreamWriter.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/InitLLVM.h" @@ -155,6 +160,12 @@ ExitOnError ExitOnErr; +LLVM_ATTRIBUTE_USED void linkComponents() { + errs() << (void *)&llvm_orc_registerEHFrameSectionWrapper + << (void *)&llvm_orc_deregisterEHFrameSectionWrapper + << (void *)&llvm_orc_registerJITLoaderGDBWrapper; +} + namespace llvm { static raw_ostream & @@ -842,6 +853,9 @@ ObjLayer.addPlugin(std::make_unique( ES, ExitOnErr(TPCEHFrameRegistrar::Create(*this->TPC)))); + ObjLayer.addPlugin(std::make_unique( + ES, ExitOnErr(createJITLoaderGDBRegistrar(*this->TPC)))); + ObjLayer.addPlugin(std::make_unique(*this)); // Process any harness files.