[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