diff mbox series

libstdc++: Implement C++26 <debugging> features (P2546R5)

Message ID 20240601102446.878286-1-jwakely@redhat.com
State New
Headers show
Series libstdc++: Implement C++26 <debugging> features (P2546R5) | expand

Commit Message

Jonathan Wakely June 1, 2024, 10:22 a.m. UTC
Here's an implementation of the C++26 <debugging> header.

We should really have some tests that invoke GDB and check that the
breakpoint works when a debugger is attached. That seems tricky to do
via the main conformance.exp testsuite. It could be done via the
prettyprinters.exp testsuite, which already uses GDB, but would require
some changes to the gdb-test procedure, which assumes it needs to insert
its own breakpoint at marked spots in the code.

I think we could add this without those tests, and improve it later.

-- >8 --

It would be good to provide a macOS definition of is_debugger_present,
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.
---
 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         |   9 ++
 libstdc++-v3/include/bits/version.h           |  10 ++
 libstdc++-v3/include/std/debugging            |  82 ++++++++++++
 libstdc++-v3/src/c++26/Makefile.am            |   4 +-
 libstdc++-v3/src/c++26/Makefile.in            |   7 +-
 libstdc++-v3/src/c++26/debugging.cc           | 126 ++++++++++++++++++
 .../19_diagnostics/debugging/breakpoint.cc    |   9 ++
 .../debugging/breakpoint_if_debugging.cc      |   9 ++
 13 files changed, 295 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

Comments

Ulrich Drepper June 1, 2024, 8:51 p.m. UTC | #1
Hey Jon,

I think we should give debuggers a chance to announce themselves by
providing an entry point they can call (in the inferior) which sets a
flag.  A set flag plus a tracer PID would then be a sufficient
indicator.  The remaining code should also stay but some additional
code can be added:

