[libc-commits] [libc] [libc] Populate _r_debug in startup code and expose it through <link.h> (PR #215649)

Pavel Labath via libc-commits libc-commits at lists.llvm.org
Tue Aug 11 13:04:27 PDT 2026


https://github.com/labath created https://github.com/llvm/llvm-project/pull/215649

This patch exposes the SVR4 r_debug and link_map structures through <link.h> and populates the _r_debug rendezvous structure during process startup in do_start().

This allows debuggers and similar tools to inspect the link map, find the load base (l_addr), and set breakpoints on the r_brk notification function (_r_debug_state) when debugging binaries built against LLVM libc.

This patch has two caveats:
- the code does not update the DT_DEBUG entry in the dynamic section. I'll do that in a separate patch because testing it requires building a -static-pie test, which we don't have support for right now.
- currently lldb refuses to set a breakpoint on _r_debug_state, because it expects in to be defined in a C++ file. I plan to fix this by relaxing the lldb check.

Assisted by Gemini.

>From 5454326f702eac7d71f69e5422231abbcf85500f Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Tue, 4 Aug 2026 15:12:20 +0000
Subject: [PATCH] [libc] Populate _r_debug in startup code and expose it
 through <link.h>

This patch exposes the SVR4 r_debug and link_map structures
through <link.h> and populates the _r_debug rendezvous structure during
process startup in do_start().

This allows debuggers and similar tools to inspect the link map, find
the load base (l_addr), and set breakpoints on the r_brk notification
function (_r_debug_state) when debugging binaries built against LLVM
libc.

This patch has two caveats:
- the code does not update the DT_DEBUG entry in the dynamic section.
  I'll do that in a separate patch because testing it requires building
  a -static-pie test, which we don't have support for right now.
- currently lldb refuses to set a breakpoint on _r_debug_state, because
  it expects in to be defined in a C++ file. I plan to fix this by
  relaxing the lldb check.

Assisted by Gemini.
---
 libc/config/linux/aarch64/entrypoints.txt     |  1 +
 libc/config/linux/riscv/entrypoints.txt       |  1 +
 libc/config/linux/x86_64/entrypoints.txt      |  1 +
 libc/hdr/types/CMakeLists.txt                 | 16 +++++++++
 libc/hdr/types/struct_link_map.h              | 27 ++++++++++++++
 libc/hdr/types/struct_r_debug.h               | 27 ++++++++++++++
 libc/include/CMakeLists.txt                   |  2 ++
 libc/include/link.yaml                        | 11 ++++++
 libc/include/llvm-libc-types/CMakeLists.txt   | 13 +++++++
 .../include/llvm-libc-types/struct_link_map.h | 29 +++++++++++++++
 libc/include/llvm-libc-types/struct_r_debug.h | 33 +++++++++++++++++
 libc/src/link/CMakeLists.txt                  | 12 +++++++
 libc/src/link/_r_debug.cpp                    | 23 ++++++++++++
 libc/src/link/_r_debug.h                      | 26 ++++++++++++++
 libc/startup/linux/CMakeLists.txt             |  3 ++
 libc/startup/linux/do_start.cpp               | 22 ++++++++++++
 .../integration/startup/linux/CMakeLists.txt  | 10 ++++++
 .../startup/linux/r_debug_test.cpp            | 35 +++++++++++++++++++
 18 files changed, 292 insertions(+)
 create mode 100644 libc/hdr/types/struct_link_map.h
 create mode 100644 libc/hdr/types/struct_r_debug.h
 create mode 100644 libc/include/llvm-libc-types/struct_link_map.h
 create mode 100644 libc/include/llvm-libc-types/struct_r_debug.h
 create mode 100644 libc/src/link/_r_debug.cpp
 create mode 100644 libc/src/link/_r_debug.h
 create mode 100644 libc/test/integration/startup/linux/r_debug_test.cpp

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 45baa15bb4532..95ae3abd16422 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1325,6 +1325,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.sys.select.select
 
     # link.h entrypoints
+    libc.src.link._r_debug
     libc.src.link.dl_iterate_phdr
 
     # wchar.h entrypoints
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index f95cfd3820708..fbf59f2572339 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1559,6 +1559,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.nl_types.catgets
 
     # link.h entrypoints
+    libc.src.link._r_debug
     libc.src.link.dl_iterate_phdr
   )
 endif()
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index e257bc19b2920..f7d84eeef5dbe 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1568,6 +1568,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.nl_types.catgets
 
     # link.h entrypoints
+    libc.src.link._r_debug
     libc.src.link.dl_iterate_phdr
   )
 endif()
