BOLTS : Click to return to MacEdition homepage
 

Grumpy code? gdb it

February 10, 2003

Feedback Farm

Have something to say about this article? Let us know below and your post might be the Post of the Month! Please read our Official Rules and Sponsor List.

Forums

Want to dig even deeper? Post to the new MacEdition Forums!

Technical Level: Beginning-Intermediate

A debugger such as gdb is a program that sits between the OS and another program, controlling the target program’s environment. The debugger can suspend the execution of the target by using breakpoints. When the target is stopped like this, you can poke around and even change the running program’s data. You can change the flow of control of the program to work around programming errors.

Break(pointing) upon the tumbling shoals code

gdb lets you place breakpoints in a number of different ways. For instance, you can tell it to suspend execution at a particular C file and line number, or tell it to stop when entering a particular function or Objective-C method. You can tell gdb to attach conditions to those breakpoints, like only break when the value of the variable x is greater than 30, or if this is the 37th time the breakpoint has been hit. There is a small scripting language built in to gdb, so you can attach a bit of code to be executed when a breakpoint is reached. These scripts can be any gdb commands, like enabling or disabling other breakpoints or displaying a stack trace.

What can you do when gdb stops at a breakpoint? Usually you’ll get a stack trace. This is a display of the function call stack, showing the currently executing function, the function that called it, the function which called that function, and so on down to main(). You can change which stack frame (the collection of information that describes the parameters and local variables used by a function) is the current one. Here is a sample stack trace, edited down [Ed – and with some spaces after colons to allow lines to break].

#0 -[BWGLView drawRect:] (self=0x1986d0, _cmd=0x1700, bounds={origin = {x = 0, y = 2}, size = {width = 480, height = 360}}) at BWGLView.m:106

#1 0x930803dc in -[NSView _drawRect:clip:] ()

#5 0x9309656c in -[NSView _recursiveDisplayAllDirtyWithLockFocus:visRect:] ()

#7 0x930b2470 in -[NSThemeFrame _recursiveDisplayRectIfNeededIgnoringOpacity: isVisibleRect:rectIsVisibleRectForView:topView:] ()

#8 0x93095da4 in -[NSView _displayRectIgnoringOpacity: isVisibleRect:rectIsVisibleRectForView:] ()

#9 0x930a5124 in -[NSView displayIfNeeded] ()

#10 0x930b4e64 in -[NSWindow displayIfNeeded] ()

#11 0x930dbcec in -[NSWindow _reallyDoOrderWindow:relativeTo: findKey:forCounter:force:isModal:] ()

#12 0x930ad014 in -[NSIBObjectData nibInstantiateWithOwner:topLevelObjects:] ()

#13 0x93123c70 in old_loadNib ()

#14 0x930e9e58 in +[NSBundle(NSNibLoading) _loadNibFile:nameTable:withZone:ownerBundle:] ()

#17 0x9315f65c in NSApplicationMain ()

#18 0x00003ac4 in _start (argc=1, argv=0xbffffbc0, envp=0xbffffbc8) at /SourceCache/Csu/Csu-45/crt.c:267 #19 0x00003944 in start ()

