Document number:

ISO/IEC/JTC1/SC22/WG21/P2546R0

Date:

2022-02-14

Audience:

SG15, LEWG

Reply-to:

René Ferdinand Rivera Morell, grafikrobot@gmail.com

Project:

ISO/IEC JTC1/SC22/WG21 14882: Programming Language — C++

1. Abstract

This paper proposes to add utilities to aid in interacting with debugging tools.

This is a merger, and successor, to P2514 and P2515. [1] [2] Of which P2514 is a successor to P1279. [3]

2. Revision History

2.1. Revision 0 (February 2022)

Initial text based on P2514 and P2515. [1] [2] Changes include:

  • Merged into one proposal with breakpoint and is_debugger_present.

  • Change breakpoint to be unconditional.

  • Added breakpoint_if_debugging as a conditional break.

  • Added feature test macro.

  • Expanded implementation experience with explanations of usage context.

  • Changed is_debugger_present description to say that it should be an immediate query.

3. Motivation

There are many scenarios where doing something special when your program is running in a debugger is important. At times interacting with the debugger programmatically can help enhance the debugging experience when the debugger on its own is lacking.

Implementation experience has shown that: this is a desired set of features as it’s implemented in many different code bases, it is difficult to implement the functionality correctly for users without deep platform knowledge. Hence the C++ community would benefit from having this implemented by the platform owners in the standard.

3.1. Is Debugger Present

Knowing when a program is running in a debugger with std::is_debugger_present is a first step in enabling such functionality as:

  • allowing printing out extra output to help diagnose problems,

  • executing extra test code,

  • displaying an extra user interface to help in debugging,

  • and more.

3.2. Breakpoint

Controlling when a debugger stops in your program with std::breakpoint allows for runtime control of breakpoints beyond what might be available from a debugger while not causing the program to exit. For example:

  • breaking when an infrequent non-critical condition is detected,

  • allowing programmatic control with complex runtime sensitive conditions,

  • breaking on user input to inspect context in interactive programs without needing to switch to the debugger application,

  • and more.

4. Design

4.1. Unconditional Breakpoint

The goal of the std::breakpoint function is to "break" or pause the running program when called. Having an unconditional, i.e. attempts to break even if the debugger is or is not actually monitoring the program allows for use in conditions where it is not possible to detect if a debugger is present.

Implementations are expected to optimize the code generated to be as minimal as possible for the platform. For example, on X86 it’s expected that this produces a single INT3 instruction. The goal in this expectation is to place the debugger as close as possible in the caller of breakpoint() to improve the debugging experience for users.

If an implementation is unable to implement the breakpoint function it’s preferred for the implementation to do nothing and return.

4.2. Conditional Breakpoint

The goal of the std::breakpoint_if_debugging function is to "break" when being debugged but to act as though it is a no-op when it is executing normally.

Although it’s trivial for users to implement a conditional break, it’s common enough that there is utility in providing a ready to use implementation.

4.3. Debugger Present

The goal of the std::is_debugger_present function is to inform when a program is executing under the control of a debugger monitoring program. The interface is minimally simple to avoid having to reduce the user from having to know the intricacies of debugger operation. This is a feature that requires arcane platform knowledge for most platforms. But it is knowledge that is readily available to the platform tooling implementors.

Existing implementations of this functionality vary in how frequently they are expected to be called. Previously the proposal suggested that it would help to cache the debugger present query to avoid frequent repetition of the possible expensive query. But, first, doing that was not found to be done in any of the existing implementations. Second, doing so would add to the implementation complexity for something that can be better controlled by the user code. And, third, it would impact the std::breakpoint_if_debugging function to need to forward the argument to pass along to control the caching choice.

4.4. Hosted and Freestanding

The debugging support functionality is particularly useful in situations where it’s difficult to debug in traditional hosted context. For example when the debugger is running on a development host machine while the program is running on specialize freestanding environment. In such situations it can be impossible to determine if a debugger is present remotely, and almost certainly unlikely that a debugger can run in the target environment. As such the debugger support in this proposal is expected to be supported, as best as possible, in freestanding environments. The wording reflects that by having maximum flexibility in implementation.

4.5. Impact On the Standard

This proposal adds a utility header (debugging) with the new declarations.

5. Implementation Experience

5.1. Reference Implementation

A full reference implementation exists as a proof of concept. [4] It implements the full functionality for at least Windows, macOS, and Linux.

In addition to the prototype implementation there are the following, full or partial, equivalent implementations of the functions in common compilers and libraries.

5.2. Microsoft® C/C++ Optimizing Compiler

The Microsoft® compiler provides a __debugbreak function that implements an unconditional break. [5]

5.3. Microsoft® Win32

The Windows® Win32 provides an IsDebuggerPresent function in the OS that implements querying if a debugger is tracing the calling process. [6]

5.4. LLVM Clang

Clang provides a __builtin_debugtrap function that implements an unconditional break. [7]

5.5. arm Keil, ARM® Compiler

The arm Keil armcc compiler provides a __breakpoint function that implements an unconditional break. [8]

5.6. Portable Snippets

