diff options
author | Stefan Gränitz <stefan.graenitz@gmail.com> | 2021-03-02 12:37:48 +0100 |
---|---|---|
committer | Stefan Gränitz <stefan.graenitz@gmail.com> | 2021-03-02 15:07:35 +0100 |
commit | ef2389235c5dec03be93f8c9585cd9416767ef4c (patch) | |
tree | 3e3439a9525825670011836aa935da1825a61f9c /llvm/lib/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.cpp | |
parent | 171849c2881bc11c82173588596cca8f539ef81d (diff) | |
download | llvm-ef2389235c5dec03be93f8c9585cd9416767ef4c.zip llvm-ef2389235c5dec03be93f8c9585cd9416767ef4c.tar.gz llvm-ef2389235c5dec03be93f8c9585cd9416767ef4c.tar.bz2 |
[Orc] Add JITLink debug support plugin for ELF x86-64
Add a new ObjectLinkingLayer plugin `DebugObjectManagerPlugin` and infrastructure to handle creation of `DebugObject`s as well as their registration in OrcTargetProcess. The current implementation only covers ELF on x86-64, but the infrastructure is not limited to that.
The journey starts with a new `LinkGraph` / `JITLinkContext` pair being created for a `MaterializationResponsibility` in ORC's `ObjectLinkingLayer`. It sends a `notifyMaterializing()` notification, which is forwarded to all registered plugins. The `DebugObjectManagerPlugin` aims to create a `DebugObject` form the provided target triple and object buffer. (Future implementations might create `DebugObject`s from a `LinkGraph` in other ways.) On success it will track it as the pending `DebugObject` for the `MaterializationResponsibility`.
This patch only implements the `ELFDebugObject` for `x86-64` targets. It follows the RuntimeDyld approach for debug object setup: it captures a copy of the input object, parses all section headers and prepares to patch their load-address fields with their final addresses in target memory. It instructs the plugin to report the section load-addresses once they are available. The plugin overrides `modifyPassConfig()` and installs a JITLink post-allocation pass to capture them.
Once JITLink emitted the finalized executable, the plugin emits and registers the `DebugObject`. For emission it requests a new `JITLinkMemoryManager::Allocation` with a single read-only segment, copies the object with patched section load-addresses over to working memory and triggers finalization to target memory. For registration, it notifies the `DebugObjectRegistrar` provided in the constructor and stores the previously pending`DebugObject` as registered for the corresponding MaterializationResponsibility.
The `DebugObjectRegistrar` registers the `DebugObject` with the target process. `llvm-jitlink` uses the `TPCDebugObjectRegistrar`, which calls `llvm_orc_registerJITLoaderGDBWrapper()` in the target process via `TargetProcessControl` to emit a `jit_code_entry` compatible with the GDB JIT interface [1]. So far the implementation only supports registration and no removal. It appears to me that it wouldn't raise any new design questions, so I left this as an addition for the near future.
[1] https://sourceware.org/gdb/current/onlinedocs/gdb/JIT-Interface.html
Reviewed By: lhames
Differential Revision: https://reviews.llvm.org/D97335
Diffstat (limited to 'llvm/lib/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.cpp')
-rw-r--r-- | llvm/lib/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.cpp | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/llvm/lib/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.cpp b/llvm/lib/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.cpp new file mode 100644 index 0000000..30f833e --- /dev/null +++ b/llvm/lib/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.cpp @@ -0,0 +1,110 @@ +//===- 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 <cstdint> +#include <mutex> +#include <utility> + +#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; + +// Serialize rendezvous with the debugger as well as access to shared data. +ManagedStatic<std::mutex> JITDebugLock; + +static std::pair<const char *, uint64_t> readDebugObjectInfo(uint8_t *ArgData, + uint64_t ArgSize) { + BinaryStreamReader ArgReader(ArrayRef<uint8_t>(ArgData, ArgSize), + support::endianness::big); + uint64_t Addr, Size; + cantFail(ArgReader.readInteger(Addr)); + cantFail(ArgReader.readInteger(Size)); + + return std::make_pair(jitTargetAddressToPointer<const char *>(Addr), Size); +} + +extern "C" orc::tpctypes::CWrapperFunctionResult +llvm_orc_registerJITLoaderGDBWrapper(uint8_t *Data, uint64_t Size) { + if (Size != sizeof(uint64_t) + sizeof(uint64_t)) + return orc::tpctypes::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<std::mutex> 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 orc::tpctypes::WrapperFunctionResult().release(); +} |