It would be good to provide a macOS definition of is_debugger_present as
per https://developer.apple.com/library/archive/qa/qa1361/_index.html
but that isn't included in this change.

libstdc++-v3/ChangeLog:

        * config.h.in: Regenerate.
        * configure: Regenerate.
        * configure.ac: Check for facilities needed by <debugging>.
        * include/Makefile.am: Add new header.
        * include/Makefile.in: Regenerate.
        * include/bits/version.def (debugging): Add.
        * include/bits/version.h: Regenerate.
        * src/c++26/Makefile.am: Add new file.
        * src/c++26/Makefile.in: Regenerate.
        * include/std/debugging: New file.
        * src/c++26/debugging.cc: New file.
        * testsuite/19_diagnostics/debugging/breakpoint.cc: New test.
        * testsuite/19_diagnostics/debugging/breakpoint_if_debugging.cc:
        New test.
        * testsuite/19_diagnostics/debugging/is_debugger_present.cc: New
        test.
---

I sent v1 of this patch in June last year:
https://gcc.gnu.org/pipermail/gcc-patches/2024-June/653293.html
My comments about testing with GDB still apply.

Compared to v1, this adds a void __gnu_cxx::attach_debugger(bool)
function that could be called by debuggers to announce that they are
attaching and detaching. It also adds a systemtap probe point to
std::breakpoint().

Tested x86_64-linux.

N.B. this only adds new symbols to the libstdc++exp.a archive, not to
libstdc++.so so it's not a risky change at this stage.

 libstdc++-v3/config.h.in                      |   9 +
 libstdc++-v3/configure                        |  22 +++
 libstdc++-v3/configure.ac                     |   9 +
 libstdc++-v3/include/Makefile.am              |   1 +
 libstdc++-v3/include/Makefile.in              |   1 +
 libstdc++-v3/include/bits/version.def         |   8 +
 libstdc++-v3/include/bits/version.h           |  10 ++
 libstdc++-v3/include/std/debugging            |  77 ++++++++
 libstdc++-v3/src/c++26/Makefile.am            |   4 +-
 libstdc++-v3/src/c++26/Makefile.in            |   7 +-
 libstdc++-v3/src/c++26/debugging.cc           | 165 ++++++++++++++++++
 .../19_diagnostics/debugging/breakpoint.cc    |  13 ++
 .../debugging/breakpoint_if_debugging.cc      |  13 ++
 .../debugging/is_debugger_present.cc          |  14 ++
 14 files changed, 350 insertions(+), 3 deletions(-)
 create mode 100644 libstdc++-v3/include/std/debugging
 create mode 100644 libstdc++-v3/src/c++26/debugging.cc
 create mode 100644 
libstdc++-v3/testsuite/19_diagnostics/debugging/breakpoint.cc
 create mode 100644 
libstdc++-v3/testsuite/19_diagnostics/debugging/breakpoint_if_debugging.cc
 create mode 100644 
libstdc++-v3/testsuite/19_diagnostics/debugging/is_debugger_present.cc

diff --git a/libstdc++-v3/config.h.in b/libstdc++-v3/config.h.in
index 77bbaf1beaa..3f1331ff36f 100644
--- a/libstdc++-v3/config.h.in
+++ b/libstdc++-v3/config.h.in
@@ -70,6 +70,9 @@
 /* Define to 1 if you have the `cosl' function. */
 #undef HAVE_COSL
 
+/* Define to 1 if you have the <debugapi.h> header file. */
+#undef HAVE_DEBUGAPI_H
+
 /* Define to 1 if you have the declaration of `strnlen', and to 0 if you
    don't. */
 #undef HAVE_DECL_STRNLEN
@@ -439,6 +442,9 @@
 /* Define to 1 if you have the <sys/param.h> header file. */
 #undef HAVE_SYS_PARAM_H
 
+/* Define to 1 if you have the <sys/ptrace.h> header file. */
+#undef HAVE_SYS_PTRACE_H
+
 /* Define to 1 if you have the <sys/resource.h> header file. */
 #undef HAVE_SYS_RESOURCE_H
 
@@ -850,6 +856,9 @@
 /* Define if nl_langinfo_l should be used for std::text_encoding. */
 #undef _GLIBCXX_USE_NL_LANGINFO_L
 