The "Portable Snippets" library [9] includes a psnip_trap function that implements an unconditional breakpoint in a variety of platforms and architectures. [10]

The reference implementation [4] uses psnip_trap to implement the unconditional breakpoint function.

5.7. Debug Break

The "Debug Break" library provides a single debug_break function that attempts to implement an unconditional debugger break. [11]

5.8. Boost.Test

The Boost.Test library implements an unconditional break in a debugger_break function. [12] And provides an under_debugger function that implements an immediate is_debugger_present function for Windows®, UNIX®, and macOS®. [13]

The two functions are used to implement an attach_debugger(bool) function that programmatically runs a debugger to trace the running program. [14]

5.9. EASTL

The EASTL library provides a EASTL_DEBUG_BREAK() macro that implements an unconditional breakpoint. [15]

The EASTL_DEBUG_BREAK() macro is used to implement breaking into the debugger on failure in the EASTL_ASSERT(expression) macro.

5.10. Catch2

The Catch2 library implements an internal and immediate isDebuggerActive function equivalent to is_debugger_present for macOS® and Linux. [16] It also provides a CATCH_TRAP macro that implements an unconditional breakpoint and a CATCH_BREAK_INTO_DEBUGGER macro that implements a conditional break per breakpoint_if_debugging. [17]

The CATCH_BREAK_INTO_DEBUGGER macro is used to cause failed assertions to pause in the debugger, if present. In addition to isDebuggerActive being used to implement the CATCH_BREAK_INTO_DEBUGGER macro, it’s also used to enable console text color output.

5.11. JUCE

The JUCE open-source cross-platform C++ application framework provides a juce_isRunningUnderDebugger function that implements an immediate is_debugger_present. [18] It also provides a JUCE_BREAK_IN_DEBUGGER macro that implements an unconditional break. [19]

In JUCE the two are used implement a conditional breakpoint when an assertion fails in the provided jassert and jassertquiet. The user perceived feature is the ability to write assert checks that can be inspected in context when running in a debugger.

The juce_isRunningUnderDebugger function is also made available as a Process::isRunningUnderDebugger method. Making it available to JUCE users in their applications to support user specific features.

5.12. Dear ImGui

Dear ImGui provides an IM_DEBUG_BREAK() macro that implements an unconditional breakpoint. [20]

In addition to being available for users, the IM_DEBUG_BREAK() macro is used to provide a GUI button that will break into the debugger on demand.

5.13. AWS C SDK

The Amazon Web Services SDK for C provides a aws_is_debugger_present function which implements an immediate is_debugger_present. [21] And also provides a aws_debug_break function that implements a conditional break, i.e. breakpoint_if_debugging. [22]

The implementation is of these functions have platform support for Windows and POSIX.

The aws_debug_break function is used to implement the aws_fatal_assert function. Which in addition to conditionally breaking into the debugger also prints out the assertion info and backtrace. Which in turn is used in the AWS_FATAL_ASSERT macro.

5.14. Unreal® Engine

Unreal® Engine [23] is a full blown game development environment composed of an IDE and more than a dozen different programs written using a common application framework. The engine provides an IsDebuggerPresent class function that implements an immediate is_debugger_present.

Unreal® Engine provides an implementation of the IsDebuggerPresent function in common platforms like Windows, macOS, Linux/POSIX, and Android. It also has implementations for a handful proprietary platforms like game consoles and virtual reality headsets.

Unreal® Engine also provides a UE_DEBUG_BREAK macro that implements a conditional break. Like the IsDebuggerPresent function this conditional break is implemented in many of the same platforms. The UE_DEBUG_BREAK macro uses IsDebuggerPresent to do the debugger conditional check.

The IsDebuggerPresent function has varied uses in Unreal® Engine: to log extra diagnostic output when certain inspection functions are called, to choose doing a debug break when present or to print out a stack trace instead, to prevent launching child parallel processes to allow debugging of normally distributed tasks, to disable auto-save on crash functionality, to turn off platform crash handling, to implement "wait for debugger" synchronization points, to add extra per thread context information to aid in finding task specific threads among the dozens of threads running, to prevent automated crash reporting, and to present GUI elements only when debugging.

6. Polls

6.1. SG15: P2514R0 and P2515R0 (2022-01-21)

SG15 approves of the design direction of P2514R0 and P2515R0 with the suggested changes of merging the two papers and adding an unconditional breakpoint interface.

SF F N A SA

2

6

0

0

0

Attendance: 8

Polls relating to the antecedent proposal P1279 are also of relevance. They can found in the corresponding GitHub issue. [24]

7. Wording

Wording is relative to N4868. [25]

7.1. Feature Test Macro

In [version.syn] add:

#define __cpp_lib_debugging YYYYMML // also in <debugging>

7.2. Library

Add a new entry to General utilities library summary [tab:utilities.summary] table.

[debugging]

Debugging

<debugging>

Add a new entry to the "C++ headers for freestanding implementations" table [tab:headers.cpp.fs].

[debugging]

Debugging

<debugging>

Add section to General utilities library [utilities].

7.2.1. Debugging [debugging]

7.2.1.1. In general [debugging.general]