On Sat, Jun 1, 2024 at 12:22 PM Jonathan Wakely <jwakely@redhat.com> wrote:
> +_GLIBCXX_WEAK_DEFINITION
> +bool
> +std::is_debugger_present() noexcept
> +{
> +#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.
> +

Here add something like:

   if (debugger_announced)
     return true;


> +      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

And then add

namespace {
  bool debugger_announced = false;
}
auto debugger_attached()
{
  debugger_announced = true;
  return std::breakpoint;
}

I suggest to also return the breakpoint function to allow debuggers to
do something clever with it (e.g., set a breakpoint on the entry
instead of having to catch the fallout of the instruction that is
issued in the function (there might be a difference).


With the function any debugger not covered by the existing test can
make itself known.
Florian Weimer June 3, 2024, 10:20 a.m. UTC | #2
* Jonathan Wakely:

> +/** 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;

Would it make sense to have a special function symbol for this, on which
the debugger sets the breakpoint?  We already have this for a slightly
different purpose in glibc, see _dl_debug_state.

Otherwise you'll get crashes if the debugger detection delivers a false
positive (e.g. when running under an strace-equivalent if libstdc++
cannot check the process name).

Or is_debugger_present should perhaps read a volatile bool that is
updated by the debugger as needed, and not bother with any ptrace
checks.

Thanks,
Florian
Ulrich Drepper June 3, 2024, 11:19 a.m. UTC | #3
On Mon, Jun 3, 2024 at 12:20 PM Florian Weimer <fweimer@redhat.com> wrote:

> Would it make sense to have a special function symbol for this, on which
> the debugger sets the breakpoint?  […]


Jon and I discussed more details off-list.  Hopefully a more complete
version is coming soon-ish.
Peter0x44 June 3, 2024, 1:34 p.m. UTC | #4
On 2024-06-01 03:22, Jonathan Wakely wrote:
> Here's an implementation of the C++26 <debugging> header.
> 
> We should really have some tests that invoke GDB and check that the
> breakpoint works when a debugger is attached. That seems tricky to do
> via the main conformance.exp testsuite. It could be done via the
> prettyprinters.exp testsuite, which already uses GDB, but would require
> some changes to the gdb-test procedure, which assumes it needs to 
> insert
> its own breakpoint at marked spots in the code.
> 
> I think we could add this without those tests, and improve it later.
> 
> -- >8 --
> 
> It would be good to provide a macOS definition of is_debugger_present,
> 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.
> ---
>  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         |   9 ++
>  libstdc++-v3/include/bits/version.h           |  10 ++
>  libstdc++-v3/include/std/debugging            |  82 ++++++++++++
>  libstdc++-v3/src/c++26/Makefile.am            |   4 +-
>  libstdc++-v3/src/c++26/Makefile.in            |   7 +-
>  libstdc++-v3/src/c++26/debugging.cc           | 126 ++++++++++++++++++
>  .../19_diagnostics/debugging/breakpoint.cc    |   9 ++
>  .../debugging/breakpoint_if_debugging.cc      |   9 ++
>  13 files changed, 295 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
> 
> diff --git a/libstdc++-v3/config.h.in b/libstdc++-v3/config.h.in
> index 486ba450749..07203815459 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
> @@ -436,6 +439,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
> 
> @@ -847,6 +853,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 5645e991af7..55ddf36a7f1 100755
> --- a/libstdc++-v3/configure
> +++ b/libstdc++-v3/configure
> @@ -54612,6 +54612,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 ccb24a82be7..96b412fb7ae 100644
> --- a/libstdc++-v3/configure.ac
> +++ b/libstdc++-v3/configure.ac
> @@ -573,6 +573,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 422a0f4bd0a..94eca0b42f9 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 9fd4ab4848c..299597ef657 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 683b967d54b..ea271901e5d 100644
> --- a/libstdc++-v3/include/bits/version.def
> +++ b/libstdc++-v3/include/bits/version.def
> @@ -1752,6 +1752,15 @@ ftms = {
>    };
>  };
> 
> +ftms = {
> +  name = debugging;
> +  values = {
> +    v = 202403;
> +    cxxmin = 26;
> +    extra_cond = "_GLIBCXX_USE_PROC_SELF_STATUS || 
> _GLIBCXX_HAVE_SYS_PTRACE_H || _GLIBCXX_HAVE_DEBUGAPI_H";
> +  };
> +};
> +
>  ftms = {
>    name = fstream_native_handle;
>    values = {
> diff --git a/libstdc++-v3/include/bits/version.h 
> b/libstdc++-v3/include/bits/version.h
> index 4850041c0a3..50d2d604ce9 100644
> --- a/libstdc++-v3/include/bits/version.h
> +++ b/libstdc++-v3/include/bits/version.h
> @@ -1953,6 +1953,16 @@
>  #endif /* !defined(__cpp_lib_unreachable) && 
> defined(__glibcxx_want_unreachable) */
>  #undef __glibcxx_want_unreachable
> 
> +#if !defined(__cpp_lib_debugging)
> +# if (__cplusplus >  202302L) && (_GLIBCXX_USE_PROC_SELF_STATUS || 
> _GLIBCXX_HAVE_SYS_PTRACE_H || _GLIBCXX_HAVE_DEBUGAPI_H)
> +#  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..76f81e23e72
> --- /dev/null
> +++ b/libstdc++-v3/include/std/debugging
> @@ -0,0 +1,82 @@
> +// 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)
> +{
> +_GLIBCXX_BEGIN_NAMESPACE_VERSION
> +
> +/** 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 such as strace.
> + *
> + * @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
> + */
> +inline void
> +breakpoint_if_debugging() noexcept
> +{
> +  if (std::is_debugger_present()) [[unlikely]]
> +    breakpoint();
> +}
> +
> +_GLIBCXX_END_NAMESPACE_VERSION
> +} // 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 000ced1f501..551d08d583e 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..150ac1ed2e7
> --- /dev/null
> +++ b/libstdc++-v3/src/c++26/debugging.cc
> @@ -0,0 +1,126 @@
> +// 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
> +
> +_GLIBCXX_WEAK_DEFINITION
> +bool
> +std::is_debugger_present() noexcept
> +{
> +#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
> +{
> +#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");
Worth noting, currently gcc has some bugs around its handling of int3,
https://sourceware.org/bugzilla/show_bug.cgi?id=31194
int3;nop seems to work around it okay. __builtin_debugtrap() of clang 
does run into the same issues.
> +#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.
> +
> +#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..ee572dc9424
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/19_diagnostics/debugging/breakpoint.cc
> @@ -0,0 +1,9 @@
> +// { dg-do run { target c++26 xfail c++26 } }
> +// { dg-options "-lstdc++exp" }
> +// { dg-require-cpp-feature-test __cpp_lib_debugging }
> +#include <debugging>
> +
> +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..02ac245daaf
> --- /dev/null
> +++ 
> b/libstdc++-v3/testsuite/19_diagnostics/debugging/breakpoint_if_debugging.cc
> @@ -0,0 +1,9 @@
> +// { dg-do run { target c++26 } }
> +// { dg-options "-lstdc++exp" }
> +// { dg-require-cpp-feature-test __cpp_lib_debugging }
> +#include <debugging>
> +
> +int main()
> +{
> +  std::breakpoint_if_debugging();
> +}

Regards,
Peter D.
Jonathan Wakely June 3, 2024, 3:14 p.m. UTC | #5
On Mon, 3 Jun 2024 at 14:36, Peter0x44 wrote:
> > +void
> > +std::breakpoint() noexcept
> > +{
> > +#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");
> Worth noting, currently gcc has some bugs around its handling of int3,

s/gcc/gdb/ ?

> https://sourceware.org/bugzilla/show_bug.cgi?id=31194
> int3;nop seems to work around it okay. __builtin_debugtrap() of clang
> does run into the same issues.

It seemed to work OK for me, maybe because there's no code after it?

void breakpoint() {
__asm__ volatile ("int3);
}
Peter0x44 June 3, 2024, 4:05 p.m. UTC | #6
3 Jun 2024 4:14:28 pm Jonathan Wakely <jwakely@redhat.com>:

> On Mon, 3 Jun 2024 at 14:36, Peter0x44 wrote:
>>> +void
>>> +std::breakpoint() noexcept
>>> +{
>>> +#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");
>> Worth noting, currently gcc has some bugs around its handling of int3,
>
> s/gcc/gdb/ ?
Yes, my bad
>
>> https://sourceware.org/bugzilla/show_bug.cgi?id=31194
>> int3;nop seems to work around it okay. __builtin_debugtrap() of clang
>> does run into the same issues.
>
> It seemed to work OK for me, maybe because there's no code after it?
I suspect it won't matter for the tests, the assertion failure is only if 
you step after hitting the int3. I just figured I'd mention it as a heads 
up. It would affect users writing code.
>
> void breakpoint() {
> __asm__ volatile ("int3);
> }
Andrew Pinski June 3, 2024, 4:11 p.m. UTC | #7
On Mon, Jun 3, 2024 at 9:06 AM Peter0x44 <peter0x44@disroot.org> wrote:
>
> 3 Jun 2024 4:14:28 pm Jonathan Wakely <jwakely@redhat.com>:
>
> > On Mon, 3 Jun 2024 at 14:36, Peter0x44 wrote:
> >>> +void
> >>> +std::breakpoint() noexcept
> >>> +{
> >>> +#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");
> >> Worth noting, currently gcc has some bugs around its handling of int3,
> >
> > s/gcc/gdb/ ?
> Yes, my bad
> >
> >> https://sourceware.org/bugzilla/show_bug.cgi?id=31194
> >> int3;nop seems to work around it okay. __builtin_debugtrap() of clang
> >> does run into the same issues.
> >
> > It seemed to work OK for me, maybe because there's no code after it?
> I suspect it won't matter for the tests, the assertion failure is only if
> you step after hitting the int3. I just figured I'd mention it as a heads
> up. It would affect users writing code.

Note there is a request for adding __builtin_break to GCC;
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84595 .
(and one for __builtin_debugtrap;
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99299 ).
When __builtin_break is added, I think we should use that here if it
is available (as a higher priority than __builtin_debugtrap).

Thanks,
Andrew


> >
> > void breakpoint() {
> > __asm__ volatile ("int3);
> > }
diff mbox series

Patch

diff --git a/libstdc++-v3/config.h.in b/libstdc++-v3/config.h.in
index 486ba450749..07203815459 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
@@ -436,6 +439,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
 
@@ -847,6 +853,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 5645e991af7..55ddf36a7f1 100755
--- a/libstdc++-v3/configure
+++ b/libstdc++-v3/configure
@@ -54612,6 +54612,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 ccb24a82be7..96b412fb7ae 100644
--- a/libstdc++-v3/configure.ac
+++ b/libstdc++-v3/configure.ac
@@ -573,6 +573,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 422a0f4bd0a..94eca0b42f9 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 9fd4ab4848c..299597ef657 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 683b967d54b..ea271901e5d 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1752,6 +1752,15 @@  ftms = {
   };
 };
 
+ftms = {
+  name = debugging;
+  values = {
+    v = 202403;
+    cxxmin = 26;
+    extra_cond = "_GLIBCXX_USE_PROC_SELF_STATUS || _GLIBCXX_HAVE_SYS_PTRACE_H || _GLIBCXX_HAVE_DEBUGAPI_H";
+  };
+};
+
 ftms = {
   name = fstream_native_handle;
   values = {
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index 4850041c0a3..50d2d604ce9 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -1953,6 +1953,16 @@ 
 #endif /* !defined(__cpp_lib_unreachable) && defined(__glibcxx_want_unreachable) */
 #undef __glibcxx_want_unreachable
 
+#if !defined(__cpp_lib_debugging)
+# if (__cplusplus >  202302L) && (_GLIBCXX_USE_PROC_SELF_STATUS || _GLIBCXX_HAVE_SYS_PTRACE_H || _GLIBCXX_HAVE_DEBUGAPI_H)
+#  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..76f81e23e72
--- /dev/null
+++ b/libstdc++-v3/include/std/debugging
@@ -0,0 +1,82 @@ 
+// 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)
+{
+_GLIBCXX_BEGIN_NAMESPACE_VERSION
+
+/** 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 such as strace.
+ *
+ * @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
+ */
+inline void
+breakpoint_if_debugging() noexcept
+{
+  if (std::is_debugger_present()) [[unlikely]]
+    breakpoint();
+}
+
+_GLIBCXX_END_NAMESPACE_VERSION
+} // 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 000ced1f501..551d08d583e 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..150ac1ed2e7
--- /dev/null
+++ b/libstdc++-v3/src/c++26/debugging.cc
@@ -0,0 +1,126 @@ 
+// 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
+
+_GLIBCXX_WEAK_DEFINITION
+bool
+std::is_debugger_present() noexcept
+{
+#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
+{
+#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");
+#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.
+
+#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..ee572dc9424
--- /dev/null
+++ b/libstdc++-v3/testsuite/19_diagnostics/debugging/breakpoint.cc
@@ -0,0 +1,9 @@ 
+// { dg-do run { target c++26 xfail c++26 } }
+// { dg-options "-lstdc++exp" }
+// { dg-require-cpp-feature-test __cpp_lib_debugging }
+#include <debugging>
+
+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..02ac245daaf
--- /dev/null
+++ b/libstdc++-v3/testsuite/19_diagnostics/debugging/breakpoint_if_debugging.cc
@@ -0,0 +1,9 @@ 
+// { dg-do run { target c++26 } }
+// { dg-options "-lstdc++exp" }
+// { dg-require-cpp-feature-test __cpp_lib_debugging }
+#include <debugging>
+
+int main()
+{
+  std::breakpoint_if_debugging();
+}