+/* Define if /proc/self/status should be used for <debugging>. */
+#undef _GLIBCXX_USE_PROC_SELF_STATUS
+
 /* Define if pthreads_num_processors_np is available in <pthread.h>. */
 #undef _GLIBCXX_USE_PTHREADS_NUM_PROCESSORS_NP
 
diff --git a/libstdc++-v3/configure b/libstdc++-v3/configure
index 819a1d82876..8143df91c38 100755
--- a/libstdc++-v3/configure
+++ b/libstdc++-v3/configure
@@ -54700,6 +54700,28 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 
 
+# For std::is_debugger_present
+case "$target_os" in
+  linux*)
+
+$as_echo "#define _GLIBCXX_USE_PROC_SELF_STATUS 1" >>confdefs.h
+
+    ;;
+esac
+for ac_header in sys/ptrace.h debugapi.h
+do :
+  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" 
"$ac_includes_default"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+  cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
 # Define documentation rules conditionally.
 
 # See if makeinfo has been installed and is modern enough
diff --git a/libstdc++-v3/configure.ac b/libstdc++-v3/configure.ac
index a6c01b29e94..83adb46454e 100644
--- a/libstdc++-v3/configure.ac
+++ b/libstdc++-v3/configure.ac
@@ -587,6 +587,15 @@ GLIBCXX_CHECK_FILEBUF_NATIVE_HANDLES
 # For std::text_encoding
 GLIBCXX_CHECK_TEXT_ENCODING
 
+# For std::is_debugger_present
+case "$target_os" in
+  linux*)
+    AC_DEFINE([_GLIBCXX_USE_PROC_SELF_STATUS],1,
+             [Define if /proc/self/status should be used for <debugging>.])
+    ;;
+esac
+AC_CHECK_HEADERS([sys/ptrace.h debugapi.h])
+
 # Define documentation rules conditionally.
 
 # See if makeinfo has been installed and is modern enough
diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am
index 537774c2668..6bb3f3dc3d6 100644
--- a/libstdc++-v3/include/Makefile.am
+++ b/libstdc++-v3/include/Makefile.am
@@ -67,6 +67,7 @@ std_headers = \
        ${std_srcdir}/codecvt \
        ${std_srcdir}/complex \
        ${std_srcdir}/condition_variable \
+       ${std_srcdir}/debugging \
        ${std_srcdir}/deque \
        ${std_srcdir}/execution \
        ${std_srcdir}/filesystem \
diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in
index 7b96b2207f8..81c3cfce4c4 100644
--- a/libstdc++-v3/include/Makefile.in
+++ b/libstdc++-v3/include/Makefile.in
@@ -423,6 +423,7 @@ std_freestanding = \
 @GLIBCXX_HOSTED_TRUE@  ${std_srcdir}/codecvt \
 @GLIBCXX_HOSTED_TRUE@  ${std_srcdir}/complex \
 @GLIBCXX_HOSTED_TRUE@  ${std_srcdir}/condition_variable \
+@GLIBCXX_HOSTED_TRUE@  ${std_srcdir}/debugging \
 @GLIBCXX_HOSTED_TRUE@  ${std_srcdir}/deque \
 @GLIBCXX_HOSTED_TRUE@  ${std_srcdir}/execution \
 @GLIBCXX_HOSTED_TRUE@  ${std_srcdir}/filesystem \
diff --git a/libstdc++-v3/include/bits/version.def 
b/libstdc++-v3/include/bits/version.def
index 2ec1b40bd38..092d0427e4a 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1841,6 +1841,14 @@ ftms = {
   };
 };
 
