[llvm] [orc-rt] Add GDBJITRegistrar for debug object registration (PR #215739)
Lang Hames via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 11 23:58:36 PDT 2026
https://github.com/lhames created https://github.com/llvm/llvm-project/pull/215739
Ports the executor side of llvm/lib/ExecutionEngine/Orc/TargetProcess/ JITLoaderGDB.cpp to orc-rt, and adds the deregistration that the LLVM version does not have.
orc_rt::gdb_jit::registerObject / deregisterObject maintain the process-wide __jit_debug_descriptor list that debuggers implementing the GDB JIT interface walk to discover JIT'd debug objects. They are exposed to controllers by sps_ci::addGDBJITRegistrar as the allocation-action pair orc_rt_ci_aa_sps_GDBJITRegistrar_{register,deregister}, meant to be attached as a finalize/dealloc pair to the allocation holding the debug object: dealloc actions run before their slab is unmapped, so deregistration is guaranteed to happen while the object is still mapped.
Deregistration unlinks the entry, notifies, and only then frees it: debuggers read symfile_addr out of the entry during the rendezvous, so it must stay live across the call. The descriptor is afterwards reset to JIT_NOACTION with a null relevant_entry, rather than being left pointing at freed memory.
A registered object's memory must remain mapped and unmodified until it is deregistered. The interface has no acknowledgement channel, and a debugger attaching at any later point re-reads every object still in the list, so the runtime can never conclude that an entry has been consumed.
>From 2f8e438e352ca06bf30807595143ec911e879872 Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Wed, 12 Aug 2026 16:48:09 +1000
Subject: [PATCH] [orc-rt] Add GDBJITRegistrar for debug object registration
Ports the executor side of llvm/lib/ExecutionEngine/Orc/TargetProcess/
JITLoaderGDB.cpp to orc-rt, and adds the deregistration that the LLVM
version does not have.
orc_rt::gdb_jit::registerObject / deregisterObject maintain the process-wide
__jit_debug_descriptor list that debuggers implementing the GDB JIT interface
walk to discover JIT'd debug objects. They are exposed to controllers by
sps_ci::addGDBJITRegistrar as the allocation-action pair
orc_rt_ci_aa_sps_GDBJITRegistrar_{register,deregister}, meant to be attached as
a finalize/dealloc pair to the allocation holding the debug object: dealloc
actions run before their slab is unmapped, so deregistration is guaranteed to
happen while the object is still mapped.
Deregistration unlinks the entry, notifies, and only then frees it: debuggers
read symfile_addr out of the entry during the rendezvous, so it must stay live
across the call. The descriptor is afterwards reset to JIT_NOACTION with a null
relevant_entry, rather than being left pointing at freed memory.
A registered object's memory must remain mapped and unmodified until it is
deregistered. The interface has no acknowledgement channel, and a debugger
attaching at any later point re-reads every object still in the list, so the
runtime can never conclude that an entry has been consumed.
---
orc-rt/include/CMakeLists.txt | 1 +
.../orc-rt/sps-ci/GDBJITRegistrarSPSCI.h | 25 ++++
orc-rt/lib/executor/CMakeLists.txt | 2 +
orc-rt/lib/executor/GDBJITRegistrar.cpp | 137 ++++++++++++++++++
orc-rt/lib/executor/GDBJITRegistrar.h | 46 ++++++
.../executor/sps-ci/GDBJITRegistrarSPSCI.cpp | 37 +++++
6 files changed, 248 insertions(+)
create mode 100644 orc-rt/include/orc-rt/sps-ci/GDBJITRegistrarSPSCI.h
create mode 100644 orc-rt/lib/executor/GDBJITRegistrar.cpp
create mode 100644 orc-rt/lib/executor/GDBJITRegistrar.h
create mode 100644 orc-rt/lib/executor/sps-ci/GDBJITRegistrarSPSCI.cpp
diff --git a/orc-rt/include/CMakeLists.txt b/orc-rt/include/CMakeLists.txt
index 9663f4f3291c4..247d82ba709e6 100644
--- a/orc-rt/include/CMakeLists.txt
+++ b/orc-rt/include/CMakeLists.txt
@@ -42,6 +42,7 @@ set(ORC_RT_HEADERS
orc-rt/span.h
orc-rt/sps-ci/AllSPSCI.h
orc-rt/sps-ci/CallSPSCI.h
+ orc-rt/sps-ci/GDBJITRegistrarSPSCI.h
orc-rt/sps-ci/MemoryAccessSPSCI.h
orc-rt/sps-ci/NativeDylibManagerSPSCI.h
orc-rt/sps-ci/SimpleNativeMemoryMapSPSCI.h
diff --git a/orc-rt/include/orc-rt/sps-ci/GDBJITRegistrarSPSCI.h b/orc-rt/include/orc-rt/sps-ci/GDBJITRegistrarSPSCI.h
new file mode 100644
index 0000000000000..747e46a6a97d3
--- /dev/null
+++ b/orc-rt/include/orc-rt/sps-ci/GDBJITRegistrarSPSCI.h
@@ -0,0 +1,25 @@
+//===------------- GDBJITRegistrarSPSCI.h -----------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// SPS Controller Interface registration for GDBJITRegistrar.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef ORC_RT_SPS_CI_GDBJITREGISTRARSPSCI_H
+#define ORC_RT_SPS_CI_GDBJITREGISTRARSPSCI_H
+
+#include "orc-rt/SimpleSymbolTable.h"
+
+namespace orc_rt::sps_ci {
+
+/// Add the GDBJITRegistrar SPS interface to the controller interface.
+Error addGDBJITRegistrar(SimpleSymbolTable &ST);
+
+} // namespace orc_rt::sps_ci
+
+#endif // ORC_RT_SPS_CI_GDBJITREGISTRARSPSCI_H
diff --git a/orc-rt/lib/executor/CMakeLists.txt b/orc-rt/lib/executor/CMakeLists.txt
index dc2e114432588..b5c49c989a2e4 100644
--- a/orc-rt/lib/executor/CMakeLists.txt
+++ b/orc-rt/lib/executor/CMakeLists.txt
@@ -4,6 +4,7 @@ set(ORC_RT_SOURCES
Environment.cpp
Error.cpp
ExecutorProcessInfo.cpp
+ GDBJITRegistrar.cpp
InProcessControllerAccess.cpp
Logging.cpp
NativeDylibManager.cpp
@@ -16,6 +17,7 @@ set(ORC_RT_SOURCES
ThreadPoolRunner.cpp
sps-ci/AllSPSCI.cpp
sps-ci/CallSPSCI.cpp
+ sps-ci/GDBJITRegistrarSPSCI.cpp
sps-ci/MemoryAccessSPSCI.cpp
sps-ci/NativeDylibManagerSPSCI.cpp
sps-ci/SimpleNativeMemoryMapSPSCI.cpp
diff --git a/orc-rt/lib/executor/GDBJITRegistrar.cpp b/orc-rt/lib/executor/GDBJITRegistrar.cpp
new file mode 100644
index 0000000000000..badf099a3503f
--- /dev/null
+++ b/orc-rt/lib/executor/GDBJITRegistrar.cpp
@@ -0,0 +1,137 @@
+//===---- GDBJITRegistrar.cpp - Register objects via GDB JIT iface -------===//
+//
+// 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 "GDBJITRegistrar.h"
+#include "orc-rt/Compiler.h"
+
+#include <cstdint>
+#include <mutex>
+
+using namespace orc_rt;
+
+// 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;
+};
+
+// First version as landed in GDB, August 2009.
+static constexpr uint32_t JitDescriptorVersion = 1;
+
+// We put information about the JIT'd object 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.
+ORC_RT_INTERFACE struct jit_descriptor __jit_debug_descriptor = {
+ JitDescriptorVersion, JIT_NOACTION, nullptr, nullptr};
+
+// Debuggers that implement the GDB JIT interface put a special breakpoint in
+// this function.
+#if defined(_MSC_VER)
+ORC_RT_INTERFACE void __jit_debug_register_code() {}
+#else
+ORC_RT_INTERFACE __attribute__((noinline)) void __jit_debug_register_code() {
+ // The noinline attribute above and the asm volatile below prevent calls to
+ // this function from being optimized out.
+ asm volatile("" ::: "memory");
+}
+#endif
+
+} // extern "C"
+
+namespace {
+// Serializes rendezvous with the debugger, as well as access to the
+// __jit_debug_descriptor list.
+std::mutex JITDebugLock;
+} // namespace
+
+namespace orc_rt::gdb_jit {
+
+Error registerObject(span<char> Obj) {
+ auto *E = new jit_code_entry;
+ E->symfile_addr = Obj.data();
+ E->symfile_size = Obj.size();
+ E->prev_entry = nullptr;
+
+ std::scoped_lock<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;
+ __jit_debug_descriptor.action_flag = JIT_REGISTER_FN;
+
+ // Run into the rendezvous breakpoint.
+ __jit_debug_register_code();
+
+ return Error::success();
+}
+
+Error deregisterObject(span<char> Obj) {
+ std::scoped_lock<std::mutex> Lock(JITDebugLock);
+
+ jit_code_entry *E = __jit_debug_descriptor.first_entry;
+ while (E && (E->symfile_addr != Obj.data() || E->symfile_size != Obj.size()))
+ E = E->next_entry;
+
+ if (!E)
+ return make_error<StringError>(
+ "No GDB JIT debug object registered for range");
+
+ // Unlink E from the list.
+ if (E->next_entry)
+ E->next_entry->prev_entry = E->prev_entry;
+ if (E->prev_entry)
+ E->prev_entry->next_entry = E->next_entry;
+ else {
+ assert(__jit_debug_descriptor.first_entry == E &&
+ "Entry has no prev_entry, but is not the first entry");
+ __jit_debug_descriptor.first_entry = E->next_entry;
+ }
+
+ __jit_debug_descriptor.relevant_entry = E;
+ __jit_debug_descriptor.action_flag = JIT_UNREGISTER_FN;
+
+ // Run into the rendezvous breakpoint. Debuggers read symfile_addr out of E
+ // here, so E must still be live for this call.
+ __jit_debug_register_code();
+
+ // Reset the descriptor rather than leaving relevant_entry dangling once E is
+ // freed below. Debuggers treat JIT_NOACTION as a no-op, and those that attach
+ // later walk first_entry instead of reading these fields.
+ __jit_debug_descriptor.relevant_entry = nullptr;
+ __jit_debug_descriptor.action_flag = JIT_NOACTION;
+
+ delete E;
+
+ return Error::success();
+}
+
+} // namespace orc_rt::gdb_jit
diff --git a/orc-rt/lib/executor/GDBJITRegistrar.h b/orc-rt/lib/executor/GDBJITRegistrar.h
new file mode 100644
index 0000000000000..00a802e27c0cc
--- /dev/null
+++ b/orc-rt/lib/executor/GDBJITRegistrar.h
@@ -0,0 +1,46 @@
+//===----------- GDBJITRegistrar.h - 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Registration of JIT'd debug objects with debuggers via the GDB JIT
+// interface (also implemented by LLDB and other tools).
+//
+// These functions exist to implement the GDBJITRegistrar allocation actions in
+// the SPS controller interface (see sps-ci/GDBJITRegistrarSPSCI.cpp). They can
+// be called directly, but that isn't their purpose: this is a private
+// implementation header, not part of the public orc-rt API. If a direct client
+// ever appears, promote this header to include/orc-rt/ rather than reaching
+// into lib/.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef ORC_RT_GDBJITREGISTRAR_H
+#define ORC_RT_GDBJITREGISTRAR_H
+
+#include "orc-rt/Error.h"
+#include "orc-rt/span.h"
+
+namespace orc_rt::gdb_jit {
+
+/// Register the object in the given buffer with the GDB JIT interface.
+///
+/// Obj range must remain mapped and unmodified until it is deregistered:
+/// debuggers read the object image out of this range, and this may happen at
+/// any time after the registration.
+Error registerObject(span<char> Obj);
+
+/// Deregister the object in the given buffer from the GDB JIT interface.
+///
+/// Returns an error if no object is registered for Obj.
+///
+/// Callers must deregister before releasing Obj's memory, so that a debugger
+/// can never observe a list entry describing memory that has been reused.
+Error deregisterObject(span<char> Obj);
+
+} // namespace orc_rt::gdb_jit
+
+#endif // ORC_RT_GDBJITREGISTRAR_H
diff --git a/orc-rt/lib/executor/sps-ci/GDBJITRegistrarSPSCI.cpp b/orc-rt/lib/executor/sps-ci/GDBJITRegistrarSPSCI.cpp
new file mode 100644
index 0000000000000..332d18b2ecd07
--- /dev/null
+++ b/orc-rt/lib/executor/sps-ci/GDBJITRegistrarSPSCI.cpp
@@ -0,0 +1,37 @@
+//===- GDBJITRegistrarSPSCI.cpp -------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// SPS Controller Interface implementation for GDBJITRegistrar.
+//
+//===----------------------------------------------------------------------===//
+
+#include "orc-rt/sps-ci/GDBJITRegistrarSPSCI.h"
+#include "orc-rt/SPSAllocAction.h"
+
+#include "../GDBJITRegistrar.h"
+
+using namespace orc_rt;
+
+namespace orc_rt::sps_ci {
+
+ORC_RT_SPS_ALLOC_ACTION(orc_rt_ci_aa_sps_GDBJITRegistrar_register,
+ (SPSExecutorAddrRange), &gdb_jit::registerObject)
+
+ORC_RT_SPS_ALLOC_ACTION(orc_rt_ci_aa_sps_GDBJITRegistrar_deregister,
+ (SPSExecutorAddrRange), &gdb_jit::deregisterObject)
+
+static std::pair<const char *, const void *>
+ orc_rt_ci_GDBJITRegistrar_sps_interface[] = {
+ ORC_RT_SYMTAB_PAIR(orc_rt_ci_aa_sps_GDBJITRegistrar_register),
+ ORC_RT_SYMTAB_PAIR(orc_rt_ci_aa_sps_GDBJITRegistrar_deregister)};
+
+Error addGDBJITRegistrar(SimpleSymbolTable &ST) {
+ return ST.addUnique(orc_rt_ci_GDBJITRegistrar_sps_interface);
+}
+
+} // namespace orc_rt::sps_ci
More information about the llvm-commits
mailing list