CS217 Precept 1: Using GDB (and Netscape)

The purpose of this lab is to introduce you to gdb, the GNU Debugger, plus a quick introduction to Netscape. When you've completed this lab, you should know: You'll also learn some cool emacs tricks along the way.

Netsurfing 101

Netsurfing is as easy as that. If it's blue, you can click on it. A good place to start is http://www.yahoo.com. Put it in your bookmarks and prepare to spend all your waking hours wandering around the planet reading funky cool pages.

Compiling a program for debugging

For our exercises, we'll be using a program that messes with a linked list. The sources are in /u/cs217/examples/gdb. Make a directory of your own, copy all those files into your own directory, i.e.:

% mkdir gdb-stuff
% cd gdb-stuff
% cp /u/cs217/examples/gdb/* .
Then, run emacs. This pops up a new window on your screen.

Type M-x compile and hit return twice. The example program will be compiled. Notice how everything happened automatically? Emacs ran a program called make which reads a file called Makefile, which tells it what files have to be compiled to make your program.

We'll talk about makefiles in more detail later on. For now, you can look at how the file is written and mutate it to serve your needs in other assignments.

The important thing, for debugging purposes, is the -g flag that's passed to the compiler. This instructs the compiler to annotate your code so the debugger can follow along as your program is running.

If you want the code to run as fast as possible, replace -g with -O, and the optimizer will work on your code (lcc will complain, but gcc will do the right thing).

Cool trick: If the compiler found a bug in your code, you could type C-x ` and you'd be taken straight to the offending line of source code. Cool, huh?

Running the debugger

Inside emacs, just type M-x gdb, hit return, then type the name of the program you want to debug when it prompts you. We'll start with llist, which you compiled earlier. Gdb will think about it for a while, and eventually give you the prompt ``(gdb)''.

To quit the debugger, simply type quit at the (gdb) prompt. To make the gdb buffer go away, you type C-x k, as usual.

Setting breakpoints

Right now, you're looking at two regions - one for gdb and one for the output of compiling your program. Flip up to the compilation buffer (C-x o or mouse-click there), type C-x f llist.c and hit return. This will load the llist.c file into that region while gdb is still running in the other.

C programs begin in main(), so find it (it's near the bottom) and go down to the line which says LInsert(7);. Type C-x space. You'll notice that some stuff appeared in the GDB buffer. You just set a breakpoint at that line of code. When you first run the program, it will stop here.

Running your program

Go down to the gdb window (click with your mouse or type C-x o) and type run. If you wanted to pass command-line arguments to your program, you could put them afterward (i.e.: run a.c b.c > output).

Notice the arrow that appeared next to our breakpoint? The first two lines of the program ran, and it stopped before running LInsert(7).

Our next two commands let us watch the program as it progresses. They're called step and next, but you can type s or n as a shorthand. step will go into a function call, and next will go over a function call. First, try next. The arrow should have gone down one line. If you just hit the return key, it will repeat your last command. Do that, and the arrow should now be in front of LInsert(3).

Now, let's go wander down to see how things are working. This time, step into the call. The arrow should have jumped up to the beginning of the LInsert() function. Type next and hit return a couple times. We're going to be inserting the number ``3'' into a list which contains an empty node, ``2'', ``7'', and another empty node.

Printing variables

The arrow should be in the middle of the for loop of LInsert(). There are two pointers wandering through the list - prevPtr and curPtr. To print them, you can say
(gdb) p prevPtr
$1 = (struct list *) 0x4100
(gdb) p curPtr
$2 = (struct list *) 0x61f0
Not very exciting, huh? All it did was give you the numerical addresses. This is a little more interesting.
(gdb) p *prevPtr
$3 = {next = 0x61f0, data = -2147483648}
(gdb) p *curPtr
$4 = {next = 0x61e0, data = 2}
(gdb) p *curPtr->next
$5 = {next = 0x40f8, data = 7}
(gdb) p *curPtr->next->next
$6 = {next = 0x40f8, data = 2147483647}
(gdb) p *curPtr->next->next->next
$7 = {next = 0x40f8, data = 2147483647}
You see those dollar signs? All those are values that you can reference again. Watch this:
(gdb) p $5->data
$8 = 7
(gdb) p curPtr->data + $5->data
$9 = 9
Basically, you can type any C-expression after the print statement and it will tell you the answer. You can even call functions!
(gdb) p LPrint(curPtr)
$10 = void

Cool trick: It's always a good idea to leave functions like LPrint() around, even if you don't need them in your final program. The debugging print interface gets annoying sometimes, but you can always run some pretty-printing code you've got hanging around.

Moving up and down the call stack

We're going to fast-forward to a later part of the program, to better explain what's going on.

In the source window, go down the bottom, where it calls LSummarize(lhead.next). Set a breakpoint there, and type continue to gdb (or c for shorthand).

The LSummarize() procedure computes a list of partial sums of the list by calling itself recursively. step and hit return a bunch of times. You'll notice the arrow bouncing between the recursive call to LSummarize() and the top of the function. In the gdb window, you'll also notice it's printing out the argument being passed to the function as you're wandering down.

Type where. You should see something like this:

#0  LSummarize (lPtr=0x61f0) at llist.c:117
#1  0x2830 in LSummarize (lPtr=0x6210) at llist.c:119
#2  0x2830 in LSummarize (lPtr=0x6220) at llist.c:119
#3  0x2b30 in main (argc=1, argv=0xf7fff954) at llist.c:146
This is the call stack of your program. You can type up and down to move around. Or, you can type frame 2 to go directly to that particular frame. When you're in a frame, you can print its local variables.

This example is pretty cheesy, but in a bigger program, this is an invaluable way to track down bugs. You generally discover a bug somewhere deep in a program, and it's often caused because somebody called your function with bogus arguments. By going upward, you can see who did it, and let them know about it.

More fun stuff

Gdb is a huge program, and this is just an introduction. By typing help, gdb will list a number of topics. For example, you can type help breakpoints for more information on breakpoints. If you wanted to turn a breakpoint off temporarily, you'd learn that the disable command could do it. If you wanted to get a list of all current breakpoints, you could type help info and then you'd learn about the info breakpoints command.

Exercise: Try breaking the source code I gave you. See if you can get one of the assert() statements to fail, or see if you can get the program to get a segmentation fault or a bus error.

Dan Wallach, CS Department, Princeton University
Last modified: Thu Feb 8 20:56:02 EST 1996