+ftms = {
+  name = debugging;
+  values = {
+    v = 202403;
+    cxxmin = 26;
+  };
+};
+
 ftms = {
   name = fstream_native_handle;
   values = {
diff --git a/libstdc++-v3/include/bits/version.h 
b/libstdc++-v3/include/bits/version.h
index 04c1349c84b..12892fc135d 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -2044,6 +2044,16 @@
 #endif /* !defined(__cpp_lib_constexpr_new) && 
defined(__glibcxx_want_constexpr_new) */
 #undef __glibcxx_want_constexpr_new
 
+#if !defined(__cpp_lib_debugging)
+# if (__cplusplus >  202302L)
+#  define __glibcxx_debugging 202403L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_debugging)
+#   define __cpp_lib_debugging 202403L
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_debugging) && defined(__glibcxx_want_debugging) */
+#undef __glibcxx_want_debugging
+
 #if !defined(__cpp_lib_fstream_native_handle)
 # if (__cplusplus >  202302L) && _GLIBCXX_HOSTED
 #  define __glibcxx_fstream_native_handle 202306L
diff --git a/libstdc++-v3/include/std/debugging 
b/libstdc++-v3/include/std/debugging
new file mode 100644
index 00000000000..4cf7e4acbc4
--- /dev/null
+++ b/libstdc++-v3/include/std/debugging
@@ -0,0 +1,77 @@
+// Debugging support -*- C++ -*-
+
+// Copyright The GNU Toolchain Authors.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// Under Section 7 of GPL version 3, you are granted additional
+// permissions described in the GCC Runtime Library Exception, version
+// 3.1, as published by the Free Software Foundation.
+
+// You should have received a copy of the GNU General Public License and
+// a copy of the GCC Runtime Library Exception along with this program;
+// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+// <http://www.gnu.org/licenses/>.
+
+/** @file include/debugging
+ *  This is a Standard C++ Library header.
+ */
+
+#ifndef _GLIBCXX_DEBUGGING
+#define _GLIBCXX_DEBUGGING 1
+
+#define __glibcxx_want_debugging
+#include <bits/version.h>
+
+#if __cpp_lib_debugging // C++ >= 26
+namespace std _GLIBCXX_VISIBILITY(default)
+{
+// N.B. _GLIBCXX_BEGIN_NAMESPACE_VERSION is not used here.
+
+/** Try to determine if the program is running under control of a debugger.
+ *
+ * On GNU/Linux systems this function will only return true if the program
+ * is being traced by another program which is known to be a debugger.
+ * This is determined by checking the command name of the tracing program
+ * against a list of known debuggers, such as "gdb".
+ *
+ * On other POSIX-based systems, this function will return true if the
+ * program is being traced by any other process, which means it can return
+ * true for non-debugger utilities that use the ptrace system call.
+ *
+ * @since C++26
+ */
+bool
+is_debugger_present() noexcept;
+
+/** Stop the program with a breakpoint or debug trap.
+ *
+ * The details of how a breakpoint is implemented are platform-specific.
+ * Some systems provide a special instruction, such as `int3` in x86.
+ * When no more appropriate mechanism is available, this will stop the
+ * program using `__builtin_trap()`. It might not be possible for the
+ * program to continue after such a breakpoint.
+ *
+ * @since C++26
+ */
+void
+breakpoint() noexcept;
+
+/** Stop the program if it is running under control of a debugger.
+ *
+ * @since C++26
+ */
+void
+breakpoint_if_debugging() noexcept;
+
+} // namespace std
+#endif
+#endif // _GLIBCXX_DEBUGGING
diff --git a/libstdc++-v3/src/c++26/Makefile.am 
b/libstdc++-v3/src/c++26/Makefile.am
index 5defa4ac481..4123b7dd369 100644
--- a/libstdc++-v3/src/c++26/Makefile.am
+++ b/libstdc++-v3/src/c++26/Makefile.am
@@ -35,7 +35,9 @@ else
 inst_sources =
 endif
 
-sources = text_encoding.cc
+sources = \
+         debugging.cc \
+         text_encoding.cc
 
 vpath % $(top_srcdir)/src/c++26
 
diff --git a/libstdc++-v3/src/c++26/Makefile.in 
b/libstdc++-v3/src/c++26/Makefile.in
index 77e73b2b265..1c317d6ac7c 100644
--- a/libstdc++-v3/src/c++26/Makefile.in
+++ b/libstdc++-v3/src/c++26/Makefile.in
@@ -121,7 +121,7 @@ CONFIG_CLEAN_FILES =
 CONFIG_CLEAN_VPATH_FILES =
 LTLIBRARIES = $(noinst_LTLIBRARIES)
 libc__26convenience_la_LIBADD =
-am__objects_1 = text_encoding.lo
+am__objects_1 = debugging.lo text_encoding.lo
 am__objects_2 =
 @GLIBCXX_HOSTED_TRUE@am_libc__26convenience_la_OBJECTS =  \
 @GLIBCXX_HOSTED_TRUE@  $(am__objects_1) $(am__objects_2)
@@ -430,7 +430,10 @@ headers =
 
 # XTEMPLATE_FLAGS = -fno-implicit-templates
 @ENABLE_EXTERN_TEMPLATE_TRUE@inst_sources = 
-sources = text_encoding.cc
+sources = \
+         debugging.cc \
+         text_encoding.cc
+
 @GLIBCXX_HOSTED_FALSE@libc__26convenience_la_SOURCES = 
 @GLIBCXX_HOSTED_TRUE@libc__26convenience_la_SOURCES = $(sources)  
$(inst_sources)
 
diff --git a/libstdc++-v3/src/c++26/debugging.cc 
b/libstdc++-v3/src/c++26/debugging.cc
new file mode 100644
index 00000000000..5ad85309ba1
--- /dev/null
+++ b/libstdc++-v3/src/c++26/debugging.cc
@@ -0,0 +1,165 @@
+// Implementation of <debugging> -*- C++ -*-
+
+// Copyright The GNU Toolchain Authors.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// Under Section 7 of GPL version 3, you are granted additional
+// permissions described in the GCC Runtime Library Exception, version
+// 3.1, as published by the Free Software Foundation.
+
+// You should have received a copy of the GNU General Public License and
+// a copy of the GCC Runtime Library Exception along with this program;
+// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+// <http://www.gnu.org/licenses/>.
+
+#include <debugging>
+
+#if __cpp_lib_debugging
+
+#if _GLIBCXX_USE_PROC_SELF_STATUS
+# include <fstream>
+# include <string>
+#endif
+
+#if _GLIBCXX_HAVE_SYS_PTRACE_H
+# include <sys/ptrace.h>
+# include <errno.h>
+#endif
+
+#if _GLIBCXX_HAVE_DEBUGAPI_H
+# include <debugapi.h>
+#endif
+
+#ifdef _GLIBCXX_HAVE_SYS_SDT_H
+# include <sys/sdt.h>
+/* We only want to use stap probes starting with v3.  Earlier versions
+   added too much startup cost.  */
+# if defined (STAP_PROBE) && _SDT_NOTE_TYPE >= 3
+#  define PROBE(name) STAP_PROBE(libstdcxx, name)
+# endif
+#endif
+
+#ifndef PROBE
+# define PROBE(name)
+#endif
+
+namespace
+{
+  volatile bool debugger_attached = false;
+}
+
+namespace __gnu_cxx
+{
+  // This should be called by debuggers when they attach and detach.
+  void attach_debugger(bool attached) { debugger_attached = attached; }
+}
+
+_GLIBCXX_WEAK_DEFINITION
+bool
+std::is_debugger_present() noexcept
+{
+  if (debugger_attached)
+    return true;
+
+#if _GLIBCXX_HOSTED
+# if _GLIBCXX_USE_PROC_SELF_STATUS
+  const string_view prefix = "TracerPid:\t";
+  ifstream in("/proc/self/status");
+  string line;
+  while (std::getline(in, line))
+    {
+      if (!line.starts_with(prefix))
+       continue;
+
+      string_view tracer = line;
+      tracer.remove_prefix(prefix.size());
+      if (tracer.size() == 1 && tracer[0] == '0') [[likely]]
+       return false; // Not being traced.
+
+      in.close();
+      string_view cmd;
+      string proc_dir = "/proc/" + string(tracer) + '/';
+      in.open(proc_dir + "comm"); // since Linux 2.6.33
+      if (std::getline(in, line)) [[likely]]
+       cmd = line;
+      else
+       {
+         in.close();
+         in.open(proc_dir + "cmdline");
+         if (std::getline(in, line))
+           cmd = line.c_str(); // Only up to first '\0'
+         else
+           return false;
+       }
+
+      for (auto i : {"gdb", "lldb"}) // known debuggers
+       if (cmd.ends_with(i))
+         return true;
+
+      // We found the TracerPid line, no need to do any more work.
+      return false;
+    }
+# endif
+# if _GLIBCXX_HAVE_SYS_PTRACE_H
+  if (::ptrace(PTRACE_TRACEME, 0, 1, 0) == -1)
+    return errno == EPERM;
+# endif
+# if _GLIBCXX_HAVE_DEBUGAPI_H && defined(_WIN32) && !defined(__CYGWIN__)
+  return IsDebuggerPresent();
+# endif
+#endif // HOSTED
+  return false;
+}
+
+void
+std::breakpoint() noexcept
+{
+  PROBE(std::breakpoint);
+
+#if _GLIBCXX_HAVE_DEBUGAPI_H && defined(_WIN32) && !defined(__CYGWIN__)
+  DebugBreak();
+#elif __has_builtin(__builtin_debugtrap)
+  __builtin_debugtrap(); // Clang
+#elif defined(__i386__) || defined(__x86_64__)
+  __asm__ volatile ("int3; nop");
+#elifdef __thumb__
+  __asm__ volatile (".inst 0xde01");
+#elifdef __aarch64__
+  __asm__ volatile (".inst 0xd4200000");
+#elifdef __arm__
+  __asm__ volatile (".inst 0xe7f001f0");
+#elifdef __riscv
+  /* section 2.8 in the RISC-V unprivileged ISA manual says for semi-hosted
+   * environments we want the sequence:
+   * slli x0, x0, 0x1f     # Entry NOP
+   * ebreak         # Break to debugger
+   * srai x0, x0, 7    # NOP encoding the semihosting call number 7
+   */
+  __asm__ volatile (".4byte 0x00100073");
+#elifdef __powerpc__
+  __asm__ volatile(".4byte 0x7d821008");
+#else
+  __builtin_trap();
+#endif
+} // If the debugger stops here, std::breakpoint() was called.
+
+// This is intentionally not defined inline. A non-inline definition allows
+// debuggers to insert a breakpoint on calls to the function, avoiding the
+// overhead of calling `is_debugger_present()`.
+void
+std::breakpoint_if_debugging() noexcept
+{
+  if (std::is_debugger_present()) [[unlikely]]
+    std::breakpoint();
+}
+
+#endif // __cpp_lib_debugging
diff --git a/libstdc++-v3/testsuite/19_diagnostics/debugging/breakpoint.cc 
b/libstdc++-v3/testsuite/19_diagnostics/debugging/breakpoint.cc
new file mode 100644
index 00000000000..ad24177f644
--- /dev/null
+++ b/libstdc++-v3/testsuite/19_diagnostics/debugging/breakpoint.cc
@@ -0,0 +1,13 @@
+// { dg-do run { target c++26 xfail c++26 } }
+// { dg-options "-lstdc++exp" }
+// { dg-require-cpp-feature-test __cpp_lib_debugging }
+#include <debugging>
+#include <type_traits>
+
+static_assert( noexcept(std::breakpoint()) );
+static_assert( std::is_void_v<decltype(std::breakpoint())> );
+
+int main()
+{
+  std::breakpoint();
+}
diff --git 
a/libstdc++-v3/testsuite/19_diagnostics/debugging/breakpoint_if_debugging.cc 
b/libstdc++-v3/testsuite/19_diagnostics/debugging/breakpoint_if_debugging.cc
new file mode 100644
index 00000000000..26461836ef7
--- /dev/null
+++ b/libstdc++-v3/testsuite/19_diagnostics/debugging/breakpoint_if_debugging.cc
@@ -0,0 +1,13 @@
+// { dg-do run { target c++26 } }
+// { dg-options "-lstdc++exp" }
+// { dg-require-cpp-feature-test __cpp_lib_debugging }
+#include <debugging>
+#include <type_traits>
+
+static_assert( noexcept(std::breakpoint_if_debugging()) );
+static_assert( std::is_void_v<decltype(std::breakpoint_if_debugging())> );
+
+int main()
+{
+  std::breakpoint_if_debugging();
+}
diff --git 
a/libstdc++-v3/testsuite/19_diagnostics/debugging/is_debugger_present.cc 
b/libstdc++-v3/testsuite/19_diagnostics/debugging/is_debugger_present.cc
new file mode 100644
index 00000000000..8dbfa694243
--- /dev/null
+++ b/libstdc++-v3/testsuite/19_diagnostics/debugging/is_debugger_present.cc
@@ -0,0 +1,14 @@
+// { dg-do run { target c++26 } }
+// { dg-options "-lstdc++exp" }
+// { dg-require-cpp-feature-test __cpp_lib_debugging }
+#include <debugging>
+#include <type_traits>
+#include <testsuite_hooks.h>
+
+static_assert( noexcept(std::is_debugger_present()) );
+static_assert( std::is_same_v<decltype(std::is_debugger_present()), bool> );
+
+int main()
+{
+  VERIFY( ! std::is_debugger_present() );
+}
-- 
2.49.0

Reply via email to