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,84 @@ +//===---- 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/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 "llvm/Support/MemoryBuffer.h" + +#include +#include +#include + +namespace llvm { +namespace orc { + +class DebugObject; + +/// 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); + ~DebugObjectManagerPlugin(); + + void notifyMaterializing(MaterializationResponsibility &MR, + jitlink::LinkGraph &G, jitlink::JITLinkContext &Ctx, + MemoryBufferRef InputObject) 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 @@ -45,6 +45,7 @@ namespace orc { +class DebugObjectProvider; class ObjectLinkingLayerJITLinkContext; /// An ObjectLayer implementation built on JITLink. @@ -72,6 +73,11 @@ const Triple &TT, jitlink::PassConfiguration &Config) {} + virtual void notifyMaterializing(MaterializationResponsibility &MR, + jitlink::LinkGraph &G, + jitlink::JITLinkContext &Ctx, + MemoryBufferRef InputObject) {} + 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/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,493 @@ +//===---- 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 + +#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 +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; + void dump(raw_ostream &OS, StringRef Name) override; + +private: + typename ELFT::Shdr *Header; + + bool isTextOrDataSection() const; +}; + +template +void ELFDebugObjectSection::setTargetMemoryRange(SectionRange Range) { + // Only patch load-addresses for executable and data sections. + if (isTextOrDataSection()) { + Header->sh_addr = static_cast(Range.getStart()); + } +} + +template +void ELFDebugObjectSection::dump(raw_ostream &OS, StringRef Name) { + if (auto Addr = static_cast(Header->sh_addr)) { + OS << formatv(" {0:x16} {1}\n", Addr, Name); + } else { + OS << formatv(" {0}\n", Name); + } +} + +template +bool ELFDebugObjectSection::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; +} + +static constexpr sys::Memory::ProtectionFlags ReadOnly = + static_cast(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 finalizeAsync(FinalizeContinuation OnFinalize); + + virtual void reportSectionTargetMemoryRange(StringRef Name, + SectionRange TargetMem) {} + virtual ~DebugObject() {} + +protected: + using Allocation = JITLinkMemoryManager::Allocation; + + virtual Expected> + finalizeWorkingMemory(JITLinkContext &Ctx) = 0; + +private: + JITLinkContext &Ctx; + std::set Reqs; + std::unique_ptr 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(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> Create(MemoryBufferRef Buffer, + JITLinkContext &Ctx); + + void reportSectionTargetMemoryRange(StringRef Name, + SectionRange TargetMem) override; + +protected: + Expected> + finalizeWorkingMemory(JITLinkContext &Ctx) override; + + Error recordSection(StringRef Name, + std::unique_ptr Section); + DebugObjectSection *getSection(StringRef Name); + +private: + template + static Expected> + CreateArchType(MemoryBufferRef Buffer, JITLinkContext &Ctx); + + static Expected> + CopyBuffer(MemoryBufferRef Buffer); + + ELFDebugObject(std::unique_ptr Buffer, + JITLinkContext &Ctx) + : DebugObject(Ctx), Buffer(std::move(Buffer)) { + set(Requirement::ReportFinalSectionLoadAddresses); + } + + std::unique_ptr Buffer; + StringMap> Sections; +}; + +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; +} + +Expected> +ELFDebugObject::CopyBuffer(MemoryBufferRef Buffer) { + size_t Size = Buffer.getBufferSize(); + StringRef Name = Buffer.getBufferIdentifier(); + auto Copy = WritableMemoryBuffer::getNewUninitMemBuffer(Size, Name); + if (!Copy) + return errorCodeToError(make_error_code(errc::not_enough_memory)); + + memcpy(Copy->getBufferStart(), Buffer.getBufferStart(), Size); + return std::move(Copy); +} + +template +Expected> +ELFDebugObject::CreateArchType(MemoryBufferRef Buffer, JITLinkContext &Ctx) { + using SectionHeader = typename ELFT::Shdr; + + Expected> ObjRef = ELFFile::create(Buffer.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> Sections = ObjRef->sections(); + if (!Sections) + return Sections.takeError(); + + Expected> Copy = CopyBuffer(Buffer); + if (!Copy) + return Copy.takeError(); + + std::unique_ptr DebugObj( + new ELFDebugObject(std::move(*Copy), Ctx)); + + 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); + + auto Wrapped = std::make_unique>(&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> +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(Buffer, Ctx); + if (Endian == ELF::ELFDATA2MSB) + return CreateArchType(Buffer, Ctx); + return nullptr; + } + if (Class == ELF::ELFCLASS64) { + if (Endian == ELF::ELFDATA2LSB) + return CreateArchType(Buffer, Ctx); + if (Endian == ELF::ELFDATA2MSB) + return CreateArchType(Buffer, Ctx); + return nullptr; + } + return nullptr; +} + +Expected> +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. + auto AllocOrErr = MemMgr.allocate(JD, {{ReadOnly, {Alignment, Size, 0}}}); + 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 Alloc = std::move(*AllocOrErr); + MutableArrayRef 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); +} + +Error ELFDebugObject::recordSection( + StringRef Name, std::unique_ptr Section) { + auto ItInserted = Sections.try_emplace(Name, std::move(Section)); + if (!ItInserted.second) + return make_error("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> +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 Target) + : ES(ES), Target(std::move(Target)) {} + +DebugObjectManagerPlugin::~DebugObjectManagerPlugin() {} + +void DebugObjectManagerPlugin::notifyMaterializing( + MaterializationResponsibility &MR, LinkGraph &G, JITLinkContext &Ctx, + MemoryBufferRef ObjBuffer) { + 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; + } + + if (auto DebugObj = createDebugObjectFromBuffer(G, Ctx, ObjBuffer)) { + // Not all link artifacts allow debugging. + if (*DebugObj) + 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. + DebugObject *DebugObj = nullptr; + { + std::lock_guard Lock(PendingObjsLock); + auto It = PendingObjs.find(getResourceKey(MR)); + if (It == PendingObjs.end()) + return; + DebugObj = It->second.get(); + } + + 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(); + }); + } +} + +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 @@ -10,6 +10,8 @@ #include "llvm/ADT/Optional.h" #include "llvm/ExecutionEngine/JITLink/EHFrameSupport.h" +#include "llvm/ExecutionEngine/Orc/DebugObjectManagerPlugin.h" +#include "llvm/Support/MemoryBuffer.h" #include @@ -40,6 +42,11 @@ JITLinkMemoryManager &getMemoryManager() override { return Layer.MemMgr; } + void notifyMaterializing(LinkGraph &G) { + for (auto &P : Layer.Plugins) + P->notifyMaterializing(*MR, G, *this, ObjBuffer->getMemBufferRef()); + } + void notifyFailed(Error Err) override { for (auto &P : Layer.Plugins) Err = joinErrors(std::move(Err), P->notifyFailed(*MR)); @@ -483,19 +490,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.