From bc70144df1b29ba7e4fd5fd7873d6b927dc5cb8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20=28gsus=29=20Lapastora?= Date: Sun, 29 Oct 2023 17:56:59 +0100 Subject: [PATCH] LibJIT: Enable registering JITted objects into GDB The new JIT::GDB namespace enables registering JITted objects into GDB dynamically. Its clients just have to ensure the memory they give to `register_into_gdb` is in a format that GDB can understand, either by generating an object file in memory with debug info + symbols or by registering a custom debug info parser. None of these are implemented by this API; it only implements the registering part and lets the client to choose the data format. GDB JIT Interface: https://sourceware.org/gdb/current/onlinedocs/gdb.html/JIT-Interface.html#JIT-Interface Things to take into account from v8's docs, some of which we may improve: https://v8.dev/docs/gdb-jit#known-limitations --- Userland/Libraries/LibJIT/CMakeLists.txt | 1 + Userland/Libraries/LibJIT/GDB.cpp | 104 +++++++++++++++++++++++ Userland/Libraries/LibJIT/GDB.h | 15 ++++ 3 files changed, 120 insertions(+) create mode 100644 Userland/Libraries/LibJIT/GDB.cpp create mode 100644 Userland/Libraries/LibJIT/GDB.h diff --git a/Userland/Libraries/LibJIT/CMakeLists.txt b/Userland/Libraries/LibJIT/CMakeLists.txt index 37cdec2343..917ee72ca3 100644 --- a/Userland/Libraries/LibJIT/CMakeLists.txt +++ b/Userland/Libraries/LibJIT/CMakeLists.txt @@ -1,5 +1,6 @@ set(SOURCES Assembler.cpp + GDB.cpp ) serenity_lib(LibJIT jit) diff --git a/Userland/Libraries/LibJIT/GDB.cpp b/Userland/Libraries/LibJIT/GDB.cpp new file mode 100644 index 0000000000..50fb8396c9 --- /dev/null +++ b/Userland/Libraries/LibJIT/GDB.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2023, Jesús Lapastora + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace JIT::GDB { + +// Declarations from https://sourceware.org/gdb/current/onlinedocs/gdb.html/Declarations.html#Declarations. +enum class JitActions : u32 { + NoAction = 0, + RegisterFn = 1, + UnregisterFn = 2, +}; + +struct JitCodeEntry { + JitCodeEntry* next_entry; + JitCodeEntry* prev_entry; + char const* symfile_addr; + u64 symfile_size; +}; + +struct JitDescriptor { + u32 version; + JitActions action_flag; + JitCodeEntry* relevant_entry; + JitCodeEntry* first_entry; +}; +extern "C" { + +// GDB puts a breakpoint in this function. +// Use an __asm__ statement to prevent compilers from inlining/removing this +// function. +// From V8: +// https://github.com/v8/v8/blob/1c7d3a94fc051e0fd69584b930faab448026941e/src/diagnostics/gdb-jit.cc#L1742 +void __attribute__((noinline)) __jit_debug_register_code(); +void __attribute__((noinline)) __jit_debug_register_code() { __asm__(""); } + +// Make sure to specify the version statically, because the debugger may check +// the version before we can set it. +// NOTE: If the JIT is multi-threaded, then it is important that the JIT synchronize any modifications to this global data properly, which can easily be done by putting a global mutex around modifications to these structures. +JitDescriptor __jit_debug_descriptor = { 1, JitActions::NoAction, nullptr, nullptr }; +} + +static JitCodeEntry* find_code_entry(ReadonlyBytes data) +{ + auto const search_addr = bit_cast(data.offset(0)); + for (JitCodeEntry* curr = __jit_debug_descriptor.first_entry; curr != NULL; curr = curr->next_entry) { + auto const entry_addr = bit_cast(curr->symfile_addr); + if (entry_addr == search_addr) { + VERIFY(curr->symfile_size == data.size()); + return curr; + } + } + return NULL; +} + +void unregister_from_gdb(ReadonlyBytes data) +{ + // Following steps from: + // https://sourceware.org/gdb/current/onlinedocs/gdb.html/Unregistering-Code.html#Unregistering-Code + auto may_have_entry = AK::adopt_own_if_nonnull(find_code_entry(data)); + VERIFY(may_have_entry); + auto entry = may_have_entry.release_nonnull(); + if (entry->prev_entry) { + entry->prev_entry->next_entry = entry->next_entry; + } + if (entry->next_entry) { + entry->next_entry->prev_entry = entry->prev_entry; + } + if (entry == __jit_debug_descriptor.first_entry) { + __jit_debug_descriptor.first_entry = entry->next_entry; + } + __jit_debug_descriptor.relevant_entry = entry; + __jit_debug_descriptor.action_flag = JitActions::UnregisterFn; + __jit_debug_register_code(); +} + +void register_into_gdb(ReadonlyBytes data) +{ + // Following steps from: + // https://sourceware.org/gdb/current/onlinedocs/gdb.html/Registering-Code.html#Registering-Code + auto* const leaked_entry = make().leak_ptr(); + + // Add it to the linked list in the JIT descriptor. + leaked_entry->symfile_addr = reinterpret_cast(data.data()); + leaked_entry->symfile_size = data.size(); + leaked_entry->next_entry = __jit_debug_descriptor.first_entry; + leaked_entry->prev_entry = NULL; + if (__jit_debug_descriptor.first_entry) { + VERIFY(__jit_debug_descriptor.first_entry->prev_entry == nullptr); + __jit_debug_descriptor.first_entry->prev_entry = leaked_entry; + } + __jit_debug_descriptor.first_entry = leaked_entry; + __jit_debug_descriptor.relevant_entry = leaked_entry; + __jit_debug_descriptor.action_flag = JitActions::RegisterFn; + __jit_debug_register_code(); +} +} diff --git a/Userland/Libraries/LibJIT/GDB.h b/Userland/Libraries/LibJIT/GDB.h new file mode 100644 index 0000000000..5d9c97a59e --- /dev/null +++ b/Userland/Libraries/LibJIT/GDB.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2023, Jesús Lapastora + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JIT::GDB { + +void register_into_gdb(ReadonlyBytes data); +void unregister_from_gdb(ReadonlyBytes data); +}