These are the stack frames, with the top of the list being the function gdb has stopped at, and the bottom of the list being the first function invoked by the system. You can see the names of C functions like start() and NSApplicationMain() as well as Objective-C methods such as -[NSView _drawRect:clip:]. For the functions that have debugging symbols (lines #0 and #18), there is additional information, such as values of arguments, and the source file and line number of the function involved.

One use for stack traces is to see where the program has stopped execution. If the program was stopped due to a crash in a function the stack trace will tell you what that function is and who called it. Another use is that you might have a function that’s being called at unexpected times. By setting a breakpoint and doing a stack trace you can see who is calling your function and under what conditions it is happening. Sometimes the problem you are tracking down may be in another function up the call stack. gdb lets you navigate through these stack frames to poke around your suspended program.

When it’s time to resume, gdb will let you step source-line by source-line, dive into called functions (or simply execute them), or if you’re on the masochistic side, gdb will disassemble code, showing you the machine instructions that match your higher-level source code, allowing you to step through machine instructions too.

While you’re stopped, gdb also lets you dig into your data structures. When you compile your program with debug symbols (the -g flag for gcc if you’re outside of Project Builder), the compiler puts in a lot of information that tells debuggers and other interested parties how the generated machine code correlates with the program source code. For example, the extra data allows debuggers and other apps to know that the location 300 bytes into the function BWFrobulateCoefficient is actually line 387 of BWMath.c. Also included in the debug symbols are descriptions of the data structures seen by the compiler. Using this information the debugger can interpret an arbitrary range of bytes as one of your structures, or as an array of a particular type. This is the reason that gdb and similar programs are known as symbolic debuggers, since they can operate with the data types of your program directly (the “symbols” that the program maintains). While gdb is fundamentally a command line tool, there are hooks that Apple and others are using to put friendlier front ends on it. Project Builder allows you to graphically set breakpoints on lines of source code and will enable data manipulation on the stopped program from the GUI. Emacs has built-in support for gdb, allowing direct setting of breakpoints from the code buffers. I have also heard that the Metrowerks debugger on Mac OS X is based on gdb.

That being said, I’m a real proponent of getting comfortable with the gdb command line (and the Mac OS X command line in general). No front end exposes all of gdb’s features. Sometimes a single feature can make your life a lot easier, but if you never dive into gdb’s command line (also exposed via Project Builder’s Console tab) you may never find out about the feature or become comfortable enough to use it.

Personal Tip: I try to read through the gdb docs top-to-bottom every couple of years. (located on your machine at /Developer/Documentation/DeveloperTools/gdb/gdb/gdb_toc.html, assuming you have the DevTools installed). Doing this reminds me of cool features I have forgotten about, introduces me to new features, and sometimes after a few more years of programming experience I can grasp the motivation behind some features that until then struck me as “what on earth is that for?”

So that’s basically what gdb does. I’ll leave the details of what command does what to some tutorials out there from Apple and Cocoa Dev Central. Shameless plug: the Big Nerd Ranch Core Mac OS X and Unix Programming class that I teach has a section devoted to gdb and techniques of usage.

So what can you actually do with gdb?

One thing I like doing is stepping through code after I write it. This can be great for shaking out the “geez, that was stupid” errors I sometimes make. Just put a breakpoint in your code and step through it. Watch where the flow of control goes. Look out for anything unexpected. Peek at data values to make sure they’re what you’re expecting. Stepping through code is also great for learning someone else’s code. After executing each line of code, see what effect it had on the variables.

gdb is also fantastic for catching crashers. If you always run your program under gdb (or can reproduce the problem that causes a crash), gdb will point you to the line of code causing the crash. Most times the cause will be obvious (the proverbial smoking gun) or at least will give you some insight as to what is happening.

You can do remote debugging with gdb (that is, the program being run is executing on one machine and your gdb session is running on another machine). If you’re doing kernel-level programming gdb has a built-in network stack that it can use for communications. If you’re doing user-level programming (which would be the great majority of us), you can use ssh to log into another machine and start up gdb there. Since Project Builder’s front end doesn’t support this kind of remote debugging you’ll be doing this with the command line (which is another reason to become savvy with the command line). This technique is very handy if you have a server crashing in the field. You can ssh into the server box, camp on the program with gdb (using the attach feature) until it falls over, then poke around and see what’s happening. It’s also handy if you can’t reproduce a problem a particular user is having. ssh into their machine and monitor the program with gdb. When it crashes (or hits breakpoints you set), you can track down what the problem is. Since you’re coming in over the network you don’t have to affect your user’s work by hovering over them waiting for the problem to happen.

It’s also important to remember that no one tool suffices for all occasions. Sometimes I don’t use gdb at all. Occasionally I’ll see a crash or some bad behavior and know what the cause is since I just touched that piece of code. I sometimes use caveman debugging (print statements) to track down problems. Generally it’s a mood thing.

One handy trick: when gdb starts up, it reads a text file from your home directory called .gdbinit. You can put gdb commands in here so you can customize your environment. One thing I keep in mine is

fb -[NSException raise]

This tells gdb to set a future breakpoint on -[NSException raise], which is the chunk of code that gets called when a Cocoa exception is raised, particularly the dreaded “selector not recognized” error. You can tell Project Builder to remember breakpoints such as this one, but putting it into your .gdbinit will make such things available to you no matter the individual project settings (like when your friend asks you to look at a problem where they’re getting “selector not recognized” errors).

Core dumps, for when your program is crashing “irregularly”

One nice unix feature that Mac OS X brings developers are core files. A core file is a dump of the address space of a program, and are usually generated when the program crashes. Assuming that errant code hasn’t totally smashed everything, gdb will let you poke around in the core file almost as if it were a running program. You can’t resume execution or step through code, but you can look at stack traces and inspect data values. Sometimes the stack trace alone will give you a big clue as to where your program crashed.

OS X doesn’t have core dumping enabled by default. You can turn it on by running (in your shell) “limit coredumpsize unlimited” and then running your program in the shell. By default the system limits core dump sizes to zero bytes (meaning not to drop the core file at all). OS X seems to be very slow at dropping core files. I don’t know if that’s due to slow I/O on the current crop of systems, or issues in the kernel, or if it’s HFS+ related. This slowness at dropping core files (and sometimes this process can temporarily hose your machine if the core file is huge) is probably why this feature is not enabled by default.

Core dumps are another way of finding problems that only exhibit themselves out in the field. You might not want to make your users enable core dumps by groveling around in the shell (especially if your program is targeted towards the tech-phobic sector). You can enable them in your program code as well by using setrlimit() which changes the processes resource limits. In particular, changing the RLIMIT_CORE core file size limit to RLIM_INFINITY, a really big value. Stick this function somewhere convenient:

#import <sys/types.h>
#import <sys/time.h>
#import <sys/resource.h>
#include <stdio.h>
#include <errno.h>

void enableCoreDumps ()
{
    struct rlimit rl;

    rl.rlim_cur = RLIM_INFINITY;
    rl.rlim_max = RLIM_INFINITY;

    if (setrlimit (RLIMIT_CORE, &rl) == -1) {
        fprintf (stderr, 
              "error in setrlimit for RLIMIT_CORE: %d (%s)\n",
              errno, strerror(errno));
    }

} // enableCoreDumps

and then call enableCoreDumps() when you want to enable core file dropping. You could hide this behind an easter egg, like “hold down control-option-shift when opening the about box”. By default core files are put into the directory /cores.

gdb is a great tool with a long history (having first been written in the mid 1980s). It’s powerful and mature, but not necessarily the easiest to use. A front end like Project Builder can help ease the learning curve. It’s still a command line based tool, so being comfortable with the gdb command line is the best way to tap its power once you get used to the basics.

Mark Dalrymple (markd@borkware.com) has been wrangling Mac and Unix systems for entirely too many years. In addition to random consulting and custom app development at Borkware, he also teaches the Core Mac OS X and Unix Programming class for the Big Nerd Ranch.

E-mail this story to a friend

Talkback on this story!