diff --git a/libc/hdr/types/CMakeLists.txt b/libc/hdr/types/CMakeLists.txt
index 38e586bded61e..bc362102ca8aa 100644
--- a/libc/hdr/types/CMakeLists.txt
+++ b/libc/hdr/types/CMakeLists.txt
@@ -49,6 +49,22 @@ add_proxy_header_library(
     libc.include.llvm-libc-types.struct_dl_phdr_info
 )
 
+add_proxy_header_library(
+  struct_link_map
+  HDRS
+    struct_link_map.h
+  FULL_BUILD_DEPENDS
+    libc.include.llvm-libc-types.struct_link_map
+)
+
+add_proxy_header_library(
+  struct_r_debug
+  HDRS
+    struct_r_debug.h
+  FULL_BUILD_DEPENDS
+    libc.include.llvm-libc-types.struct_r_debug
+)
+
 add_proxy_header_library(
   ldiv_t
   HDRS
diff --git a/libc/hdr/types/struct_link_map.h b/libc/hdr/types/struct_link_map.h
new file mode 100644
index 0000000000000..9960913d72837
--- /dev/null
+++ b/libc/hdr/types/struct_link_map.h
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Proxy for struct link_map.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_HDR_TYPES_STRUCT_LINK_MAP_H
+#define LLVM_LIBC_HDR_TYPES_STRUCT_LINK_MAP_H
+
+#ifdef LIBC_FULL_BUILD
+
+#include "include/llvm-libc-types/struct_link_map.h"
+
+#else
+
+#include <link.h>
+
+#endif // LIBC_FULL_BUILD
+
+#endif // LLVM_LIBC_HDR_TYPES_STRUCT_LINK_MAP_H
diff --git a/libc/hdr/types/struct_r_debug.h b/libc/hdr/types/struct_r_debug.h
new file mode 100644
index 0000000000000..c0386bf648f30
--- /dev/null
+++ b/libc/hdr/types/struct_r_debug.h
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Proxy for struct r_debug.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_HDR_TYPES_STRUCT_R_DEBUG_H
+#define LLVM_LIBC_HDR_TYPES_STRUCT_R_DEBUG_H
+
+#ifdef LIBC_FULL_BUILD
+
+#include "include/llvm-libc-types/struct_r_debug.h"
+
+#else
+
+#include <link.h>
+
+#endif // LIBC_FULL_BUILD
+
+#endif // LLVM_LIBC_HDR_TYPES_STRUCT_R_DEBUG_H
diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index 485012a6c2aca..a7c2978aae3e7 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -607,6 +607,8 @@ add_header_macro(
     .llvm_libc_common_h
     .llvm-libc-macros.link_macros
     .llvm-libc-types.struct_dl_phdr_info
+    .llvm-libc-types.struct_link_map
+    .llvm-libc-types.struct_r_debug
     .llvm-libc-types.__dl_iterate_phdr_callback_t
 )
 
diff --git a/libc/include/link.yaml b/libc/include/link.yaml
index e0228ef6ed6ef..d8fd0bba0d404 100644
--- a/libc/include/link.yaml
+++ b/libc/include/link.yaml
@@ -12,6 +12,17 @@ types:
   - type_name: struct_dl_phdr_info
     standards:
       - gnu
+  - type_name: struct_link_map
+    standards:
+      - svid
+  - type_name: struct_r_debug
+    standards:
+      - svid
+objects:
+  - object_name: _r_debug
+    standards:
+      - svid
+    object_type: struct r_debug
 functions:
   - name: dl_iterate_phdr
     standards:
diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt
index e6aa0497837b5..77af6a77b942e 100644
--- a/libc/include/llvm-libc-types/CMakeLists.txt
+++ b/libc/include/llvm-libc-types/CMakeLists.txt
@@ -127,6 +127,19 @@ add_header(suseconds_t HDR suseconds_t.h)
 add_header(struct_dirent HDR struct_dirent.h DEPENDS .ino_t .off_t .reclen_t)
 add_header(struct_posix_dent HDR struct_posix_dent.h DEPENDS .ino_t .off_t .reclen_t)
 add_header(struct_dl_phdr_info HDR struct_dl_phdr_info.h DEPENDS .size_t libc.include.llvm-libc-macros.link_macros)
+add_header(
+  struct_link_map
+  HDR struct_link_map.h
+  DEPENDS
+    libc.include.llvm-libc-macros.link_macros
+)
+add_header(
+  struct_r_debug
+  HDR struct_r_debug.h
+  DEPENDS
+    .struct_link_map
+    libc.include.llvm-libc-macros.link_macros
+)
 add_header(dl_info HDR Dl_info.h)
 add_header(struct_f_owner_ex HDR struct_f_owner_ex.h DEPENDS .pid_t)
 add_header(struct_flock HDR struct_flock.h DEPENDS .off_t .pid_t)
diff --git a/libc/include/llvm-libc-types/struct_link_map.h b/libc/include/llvm-libc-types/struct_link_map.h
new file mode 100644
index 0000000000000..ebc02b60d1528
--- /dev/null
+++ b/libc/include/llvm-libc-types/struct_link_map.h
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Definition of type struct link_map.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_TYPES_STRUCT_LINK_MAP_H
+#define LLVM_LIBC_TYPES_STRUCT_LINK_MAP_H
+
+#include "../llvm-libc-macros/link-macros.h"
+
+// NB: This structure is accessed by debuggers and similar tools, which have
+// their own definition of the structure. Changing the layout here will likely
+// break those tools.
+struct link_map {
+  ElfW(Addr) l_addr;
+  char *l_name;
+  ElfW(Dyn) * l_ld;
+  struct link_map *l_next, *l_prev;
+};
+
+#endif // LLVM_LIBC_TYPES_STRUCT_LINK_MAP_H
diff --git a/libc/include/llvm-libc-types/struct_r_debug.h b/libc/include/llvm-libc-types/struct_r_debug.h
new file mode 100644
index 0000000000000..68c996082650c
--- /dev/null
+++ b/libc/include/llvm-libc-types/struct_r_debug.h
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Definition of type struct r_debug.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_TYPES_STRUCT_R_DEBUG_H
+#define LLVM_LIBC_TYPES_STRUCT_R_DEBUG_H
+
+#include "../llvm-libc-macros/link-macros.h"
+#include "struct_link_map.h"
+
+enum { RT_CONSISTENT = 0, RT_ADD = 1, RT_DELETE = 2 };
+
+// NB: This structure and the related constants are accessed by debuggers and
+// similar tools, which have their own definition of the structure. Changing
+// their definition here will likely break those tools.
+struct r_debug {
+  int r_version;
+  struct link_map *r_map;
+  ElfW(Addr) r_brk;
+  int r_state;
+  ElfW(Addr) r_ldbase;
+};
+
+#endif // LLVM_LIBC_TYPES_STRUCT_R_DEBUG_H
diff --git a/libc/src/link/CMakeLists.txt b/libc/src/link/CMakeLists.txt
index 5f0bbcd3a4f4e..c3fcdb3c1656e 100644
--- a/libc/src/link/CMakeLists.txt
+++ b/libc/src/link/CMakeLists.txt
@@ -1,3 +1,15 @@
+add_entrypoint_object(
+  _r_debug
+  SRCS
+    _r_debug.cpp
+  HDRS
+    _r_debug.h
+  DEPENDS
+    libc.hdr.types.struct_r_debug
+    libc.src.__support.common
+    libc.src.__support.macros.config
+)
+
 add_entrypoint_object(
   dl_iterate_phdr
   SRCS
diff --git a/libc/src/link/_r_debug.cpp b/libc/src/link/_r_debug.cpp
new file mode 100644
index 0000000000000..dfbc6c86d6559
--- /dev/null
+++ b/libc/src/link/_r_debug.cpp
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of _r_debug.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/link/_r_debug.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+// Initialized by startup code.
+LLVM_LIBC_VARIABLE(struct r_debug, _r_debug) = {};
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/link/_r_debug.h b/libc/src/link/_r_debug.h
new file mode 100644
index 0000000000000..a15023f6f88ee
--- /dev/null
+++ b/libc/src/link/_r_debug.h
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header of _r_debug.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_LINK__R_DEBUG_H
+#define LLVM_LIBC_SRC_LINK__R_DEBUG_H
+
+#include "hdr/types/struct_r_debug.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+extern struct r_debug _r_debug;
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_LINK__R_DEBUG_H
diff --git a/libc/startup/linux/CMakeLists.txt b/libc/startup/linux/CMakeLists.txt
index 1d64fa17390a8..647de99a32125 100644
--- a/libc/startup/linux/CMakeLists.txt
+++ b/libc/startup/linux/CMakeLists.txt
@@ -114,12 +114,15 @@ add_object_library(
     libc.hdr.link_macros
     libc.hdr.stdint_proxy
     libc.hdr.sys_mman_macros
+    libc.hdr.types.struct_link_map
+    libc.hdr.types.struct_r_debug
     libc.include.sys_syscall
     libc.src.__support.OSUtil.linux.auxv
     libc.src.__support.OSUtil.osutil
     libc.src.__support.threads.thread
     libc.src.__support.macros.config
     libc.startup.linux.${LIBC_TARGET_ARCHITECTURE}.irelative
+    libc.src.link._r_debug
     libc.src.stdlib.exit
     libc.src.stdlib.atexit
     libc.src.unistd.environ
diff --git a/libc/startup/linux/do_start.cpp b/libc/startup/linux/do_start.cpp
index f2e8da5327832..85f46a8e97fcf 100644
--- a/libc/startup/linux/do_start.cpp
+++ b/libc/startup/linux/do_start.cpp
@@ -11,12 +11,15 @@
 #include "hdr/link_macros.h"
 #include "hdr/stdint_proxy.h"
 #include "hdr/sys_mman_macros.h"
+#include "hdr/types/struct_link_map.h"
+#include "hdr/types/struct_r_debug.h"
 #include "src/__support/OSUtil/linux/auxv.h"
 #include "src/__support/OSUtil/syscall.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/threads/thread.h"
 #include "src/errno/program_invocation_name.h"
 #include "src/errno/program_invocation_short_name.h"
+#include "src/link/_r_debug.h"
 #include "src/stdlib/atexit.h"
 #include "src/stdlib/exit.h"
 #include "src/unistd/environ.h"
@@ -41,11 +44,18 @@ extern uintptr_t __fini_array_end[];
 // on how the program is loaded exactly.
 [[gnu::weak,
   gnu::visibility("hidden")]] extern const ElfW(Dyn) _DYNAMIC[]; // NOLINT
+
+// Debuggers look for this function by name. Carefully consider any changes.
+void _r_debug_state() { // NOLINT
+  asm volatile("");
+}
 }
 
 namespace LIBC_NAMESPACE_DECL {
 AppProperties app;
 
+static struct link_map main_map;
+
 using InitCallback = void(int, char **, char **);
 using FiniCallback = void(void);
 
@@ -143,6 +153,18 @@ static TLSDescriptor tls;
     // TODO: adjust PT_GNU_STACK
   }
 
+  main_map.l_addr = base;
+  main_map.l_name = const_cast<char *>("");
+  main_map.l_ld = const_cast<ElfW(Dyn) *>(_DYNAMIC);
+  main_map.l_next = nullptr;
+  main_map.l_prev = nullptr;
+
+  _r_debug.r_version = 1;
+  _r_debug.r_map = &main_map;
+  _r_debug.r_brk = reinterpret_cast<uintptr_t>(&_r_debug_state);
+  _r_debug.r_state = RT_CONSISTENT;
+  _r_debug.r_ldbase = base;
+
   // Process IRELATIVE relocations (ifunc resolvers).
   // Skips when no ifuncs are present in the binary.
   if (reinterpret_cast<uintptr_t>(__rela_iplt_start) !=
diff --git a/libc/test/integration/startup/linux/CMakeLists.txt b/libc/test/integration/startup/linux/CMakeLists.txt
index ef98af3f0a04d..a0885a9e70990 100644
--- a/libc/test/integration/startup/linux/CMakeLists.txt
+++ b/libc/test/integration/startup/linux/CMakeLists.txt
@@ -76,3 +76,13 @@ add_integration_test(
   SRCS
     irelative_no_ifunc_test.cpp
 )
+
+add_integration_test(
+  startup_r_debug_test
+  SUITE libc-startup-tests
+  SRCS
+    r_debug_test.cpp
+  DEPENDS
+    libc.include.link
+    libc.src.link._r_debug
+)
diff --git a/libc/test/integration/startup/linux/r_debug_test.cpp b/libc/test/integration/startup/linux/r_debug_test.cpp
new file mode 100644
index 0000000000000..334cf00f09482
--- /dev/null
+++ b/libc/test/integration/startup/linux/r_debug_test.cpp
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Loader test to check if r_debug is set correctly.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/link/_r_debug.h"
+#include "test/IntegrationTest/test.h"
+
+extern "C" [[gnu::weak]] ElfW(Dyn) _DYNAMIC[];
+
+TEST_MAIN() {
+  ASSERT_EQ(LIBC_NAMESPACE::_r_debug.r_version, 1);
+  ASSERT_EQ(LIBC_NAMESPACE::_r_debug.r_state, static_cast<int>(RT_CONSISTENT));
+  ASSERT_NE(LIBC_NAMESPACE::_r_debug.r_map,
+            static_cast<struct link_map *>(nullptr));
+  ASSERT_NE(LIBC_NAMESPACE::_r_debug.r_brk, static_cast<ElfW(Addr)>(0));
+  ASSERT_EQ(LIBC_NAMESPACE::_r_debug.r_ldbase,
+            LIBC_NAMESPACE::_r_debug.r_map->l_addr);
+
+  struct link_map *map = LIBC_NAMESPACE::_r_debug.r_map;
+  ASSERT_STREQ(map->l_name, "");
+  ASSERT_EQ(map->l_ld, _DYNAMIC);
+  ASSERT_EQ(map->l_next, static_cast<struct link_map *>(nullptr));
+  ASSERT_EQ(map->l_prev, static_cast<struct link_map *>(nullptr));
+
+  return 0;
+}



More information about the libc-commits mailing list