This subclause [debugging] describes functionality to introspect and interact with implementation-defined behavior of the executing program.

[ Note 1: The facilities provided by the debugging functionality are expected to interact with a program that may be tracing the execution of a C++ program. Most commonly such a tracing program would be a debugger.  — end note ]

7.2.1.2. Header <debugging> synopsis [debugging.syn]
namespace std {
	// [debugging.utility], utility
	void breakpoint() noexcept;
	void breakpoint_if_debugging() noexcept;
	bool is_debugger_present() noexcept;
}
7.2.1.3. Utility [debugging.utility]

void breakpoint() noexcept;

Effects: Effects when invoked are implementation-defined behavior.

[ Note 1: When the function is invoked it is expected that the program’s execution temporarily halts and execution is handed to the debugger until such a time as: the program is terminated by the debugger or, the debugger resumes execution of the program as if the function was not invoked.  — end note ]

[ Note 1: Implementations should prefer doing nothing, instead of terminating, if they can’t provide the desired effect of halting the debugger.  — end note ]

void breakpoint_if_debugging() noexcept;

Effects: Equivalent to:

if (is_debugger_present()) breakpoint();

bool is_debugger_present() noexcept;

Returns: Returns an implementation-defined value.

[ Note 1: Recomended practice: if the program is currently running in the context of being monitored by a debugger an implementation should return true. An implementation should always perform an immediate query, as needed, to determine if the program is monitored by a debugger. On Windows, or equivalent, systems it’s expected this will be implemented by calling the ::IsDebuggerPresent() Win32 function. On POSIX it’s expected that this will check for a tracer parent process, with best effort determination that such a tracer parent process is a debugger.  — end note ]

8. Acknowledgements

Thank you Isabella Muerte for the initial proposal from which this paper steals a good amount of text.


1. P2514R0 std::breakpoint, René Ferdinand Rivera Morell 2021-12-30 (https://wg21.link/P2514R0)
2. P2515R0 std::is_debugger_present, René Ferdinand Rivera Morell 2021-12-29 (https://wg21.link/P2515R0)
3. P1279 std::breakpoint, Isabella Muerte 2018-10-05 (https://wg21.link/P1279)
4. Debugging prototype implementation (https://github.com/grafikrobot/debugging)
5. Microsoft compiler __debugbreak intrinsic (https://docs.microsoft.com/en-us/cpp/intrinsics/debugbreak)
6. Win32 IsDebuggerPresent (https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-isdebuggerpresent)
7. LLVM Clang __builtin_debugtrap (https://clang.llvm.org/docs/LanguageExtensions.html#builtin-debugtrap)
8. armKEIL __breakpoint intrinsic (https://www.keil.com/support/man/docs/armcc/armcc_chr1359124993371.htm)
9. Portable Snippets (https://github.com/nemequ/portable-snippets)
10. Portable Snippets Debug Trap (https://github.com/nemequ/portable-snippets/tree/master/debug-trap)
11. Debug Break Library (https://github.com/scottt/debugbreak)
12. Boost.Test Library debugger_break (https://github.com/boostorg/test/blob/boost-1.78.0/include/boost/test/impl/debug.ipp#L708)
13. Boost.Test Library under_debugger (https://www.boost.org/doc/libs/1_78_0/libs/test/doc/html/boost/debug/under_debugger.html)
14. Boost.Test Library attach_debugger(bool) (https://www.boost.org/doc/libs/1_78_0/libs/test/doc/html/boost/debug/attach_debugger.html)
15. EASTL EASTL_DEBUG_BREAK (https://github.com/electronicarts/EASTL/blob/3.18.00/include/EASTL/internal/config.h#L613)
16. Catch2 isDebuggerActive (https://github.com/catchorg/Catch2/blob/devel/src/catch2/internal/catch_debugger.cpp)
17. Catch2 CATCH_TRAP and CATCH_BREAK_INTO_DEBUGGER (https://github.com/catchorg/Catch2/blob/v3.0.0-preview4/src/catch2/internal/catch_debugger.hpp)
18. JUCE juce_isRunningUnderDebugger (https://github.com/juce-framework/JUCE/blob/6.1.5/modules/juce_core/juce_core.h#L218)
19. JUCE JUCE_BREAK_IN_DEBUGGER (https://github.com/juce-framework/JUCE/blob/6.1.5/modules/juce_core/system/juce_PlatformDefs.h#L63)
20. Dear ImGui IM_DEBUG_BREAK (https://github.com/ocornut/imgui/blob/v1.86/imgui_internal.h#L257)
21. AWS C Common aws_is_debugger_present (https://github.com/awslabs/aws-c-common/blob/v0.6.19/include/aws/common/system_info.h#L51)
22. AWS C Common aws_debug_break (https://github.com/awslabs/aws-c-common/blob/v0.6.19/include/aws/common/system_info.h#L55)
23. Unreal® Engine (https://www.unrealengine.com)
24. P1279 GitHub Issue (https://github.com/cplusplus/papers/issues/307)
25. N4868 Working Draft, Standard for Programming Language C++ 2020-10-18 (https://wg21.link/N4868)