Hello Debugger!

Sometimes, we get ideas we wish we’d thought of sooner. A few nights ago, I got this one: break to the debugger from the source code.

I’m a huge fan of GDB and DDD, but for anything beyond basic breakpoints, I’ve found myself wading through too much user documentation. What if I want a consistent conditional breakpoint, even if I add or remove earlier code? Never mind setting an initial breakpoint, then adding a data-dependent watchpoint… already, the terminology is getting thick. There has to be a better way.

I can give you a real-world example, albeit on a toy C program. Over an unbounded array of independent results, for some reason the 9th result was obviously wrong, and then the program crashed with a corrupt memory table.

I’m very comfortable in C, but clearly not perfect. I’m not nearly so comfortable with GDB’s occasional verbosity. What’s more, I see no way to set a breakpoint that should be skipped N times before activating, at least without using some kind of internal GDB scripting mechanism. Is there a way to get the program to stop itself for debugging? Yes, there is. What’s more, it doesn’t involve any major alterations to the operating environment.

This has the effect of suspending execution, in order to attach a debugger after the fact. It has two great side benefits:

It’s in a familiar syntax, in this case, C.

It takes effect the same way, even after altering the source and re-building the object code.

When run from the command shell, the behavior resembles job control: background, stop, resume, Ctrl-Z, and the like. I verified this in Bash, Tcsh, and zsh, on my desktop PC and my Raspberry Pi. All of the regular job control commands apply.

After the program stops, attaching GDB (or DDD) is trivial, even for basic users. It’s just like attaching to a running program, using the process ID and the executable file. But when a program is running, it’s usually a matter of chance where the debugger will attach and make it stop. Getting a program to stop itself is easier to manage.

But what about running the program inside the debugger? Instead of attaching the debugger from the outside, after the fact, can the debugger treat the program as a typical target? Yes, once again!

It isn’t perfect, but it Works For Me

The raise(SIGSTOP) call actually stops a couple levels deep, where source code isn’t typically available. If you’ve built the object code with debugging annotations (using the “-g” option), two “finish” commands in GDB will get you back to the raise() call in your source code. I think it’s a small price to pay for the convenience.

A tiny demonstration

Here’s an example of before-and-after behavior, that you can test on your own system.

It’s nothing terribly challenging. It prints something, halts until it’s ordered to continue, then it prints something else. Save it as “stopself.c”, then build it with:

gcc -g -o stopself stopself.c

Running the program in Bash:

$ ./stopself
Launching program...
[1]+ Stopped ./stopself
$ _

Just as if you had typed Ctrl-Z during execution. (Job control messages vary under tcsh and zsh, but the overall behavior is the same.) You can resume the program the normal way, using either the “fg” command or

Once attached, the debugger will be one level down from the call to signal(), so you can type “up” to see where the code stopped in your source.

A side effect of this approach is a second STOP signal, which won’t be handled until you resume execution. It’s just the nature of the beast, and it’s even sort-of documented in the GDB info page. Sometimes, signals get delivered twice. I haven’t found a way to avoid the second STOP. But really, it’s a small price to pay for code with built-in conditional breakpoints.

Break where you want

It comes down to two simple points in C (or C++, or Objective-C, or whatever language you can link in): include the header, then stop the program when you want. Really, when you, the developer, want to stop the program, you are allowed. You aren’t confined by the tools you’re using; those tools permit you to do things their designers never thought of. This means you can do something like this:

That means, if your counter has reached 9, or the value of π has changed (not bloody likely, but who knows?), or the zombies are invading (sooner than you think), your program can stop itself and wait for further command. The conditions are yours to determine.