diff --git a/llvm/examples/OrcV2Examples/LLJITWithObjectLinkingLayerPlugin/LLJITWithObjectLinkingLayerPlugin.cpp b/llvm/examples/OrcV2Examples/LLJITWithObjectLinkingLayerPlugin/LLJITWithObjectLinkingLayerPlugin.cpp --- a/llvm/examples/OrcV2Examples/LLJITWithObjectLinkingLayerPlugin/LLJITWithObjectLinkingLayerPlugin.cpp +++ b/llvm/examples/OrcV2Examples/LLJITWithObjectLinkingLayerPlugin/LLJITWithObjectLinkingLayerPlugin.cpp @@ -63,7 +63,9 @@ }); } - void notifyLoaded(MaterializationResponsibility &MR) override { + void + notifyLoaded(MaterializationResponsibility &MR, + ObjectLinkingLayer::GetDebugObjCallback GetDebugObj) override { dbgs() << "Loading object defining " << MR.getSymbols() << "\n"; } diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/ELF_x86_64.h b/llvm/include/llvm/ExecutionEngine/JITLink/ELF_x86_64.h --- a/llvm/include/llvm/ExecutionEngine/JITLink/ELF_x86_64.h +++ b/llvm/include/llvm/ExecutionEngine/JITLink/ELF_x86_64.h @@ -52,6 +52,11 @@ Expected> createLinkGraphFromELFObject_x86_64(MemoryBufferRef ObjectBuffer); +/// ObjectBuffer refers to a WriteableMemoryBuffer +Error patchSectionLoadAddressesInELFObject_x86_64( + MutableArrayRef ObjBufferMem, + const jitlink::NameAddrMap &SectionsInTargetMemory); + /// jit-link the given object buffer, which must be a ELF x86-64 object file. void link_ELF_x86_64(std::unique_ptr G, std::unique_ptr Ctx); 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 @@ -262,6 +262,15 @@ std::vector Edges; }; +struct SegmentLayout { + using BlocksList = std::vector; + + BlocksList ContentBlocks; + BlocksList ZeroFillBlocks; +}; + +using SegmentLayoutMap = DenseMap; + /// Describes symbol linkage. This can be used to make resolve definition /// clashes. enum class Linkage : uint8_t { @@ -659,6 +668,12 @@ Block *Last = nullptr; }; +// TODO: If we can move the debug-object callback to JITLinkContext, +// we get rid of this too. +using NameAddrMap = std::map; +using PrepareObjectForDebugFunction = + std::function, const NameAddrMap &)>; + class LinkGraph { private: using SectionList = std::vector>; @@ -787,9 +802,11 @@ getSectionConstBlocks>; LinkGraph(std::string Name, const Triple &TT, unsigned PointerSize, - support::endianness Endianness) + support::endianness Endianness, + PrepareObjectForDebugFunction PrepareDebugObjFunc = nullptr) : Name(std::move(Name)), TT(TT), PointerSize(PointerSize), - Endianness(Endianness) {} + Endianness(Endianness), + PrepareDebugObjFunc(std::move(PrepareDebugObjFunc)) {} /// Returns the name of this graph (usually the name of the original /// underlying MemoryBuffer). @@ -804,6 +821,19 @@ /// Returns the endianness of content in this graph. support::endianness getEndianness() const { return Endianness; } + bool canPrepareObjectForDebug() const { + return static_cast(PrepareDebugObjFunc); + } + + // TODO: It would be nice to move this to JITLinkContext, but it's + // instantiated in ObjectLinkingLayer::emit() and cannot reach the linker + // backends that provide the PrepareDebugObjFunc function. + Error prepareObjectForDebug(MutableArrayRef ObjBuffer, + const NameAddrMap &SectionsInTargetMemory) const { + assert(PrepareDebugObjFunc && "If we cannot transform we should be here"); + return PrepareDebugObjFunc(ObjBuffer, SectionsInTargetMemory); + }; + /// Allocate a copy of the given string using the LinkGraph's allocator. /// This can be useful when renaming symbols or adding new content to the /// graph. @@ -1056,6 +1086,8 @@ SectionList Sections; ExternalSymbolSet ExternalSymbols; ExternalSymbolSet AbsoluteSymbols; + + PrepareObjectForDebugFunction PrepareDebugObjFunc; }; /// Enables easy lookup of blocks by addresses. @@ -1333,7 +1365,8 @@ /// If the client detects an error in the LinkGraph state (e.g. unexpected or /// missing symbols) they may return an error here. The error will be /// propagated to notifyFailed and the linker will bail out. - virtual Error notifyResolved(LinkGraph &G) = 0; + virtual Error notifyResolved(LinkGraph &G, + const SegmentLayoutMap &Layout) = 0; /// Called by JITLink to notify the context that the object has been /// finalized (i.e. emitted to memory and memory permissions set). If all of 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 @@ -56,6 +56,9 @@ friend class ObjectLinkingLayerJITLinkContext; public: + using GetDebugObjCallback = + std::function>()>; + /// Plugin instances can be added to the ObjectLinkingLayer to receive /// callbacks when code is loaded or emitted, and when JITLink is being /// configured. @@ -69,7 +72,8 @@ const Triple &TT, jitlink::PassConfiguration &Config) {} - virtual void notifyLoaded(MaterializationResponsibility &MR) {} + virtual void notifyLoaded(MaterializationResponsibility &MR, + GetDebugObjCallback GetDebugObj) {} virtual Error notifyEmitted(MaterializationResponsibility &MR) { return Error::success(); } @@ -163,7 +167,8 @@ void modifyPassConfig(MaterializationResponsibility &MR, const Triple &TT, jitlink::PassConfiguration &PassConfig); - void notifyLoaded(MaterializationResponsibility &MR); + void notifyLoaded(MaterializationResponsibility &MR, + GetDebugObjCallback GetDebugObj); Error notifyEmitted(MaterializationResponsibility &MR, AllocPtr Alloc); Error handleRemoveResources(ResourceKey K) override; diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF_x86_64.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF_x86_64.cpp --- a/llvm/lib/ExecutionEngine/JITLink/ELF_x86_64.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/ELF_x86_64.cpp @@ -655,9 +655,9 @@ public: ELFLinkGraphBuilder_x86_64(StringRef FileName, const object::ELFFile &Obj) - : G(std::make_unique(FileName.str(), - Triple("x86_64-unknown-linux"), - getPointerSize(Obj), getEndianness(Obj))), + : G(std::make_unique( + FileName.str(), Triple("x86_64-unknown-linux"), getPointerSize(Obj), + getEndianness(Obj), &patchSectionLoadAddressesInELFObject_x86_64)), Obj(Obj) {} Expected> buildGraph() { @@ -762,6 +762,55 @@ .buildGraph(); } +LLVM_ELF_IMPORT_TYPES_ELFT(object::ELF64LE) + +Error patchSectionLoadAddressesInELFObject_x86_64( + MutableArrayRef ObjBufferMem, + const jitlink::NameAddrMap &SectionsInTargetMemory) { + + MemoryBufferRef ObjectBuffer( + StringRef(ObjBufferMem.data(), ObjBufferMem.size()), ""); + auto ELFObj = object::ObjectFile::createELFObjectFile(ObjectBuffer); + if (!ELFObj) + return ELFObj.takeError(); + + auto &ELFObjFile = cast>(**ELFObj); + const object::ELFFile &Obj = ELFObjFile.getELFFile(); + + auto Sections = Obj.sections(); + if (!Sections) + return Sections.takeError(); + + for (auto &SecRef : *Sections) { + assert(SecRef.sh_addr == 0 && "Section load-address set already"); + StringRef Name = cantFail(Obj.getSectionName(SecRef)); + auto It = SectionsInTargetMemory.find(Name); + if (It != SectionsInTargetMemory.end()) { + // Overwrite the load-address location in ObjBufferMem. + Elf_Shdr *shdr = + const_cast(reinterpret_cast(&SecRef)); + shdr->sh_addr = static_cast(It->second); + } + } + + LLVM_DEBUG({ + dbgs() << formatv("Section load-addresses in debug object {0:x}\n", + pointerToJITTargetAddress(ObjBufferMem.data())); + for (auto &SecRef : *Sections) { + StringRef Name = cantFail(Obj.getSectionName(SecRef)); + if (!Name.empty()) { + if (auto Addr = static_cast(SecRef.sh_addr)) { + dbgs() << formatv(" {0:x16} {1}\n", Addr, Name); + } else { + dbgs() << formatv(" {0}\n", Name); + } + } + } + }); + + return Error::success(); +} + void link_ELF_x86_64(std::unique_ptr G, std::unique_ptr Ctx) { PassConfiguration Config; diff --git a/llvm/lib/ExecutionEngine/JITLink/JITLinkGeneric.h b/llvm/lib/ExecutionEngine/JITLink/JITLinkGeneric.h --- a/llvm/lib/ExecutionEngine/JITLink/JITLinkGeneric.h +++ b/llvm/lib/ExecutionEngine/JITLink/JITLinkGeneric.h @@ -42,15 +42,6 @@ virtual ~JITLinkerBase(); protected: - struct SegmentLayout { - using BlocksList = std::vector; - - BlocksList ContentBlocks; - BlocksList ZeroFillBlocks; - }; - - using SegmentLayoutMap = DenseMap; - // Phase 1: // 1.1: Run pre-prune passes // 1.2: Prune graph diff --git a/llvm/lib/ExecutionEngine/JITLink/JITLinkGeneric.cpp b/llvm/lib/ExecutionEngine/JITLink/JITLinkGeneric.cpp --- a/llvm/lib/ExecutionEngine/JITLink/JITLinkGeneric.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/JITLinkGeneric.cpp @@ -69,7 +69,7 @@ LLVM_DEBUG( { dbgs() << "Resolving symbols defined in " << G->getName() << "\n"; }); - if (auto Err = Ctx->notifyResolved(*G)) + if (auto Err = Ctx->notifyResolved(*G, Layout)) return Ctx->notifyFailed(std::move(Err)); auto ExternalSymbols = getExternalSymbolNames(); @@ -174,8 +174,7 @@ return Error::success(); } -JITLinkerBase::SegmentLayoutMap JITLinkerBase::layOutBlocks() { - +SegmentLayoutMap JITLinkerBase::layOutBlocks() { SegmentLayoutMap Layout; /// Partition blocks based on permissions and content vs. zero-fill. 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/JITLink/JITLink.h" +#include "llvm/Support/Process.h" #include @@ -96,7 +98,7 @@ }); } - Error notifyResolved(LinkGraph &G) override { + Error notifyResolved(LinkGraph &G, const SegmentLayoutMap &Layout) override { auto &ES = Layer.getExecutionSession(); SymbolFlagsMap ExtraSymbolsToClaim; @@ -190,7 +192,14 @@ if (auto Err = MR->notifyResolved(InternedResult)) return Err; - Layer.notifyLoaded(*MR); + ObjectLinkingLayer::GetDebugObjCallback GetDebugObjFunc = nullptr; + if (ObjBuffer && G.canPrepareObjectForDebug()) + GetDebugObjFunc = [&]() { + return transformDebugObj(*ObjBuffer, G, Layout); + }; + + Layer.notifyLoaded(*MR, GetDebugObjFunc); + return Error::success(); } @@ -450,9 +459,61 @@ } } + Expected> allocateDebugObj(StringRef ObjMem) { + // TODO: We probably want to allow multiple allocs per JITLinkContext. + if (DebugObjAlloc) + return make_error( + formatv("Failed to initialize debug object: JITLinkContext for has " + "an associated debug object already"), + inconvertibleErrorCode()); + + // TODO: This works, but what alignment requirements do we actually have? + unsigned Alignment = sys::Process::getPageSizeEstimate(); + unsigned ReadWrite = sys::Memory::MF_READ | sys::Memory::MF_WRITE; + + Expected> AllocOrErr = + Layer.MemMgr.allocate(&MR->getTargetJITDylib(), + {{ReadWrite, {Alignment, ObjMem.size(), 0}}}); + if (!AllocOrErr) + return AllocOrErr.takeError(); + DebugObjAlloc = std::move(*AllocOrErr); + + MutableArrayRef DebugObjMem = DebugObjAlloc->getWorkingMemory( + static_cast(ReadWrite)); + memcpy(DebugObjMem.data(), ObjMem.data(), ObjMem.size()); + + return DebugObjMem; + } + + Expected> + transformDebugObj(const MemoryBuffer &ObjBuffer, const LinkGraph &G, + const SegmentLayoutMap &Layout) { + + Expected> DebugObjMem = + allocateDebugObj(ObjBuffer.getBuffer()); + if (!DebugObjMem) + return DebugObjMem.takeError(); + + std::map SectionLoadAddresses; + for (auto &KV : Layout) { + for (Block *B : KV.second.ContentBlocks) + SectionLoadAddresses[B->getSection().getName()] = B->getAddress(); + for (Block *B : KV.second.ZeroFillBlocks) + SectionLoadAddresses[B->getSection().getName()] = B->getAddress(); + } + + if (Error Err = G.prepareObjectForDebug(*DebugObjMem, SectionLoadAddresses)) + return std::move(Err); + + StringRef Name = ObjBuffer.getBufferIdentifier(); + StringRef MemRange(DebugObjMem->data(), DebugObjMem->size()); + return MemoryBuffer::getMemBuffer(MemRange, Name, false); + } + ObjectLinkingLayer &Layer; std::unique_ptr MR; std::unique_ptr ObjBuffer; + std::unique_ptr DebugObjAlloc; DenseMap ExternalNamedSymbolDeps; DenseMap InternalNamedSymbolDeps; }; @@ -501,9 +562,10 @@ P->modifyPassConfig(MR, TT, PassConfig); } -void ObjectLinkingLayer::notifyLoaded(MaterializationResponsibility &MR) { +void ObjectLinkingLayer::notifyLoaded(MaterializationResponsibility &MR, + GetDebugObjCallback GetDebugObj) { for (auto &P : Plugins) - P->notifyLoaded(MR); + P->notifyLoaded(MR, GetDebugObj); } Error ObjectLinkingLayer::notifyEmitted(MaterializationResponsibility &MR, diff --git a/llvm/tools/llvm-jitlink/CMakeLists.txt b/llvm/tools/llvm-jitlink/CMakeLists.txt --- a/llvm/tools/llvm-jitlink/CMakeLists.txt +++ b/llvm/tools/llvm-jitlink/CMakeLists.txt @@ -22,6 +22,7 @@ llvm-jitlink.cpp llvm-jitlink-elf.cpp llvm-jitlink-macho.cpp + llvm-jitlink-gdb-loader.cpp ) export_executable_symbols(llvm-jitlink) diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink-gdb-loader.h b/llvm/tools/llvm-jitlink/llvm-jitlink-gdb-loader.h new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-jitlink/llvm-jitlink-gdb-loader.h @@ -0,0 +1,47 @@ +#ifndef LLVM_TOOLS_LLVM_JITLINK_JITLINKGDBLOADERPLUGIN_H +#define LLVM_TOOLS_LLVM_JITLINK_JITLINKGDBLOADERPLUGIN_H + +#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ExecutionEngine/Orc/Core.h" +#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Support/MemoryBuffer.h" + +#include + +namespace llvm { + +struct jit_code_entry; + +class JITLinkGDBLoaderPlugin : public orc::ObjectLinkingLayer::Plugin { + + struct RegisteredObjectInfo { + jit_code_entry *Entry; + std::unique_ptr ObjBuffer; + }; + + using RegisteredObjectsList = std::vector; + using GetDebugObjCallback = orc::ObjectLinkingLayer::GetDebugObjCallback; + +public: + void notifyLoaded(orc::MaterializationResponsibility &MR, + GetDebugObjCallback GetDebugObj) override; + + Error notifyFailed(orc::MaterializationResponsibility &MR) override { + return Error::success(); + } + Error notifyRemovingResources(orc::ResourceKey K) override { + return Error::success(); + } + void notifyTransferringResources(orc::ResourceKey DstKey, + orc::ResourceKey SrcKey) override {} + +private: + DenseMap RegisteredObjectsMap; +}; + +} // namespace llvm + +#endif // LLVM_TOOLS_LLVM_JITLINK_JITLINKGDBLOADERPLUGIN_H diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink-gdb-loader.cpp b/llvm/tools/llvm-jitlink/llvm-jitlink-gdb-loader.cpp new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-jitlink/llvm-jitlink-gdb-loader.cpp @@ -0,0 +1,103 @@ +#include "llvm-jitlink-gdb-loader.h" + +#include "llvm/Support/ManagedStatic.h" + +#include +#include + +using namespace llvm::object; +using namespace llvm::orc; + +namespace llvm { + +// This must be kept 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 = {1, 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 +} +} + +// Append an entry to the list in the JIT descriptor symbol and call the debug +// trap function. TODO: Implement removal. +void NotifyDebugger(jit_code_entry *JITCodeEntry) { + __jit_debug_descriptor.action_flag = JIT_REGISTER_FN; + + // Insert this entry at the head of the list. + JITCodeEntry->prev_entry = nullptr; + jit_code_entry *NextEntry = __jit_debug_descriptor.first_entry; + JITCodeEntry->next_entry = NextEntry; + if (NextEntry) { + NextEntry->prev_entry = JITCodeEntry; + } + __jit_debug_descriptor.first_entry = JITCodeEntry; + __jit_debug_descriptor.relevant_entry = JITCodeEntry; + __jit_debug_register_code(); +} + +// JIT registration modifies global variables. Serialize them. +ManagedStatic JITDebugLock; + +void JITLinkGDBLoaderPlugin::notifyLoaded(MaterializationResponsibility &MR, + GetDebugObjCallback GetDebugObj) { + // Not all object types support debugging (yet). + if (!GetDebugObj) + return; + + // Obtain an object buffer with patched load addresses for all sections. + Expected> DebugObj = GetDebugObj(); + if (!DebugObj) { + MR.getExecutionSession().reportError(DebugObj.takeError()); + return; + } + + StringRef MemRange = (**DebugObj).getBuffer(); + jit_code_entry *JITCodeEntry = + new jit_code_entry{nullptr, nullptr, MemRange.data(), MemRange.size()}; + + orc::ResourceKey Key; + if (auto Err = MR.withResourceKeyDo([&](orc::ResourceKey K) { Key = K; })) { + MR.getExecutionSession().reportError(std::move(Err)); + return; + } + + // Cover registry as well. A second mutex wouldn't pay off. + std::lock_guard Lock(*JITDebugLock); + RegisteredObjectsMap[Key].push_back({JITCodeEntry, std::move(*DebugObj)}); + NotifyDebugger(JITCodeEntry); +} + +} // namespace llvm 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 @@ -14,6 +14,8 @@ #include "llvm-jitlink.h" +#include "llvm-jitlink-gdb-loader.h" + #include "llvm/BinaryFormat/Magic.h" #include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" #include "llvm/ExecutionEngine/Orc/TPCDynamicLibrarySearchGenerator.h" @@ -843,6 +845,7 @@ ES, ExitOnErr(TPCEHFrameRegistrar::Create(*this->TPC)))); ObjLayer.addPlugin(std::make_unique(*this)); + ObjLayer.addPlugin(std::make_unique()); // Process any harness files. for (auto &HarnessFile : TestHarnesses) {