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 @@ -1315,6 +1315,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(const jitlink::LinkGraph &G) = 0; + /// Notify this context that linking failed. /// Called by JITLink if linking cannot be completed. virtual void notifyFailed(Error Err) = 0; @@ -1360,6 +1363,11 @@ /// The default version performs no modification. virtual Error modifyPassConfig(const Triple &TT, PassConfiguration &Config); + virtual Expected> + allocateDebugObj(sys::Memory::ProtectionFlags Segment) const { + return nullptr; + } + private: const JITLinkDylib *JD = nullptr; }; 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, + const jitlink::LinkGraph &G, + const jitlink::JITLinkContext &Ctx) {} + virtual void notifyLoaded(MaterializationResponsibility &MR) {} virtual Error notifyEmitted(MaterializationResponsibility &MR) { return Error::success(); 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 @@ -327,15 +327,6 @@ if (!Name) return Name.takeError(); - // Skip Dwarf sections. - if (isDwarfSection(*Name)) { - LLVM_DEBUG({ - dbgs() << *Name - << " is a debug section: No graph section will be created.\n"; - }); - continue; - } - sys::Memory::ProtectionFlags Prot; if (SecRef.sh_flags & ELF::SHF_EXECINSTR) { Prot = static_cast(sys::Memory::MF_READ | 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 @@ -40,6 +42,11 @@ JITLinkMemoryManager &getMemoryManager() override { return Layer.MemMgr; } + void notifyMaterializing(const jitlink::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 +457,25 @@ } } + Expected> + allocateDebugObj(sys::Memory::ProtectionFlags Segment) const override { + // TODO: This works, but what actual alignment requirements do we have? + unsigned Alignment = sys::Process::getPageSizeEstimate(); + + StringRef ObjMem = ObjBuffer->getBuffer(); + Expected> AllocOrErr = + Layer.MemMgr.allocate(&MR->getTargetJITDylib(), + {{Segment, {Alignment, ObjMem.size(), 0}}}); + if (!AllocOrErr) + return AllocOrErr.takeError(); + + JITLinkMemoryManager::Allocation &DebugObjAlloc = **AllocOrErr; + MutableArrayRef DebugObjMem = DebugObjAlloc.getWorkingMemory(Segment); + memcpy(DebugObjMem.data(), ObjMem.data(), ObjMem.size()); + + return std::move(*AllocOrErr); + } + ObjectLinkingLayer &Layer; std::unique_ptr MR; std::unique_ptr ObjBuffer; @@ -479,19 +505,26 @@ void ObjectLinkingLayer::emit(std::unique_ptr R, std::unique_ptr O) { assert(O && "Object must not be null"); - auto ObjBuffer = O->getMemBufferRef(); - auto Ctx = std::make_unique( - *this, std::move(R), std::move(O)); - if (auto G = createLinkGraphFromObject(std::move(ObjBuffer))) + MemoryBufferRef ObjBuffer = O->getMemBufferRef(); + + std::unique_ptr Ctx = + std::make_unique(*this, std::move(R), + std::move(O)); + 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)); + std::unique_ptr 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/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,62 @@ +#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/ADT/StringRef.h" +#include "llvm/ExecutionEngine/JITLink/JITLink.h" +#include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h" +#include "llvm/ExecutionEngine/JITSymbol.h" +#include "llvm/ExecutionEngine/Orc/Core.h" +#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" +#include "llvm/Object/ELFObjectFile.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/MemoryBuffer.h" + +#include +#include + +struct jit_code_entry; + +namespace llvm { +namespace orc { + +class JITLoaderGDBPlugin : public ObjectLinkingLayer::Plugin { + using DebugAllocation = + std::unique_ptr; + + static constexpr sys::Memory::ProtectionFlags ReadOnlySegment = + static_cast(sys::Memory::MF_READ); + +public: + JITLoaderGDBPlugin(ExecutionSession &ES) : ES(ES) {} + + void notifyMaterializing(MaterializationResponsibility &MR, + const jitlink::LinkGraph &G, + const 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; + std::mutex DebugAllocLock; + DenseMap> DebugAllocs; + DenseMap PendingDebugAllocs; + + static ResourceKey getResourceKey(MaterializationResponsibility &MR); +}; + +} // namespace orc +} // 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,366 @@ +#include "llvm-jitlink-gdb-loader.h" + +#include "llvm/Support/ManagedStatic.h" + +#include +#include + +#define DEBUG_TYPE "llvm_jitlink" + +using namespace llvm::object; + +// 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 +} +} + +namespace llvm { +namespace orc { + +// Serialize rendezvous as well as access to shared data globally. +ManagedStatic JITDebugLock; + +// Append an entry to the list in the JIT descriptor symbol and call the debug +// trap function. TODO: Implement removal. +void AppendJITCodeEntry(const char *DebugObj, uint64_t DebugObjSize) { + std::lock_guard Lock(*JITDebugLock); + + jit_code_entry *JITCodeEntry = new jit_code_entry; + JITCodeEntry->symfile_addr = DebugObj; + JITCodeEntry->symfile_size = DebugObjSize; + + // 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; +} + +void NotifyDebugger(jit_actions_t Action) { + std::lock_guard Lock(*JITDebugLock); + __jit_debug_descriptor.action_flag = Action; + __jit_debug_register_code(); +} + +class LoadedObjectView { +public: + virtual ~LoadedObjectView(){}; + virtual void + submitSectionLoadAddress(StringRef Name, + JITTargetAddress AddrInTargetMemory) = 0; + virtual void dumpSectionLoadAddresses(raw_ostream &OS) = 0; +}; + +template class ELFLoadedObjectView : public LoadedObjectView { + using SectionHeader = typename ELFT::Shdr; + + static bool isSectionTextOrData(unsigned Type, unsigned Flags) { + switch (Type) { + case ELF::SHT_PROGBITS: + case ELF::SHT_X86_64_UNWIND: + return Flags & (ELF::SHF_EXECINSTR | ELF::SHF_ALLOC); + } + return false; + } + +public: + static Expected>> + Create(MutableArrayRef Buffer) { + Expected> ObjFile = + object::ObjectFile::createELFObjectFile(MemoryBufferRef( + StringRef(Buffer.data(), Buffer.size()), "ELFLoadedObjectView")); + if (!ObjFile) + return ObjFile.takeError(); + + auto &ELFObjFile = cast>(**ObjFile); + const ELFFile &ELFFile = ELFObjFile.getELFFile(); + + Expected> Sections = ELFFile.sections(); + if (!Sections) + return Sections.takeError(); + + auto ObjView = std::make_unique>(); + for (const SectionHeader &Section : *Sections) { + Expected Name = ELFFile.getSectionName(Section); + if (!Name) + consumeError(Name.takeError()); + if (!Name || Name->empty()) + continue; + if (!isSectionTextOrData(Section.sh_type, Section.sh_flags)) + continue; + + assert((const char *)&Section >= Buffer.data() && + (const char *)&Section < Buffer.data() + Buffer.size() && + "Section header outside memory range of given buffer"); + ObjView->SectionHeaders[*Name] = const_cast(&Section); + } + + return std::move(ObjView); + } + + void submitSectionLoadAddress(StringRef Name, + JITTargetAddress AddrInTargetMemory) override { + auto It = SectionHeaders.find(Name); + if (It != SectionHeaders.end()) { + SectionHeader *Section = It->second; + Section->sh_addr = static_cast(AddrInTargetMemory); + } + } + + void dumpSectionLoadAddresses(raw_ostream &OS) override { + for (const auto &KV : SectionHeaders) { + if (auto Addr = static_cast(KV.second->sh_addr)) { + OS << formatv(" {0:x16} {1}\n", Addr, KV.first()); + } else { + OS << formatv(" {0}\n", KV.first()); + } + } + } + +private: + StringMap SectionHeaders; +}; + +static Expected> +createLoadedObjectELF(unsigned char Class, unsigned char Endian, + MutableArrayRef Buffer) { + if (Class == ELF::ELFCLASS32) { + if (Endian == ELF::ELFDATA2LSB) + return ELFLoadedObjectView::Create(Buffer); + if (Endian == ELF::ELFDATA2MSB) + return ELFLoadedObjectView::Create(Buffer); + return nullptr; + } + if (Class == ELF::ELFCLASS64) { + if (Endian == ELF::ELFDATA2LSB) + return ELFLoadedObjectView::Create(Buffer); + if (Endian == ELF::ELFDATA2MSB) + return ELFLoadedObjectView::Create(Buffer); + return nullptr; + } + return nullptr; +} + +static Expected> +createLoadedObject(const Triple &TT, MutableArrayRef Buffer) { + switch (TT.getObjectFormat()) { + case Triple::ELF: { + auto Ident = getElfArchType(StringRef(Buffer.data(), Buffer.size())); + return createLoadedObjectELF(Ident.first, Ident.second, Buffer); + } + default: + // TODO: Add debug support for other formats. + return nullptr; + } +} + +ResourceKey +JITLoaderGDBPlugin::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 value"); + return Key; +} + +void JITLoaderGDBPlugin::notifyMaterializing( + MaterializationResponsibility &MR, const jitlink::LinkGraph &G, + const jitlink::JITLinkContext &Ctx) { + + std::lock_guard Lock(DebugAllocLock); + + // We should never have more than one pending debug object. + ResourceKey Key = getResourceKey(MR); + auto It = PendingDebugAllocs.find(Key); + if (It != PendingDebugAllocs.end()) { + ES.reportError(make_error( + formatv("Materializing new LinkGraph '{0}' for pending resource: {1}", + G.getName(), Key), + inconvertibleErrorCode())); + return; + } + + Expected Alloc = Ctx.allocateDebugObj(ReadOnlySegment); + if (!Alloc) { + ES.reportError(Alloc.takeError()); + return; + } + + // Not all linker artifacts support debugging. + if (*Alloc) + PendingDebugAllocs[Key] = std::move(*Alloc); +} + +void JITLoaderGDBPlugin::modifyPassConfig( + MaterializationResponsibility &MR, const Triple &TT, + jitlink::PassConfiguration &PassConfig) { + + std::lock_guard Lock(DebugAllocLock); + + // We allocate debug objects for LinkGraphs built from an object file. + // We cannot synthesize debug objects for raw LinkGraphs yet. + auto It = PendingDebugAllocs.find(getResourceKey(MR)); + if (It == PendingDebugAllocs.end()) + return; + + Expected> ObjView = + createLoadedObject(TT, It->second->getWorkingMemory(ReadOnlySegment)); + if (!ObjView) { + ES.reportError(ObjView.takeError()); + return; + } + + if (*ObjView) { + PassConfig.PostAllocationPasses.push_back( + [OV = ObjView->release()](jitlink::LinkGraph &Graph) -> Error { + std::unique_ptr ObjView(OV); + + // Enter load addresses in target memory for all executable sections. + for (const jitlink::Section &Section : Graph.sections()) { + jitlink::SectionRange TargetMemRange(Section); + if (!TargetMemRange.isEmpty()) + ObjView->submitSectionLoadAddress(Section.getName(), + TargetMemRange.getStart()); + } + + LLVM_DEBUG({ + dbgs() << formatv("Section load-addresses in debug object {0:x}\n", + pointerToJITTargetAddress(ObjView.get())); + ObjView->dumpSectionLoadAddresses(dbgs()); + }); + + return Error::success(); + }); + } +} + +Error JITLoaderGDBPlugin::notifyFailed(MaterializationResponsibility &MR) { + // TODO: Drop DebugAlloc for MR? + return Error::success(); +} + +void JITLoaderGDBPlugin::notifyLoaded(MaterializationResponsibility &MR) { + ResourceKey Key = getResourceKey(MR); + DebugAllocation Alloc = nullptr; + { + std::unique_lock Lock(DebugAllocLock); + auto It = PendingDebugAllocs.find(Key); + if (It != PendingDebugAllocs.end()) { + Alloc = std::move(It->second); + PendingDebugAllocs.erase(It); + } + } + + if (Alloc) + Alloc->finalizeAsync([this, Key, RawAlloc = Alloc.release()](Error Unused) { + DebugAllocation Alloc(RawAlloc); + + // TODO: Move to TargetProcessControl for out-of-process execution. + uint64_t DebugObjSize = Alloc->getWorkingMemory(ReadOnlySegment).size(); + const char *DebugObjInTargetMemory = + jitTargetAddressToPointer( + Alloc->getTargetMemory(ReadOnlySegment)); + + AppendJITCodeEntry(DebugObjInTargetMemory, DebugObjSize); + NotifyDebugger(JIT_REGISTER_FN); + + std::lock_guard Lock(DebugAllocLock); + DebugAllocs[Key].push_back(std::move(Alloc)); + + consumeError(std::move(Unused)); + }); +} + +void JITLoaderGDBPlugin::notifyTransferringResources(ResourceKey DstKey, + ResourceKey SrcKey) { + std::lock_guard Lock(DebugAllocLock); + + // Transfer registered debug objects. + auto SrcIt = DebugAllocs.find(SrcKey); + if (SrcIt != DebugAllocs.end()) { + auto DstIt = DebugAllocs.find(DstKey); + if (DstIt == DebugAllocs.end()) { + bool Inserted; + std::tie(DstIt, Inserted) = DebugAllocs.try_emplace(DstKey); + } + + for (DebugAllocation &Alloc : SrcIt->second) { + DstIt->second.push_back(std::move(Alloc)); + } + } + + // Transfer pending debug objects. + auto SrcItPending = PendingDebugAllocs.find(SrcKey); + if (SrcItPending != PendingDebugAllocs.end()) { + if (PendingDebugAllocs.count(DstKey)) { + ES.reportError(make_error( + formatv("Destination '{0}' for transferring pending debug object has " + "a pending resource already: {1}", + DstKey, SrcKey), + inconvertibleErrorCode())); + } else { + PendingDebugAllocs[DstKey] = std::move(SrcItPending->second); + } + } +} + +Error JITLoaderGDBPlugin::notifyRemovingResources(ResourceKey K) { + std::lock_guard Lock(DebugAllocLock); + + auto It = DebugAllocs.find(K); + if (It != DebugAllocs.end()) { + // TODO: Unregister submitted JITCodeEntries. + DebugAllocs.erase(K); + } + + if (PendingDebugAllocs.find(K) != PendingDebugAllocs.end()) + PendingDebugAllocs.erase(K); + + return Error::success(); +} + +} // namespace orc +} // 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(ES)); // Process any harness files. for (auto &HarnessFile : TestHarnesses) {