- 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++
2. Revision History
2.1. Revision 0 (February 2022)
-
Merged into one proposal with
breakpoint
andis_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.
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
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 |
|
Add a new entry to the "C++ headers for freestanding implementations" table [tab:headers.cpp.fs].
[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 ]