A surprisingly useful debugger trick I know is to add
// in your "include me everywhere" base.h. if you have c++17 I guess you can make these inline variables
extern bool debug1;
extern bool debug2;
extern bool debug3;
extern bool debug4;
// somewhere low level
debug1 = glfwGetKey( window, GLFW_KEY_F1 ) == GLFW_PRESS;
...
to your codebase and combine it with the article to get fine grained conditional breakpoints. I can’t think of any good examples when this was really helpful lately but as a basic example you can do stuff like:
A useful tactic enough that I keep a generalised version in the client end of the windowing IPC libraries, re-using the same path to sneak in debugger script with the intended break/watchpoints/…
I’ve definitely used that before. If tying it to a button is tricky/too much effort in your runtime environment, you can also hit pause in the debugger, modify the variable’s state directly in the debugger, then resume.
Some related strategies:
Resumable asserts. I first saw these used almost 20 years ago in my gamedev days(*) and basically a failed assertion stops the world, but depending on what button you press next, it will either drop you into the debugger, continue in spite of the failed assertion, or disable that assertion entirely for the remainder of execution.
Out of band input devices. On a game that supported up to 2 controllers, a third controller would (in debug builds) provide a bunch of debug functionality, such as controlling the soft assertions above, toggling diagnostic overlays, pausing/resuming or single-stepping the simulation, taking over the camera, etc. I’ve used this technique on non-game code as well though: game controller inputs are usually no-ops by default on desktop and mobile systems, and can frequently also be captured by non-focused apps. (So if you have a foreground app controlled by keyboard and mouse, you can also directly control a background/helper tool with a game controller.)
You can add a backend console to your code that’s accessible over the network. (or perhaps via IPC) For example, by embedding a webserver in debug builds which allows you to inspect complex state that’s not easily visualised in a debugger. (A “light” version of this is dumping logs/tracing as an HTML file rather than plain text, for when you’re still convinced it’s not worth the effort to embed a webserver.) In a project as large as a game, you might want to add a live connection with other tooling, such as a level editor that pushes any edits directly into the running game. (But I’ve done it in other types of projects too!)
(*) I suspect gamedev is an unusually fertile ground for these tricks to be discovered and developed. From the use of GLFW in the example code above, I’m guessing that’s also where that came from. I think there are a number of reasons for this:
Games tend to run in full-screen mode and capture all regular input. On desktop or mobile OSes, using the OS’s multitasking mechanism to switch into the debugger or other tools will alter the state of the running program in ways that disrupt the thing you’re trying to debug. On consoles and similar, you may not always run with a remote debugger attached, etc.
Reproducing bugs or specific states can be time intensive. Even if you have a savegame/snapshot mechanism, this is rarely perfect and depending on the type of issue, you have to try reproducing manually or even work on something else for a while until a very rare issue rears its head again by chance.
Realtime systems are, well, time sensitive, so many “traditional” debugging techniques inadvertently affect the system state.
I’ve never had the need to change how the default assert() macro works for debugging purposes. In particular, I don’t see why you’d need or want the program to continue after a failed assertion. I think he is probably misusing the debugger if he is trying to get this behavior.
I don’t see why you would want to have arbitrary rules about what constitutes “misuse” of a debugger? If it enables the developer debug a problem faster, or at all, it’s fair game, surely? (Outside of immoral/literally illegal methods like telemetry that violates data protection legislation or such, but the proposed techniques do nothing of the sort.)
As for motivation why you’d want to do this: the combination of a long-running program and an extremely rare bug would be a good example. It happens that you can’t make sense of what leads to the (crashing) assertion failure with just the assert message; if it’s also hard or time-consuming to reproduce the issue, you’ll want to gather as much information as possible next time the condition is hit. Inspecting program state in detail in the debugger is an important tool in that situation. It can also be useful to single-step or continue past the failed assertion in some cases, particularly if you identify a possible fix and can emulate it by modifying live program state directly in the debugger.
I don’t see why you would want to have arbitrary rules about what constitutes “misuse” of a debugger?
Misuse of a debugger will cause you to use it in inefficient ways, wasting time and effort. If you are properly using a debugger, I.e. using it the way it’s intended to be used, you’ll be able to debug most issues using nothing but a core dump. In the cases that the core dump isn’t sufficient, all you’ll need is a time traveling debugger like “rr.”
If you inline __debugbreak the debugger drops you in your code instead of libc, and the nop thing is to work around a gdb bug. Being able to continue is just a nice option to have I guess
I know this post is about C specifically, but for anyone who may have been following the C++ standard, some work by myself was later carried on in P2514 which is now part of C++26, and which references some of the tricks mentioned here. cppreference’s docs on the matter also point to additional implementations if anyone else wants to “know more” (and there is an equivalent is_debugger_present, but it has a lot more gotchas involved sadly). I’m fairly certain there is nothing blocking the <debugging> header interface from existing under the C standard or within a C library 🙂
A surprisingly useful debugger trick I know is to add
to your codebase and combine it with the article to get fine grained conditional breakpoints. I can’t think of any good examples when this was really helpful lately but as a basic example you can do stuff like:
and hit F1 to drop into the debugger
A useful tactic enough that I keep a generalised version in the client end of the windowing IPC libraries, re-using the same path to sneak in debugger script with the intended break/watchpoints/…
https://arcan-fe.com/2020/02/10/leveraging-the-display-server-to-improve-debugging/
I’ve definitely used that before. If tying it to a button is tricky/too much effort in your runtime environment, you can also hit pause in the debugger, modify the variable’s state directly in the debugger, then resume.
Some related strategies:
(*) I suspect gamedev is an unusually fertile ground for these tricks to be discovered and developed. From the use of GLFW in the example code above, I’m guessing that’s also where that came from. I think there are a number of reasons for this:
I’ve never had the need to change how the default assert() macro works for debugging purposes. In particular, I don’t see why you’d need or want the program to continue after a failed assertion. I think he is probably misusing the debugger if he is trying to get this behavior.
I don’t see why you would want to have arbitrary rules about what constitutes “misuse” of a debugger? If it enables the developer debug a problem faster, or at all, it’s fair game, surely? (Outside of immoral/literally illegal methods like telemetry that violates data protection legislation or such, but the proposed techniques do nothing of the sort.)
As for motivation why you’d want to do this: the combination of a long-running program and an extremely rare bug would be a good example. It happens that you can’t make sense of what leads to the (crashing) assertion failure with just the assert message; if it’s also hard or time-consuming to reproduce the issue, you’ll want to gather as much information as possible next time the condition is hit. Inspecting program state in detail in the debugger is an important tool in that situation. It can also be useful to single-step or continue past the failed assertion in some cases, particularly if you identify a possible fix and can emulate it by modifying live program state directly in the debugger.
Misuse of a debugger will cause you to use it in inefficient ways, wasting time and effort. If you are properly using a debugger, I.e. using it the way it’s intended to be used, you’ll be able to debug most issues using nothing but a core dump. In the cases that the core dump isn’t sufficient, all you’ll need is a time traveling debugger like “rr.”
If you inline __debugbreak the debugger drops you in your code instead of libc, and the
nopthing is to work around a gdb bug. Being able to continue is just a nice option to have I guessI couldn’t help but wonder: does Clang’s __builtin_debugtrap compile to int3, triggering GDB bug, or to int3; nop, avoiding it?
It looks like it emits just the
int3instruction: https://godbolt.org/z/9v334hv5PI know this post is about C specifically, but for anyone who may have been following the C++ standard, some work by myself was later carried on in P2514 which is now part of C++26, and which references some of the tricks mentioned here. cppreference’s docs on the matter also point to additional implementations if anyone else wants to “know more” (and there is an equivalent
is_debugger_present, but it has a lot more gotchas involved sadly). I’m fairly certain there is nothing blocking the<debugging>header interface from existing under the C standard or within a C library 🙂