Getting to Know gdb

It's worth making friends with a good C debugger.
Single-step Execution

gdb provides two forms of single-step execution. The next command executes an entire function when it encounters a call, while the step command enters the function and keeps going one statement at a time. To understand the difference between these two commands, look at their behavior in the context of debugging a simple program. Consider the following example:

$ gdb qsort2
(gdb) break main
Breakpoint 6 at 0x235c: file qsort2.c, line 40.
(gdb) run
Breakpoint 6, main () at qsort2.c:40
40      int power=1;
(gdb) step
43      printf("Tests with RANDOM inputs
and FIXED pivot\n");
(gdb) step
Tests with RANDOM inputs and FIXED pivot
45      for (testsize = 10; testsize <=
MAXSIZE; testsize *= 10){
(gdb) step
46           gen_and_sort(testsize,RANDOM,FIXED);
(gdb) step
gen_and_sort (numels=10, genstyle=0, strat=1) at
79      s = &start_time;

We set a breakpoint at the entry to the main() function, and started single-stepping. After a few steps, we reach the call to gen_and_sort(). At this point, the step command takes us into the function gen_and_sort(); all of a sudden, we're executing at line 79, rather than 46. Rather than executing gen_and_sort() in its entirety, it stepped “into” the function. In contrast, next would execute line 46 entirely, including the call to gen_and_sort().

Moving Up and Down the Call Stack

A number of informational commands vary according to where you are in the program; their arguments and output depend on the current frame. Usually, the current frame is the function where you are stopped. Occasionally, however, you want to change this default so you can do something like display a number of variables from another function.

The commands up and down move you up and down one level in the current call stack. The commands up n and down n move you up or down n levels in the stack. Down the stack means farther away from the program's main() function; up means closer to main(). By using up and down, you can investigate local variables in any function that's on the stack, including recursive invocations. Naturally, you can't move down until you've moved up first—by default you're in the currently executing function, which is as far down in the stack as you can go.

For example, in qsort2(), main() calls gen_and_sort(), which calls qsort2(), which calls swap(). If you're stopped at a breakpoint in swap(), a where command gives you a report like this:

(gdb) where
#0  swap (i=3, j=7) at qsort2.c:134
#1  0x278c in qsort2 (l=0, u=9, strat=1) at
#2  0x25a8 in gen_and_sort (numels=10, genstyle=0,
strat=1) at qsort2.c:90
#3  0x23a8 in main () at qsort2.c:46

The up command directs gdb's attention at the stack frame for qsort2(), meaning that you can now examine qsort2's local variables; previously, they were out of context. Another up gets you to the stack frame for gen_and_sort(); the command down moves you back towards swap(). If you forget where you are, the command frame summarizes the current stack frame:

(gdb) frame
#1  0x278c in qsort2 (l=0, u=9, strat=1) at
121                    swap(i,j);

In this case, it shows that we're looking at the stack frame for qsort2(), and currently executing the call to the function swap(). This should be no surprise, since we already now that we're stopped at a breakpoint in swap.

Machine Language Facilities

gdb provides a few special commands for working with machine language. First, the info line command is used to tell you where the object code for a specific line of source code begins and ends. For example:

(gdb) info line 121
Line 121 of "qsort2.c" starts at pc 0x277c and
ends at 0x278c.

You can then use the disassemble command to discover the machine code for this line:

(gdb) disassemble 0x260c 0x261c
Dump of assembler code from 0x260c to 0x261c:
0x260c <qsort2>:        save  %sp, -120, %sp
0x2610 <qsort2+4>:      st  %i0, [ %fp + 0x44 ]
0x2614 <qsort2+8>:      st  %i1, [ %fp + 0x48 ]
0x2618 <qsort2+12>:     st  %i2, [ %fp + 0x4c ]
End of assembler dump.

The commands stepi and nexti are equivalent to step and next but work on the level of machine language instructions rather than source statements. The stepi command executes the next machine language instruction. The nexti command executes the next instruction, unless that instruction calls a function, in which case nexti executes the entire function.

The memory inspection command x (for “examine”) prints the contents of memory. It can be used in two ways:

(gdb) x/nfu addr
(gdb) x addr

The first form provides explicit formatting information; the second form accepts the default (which is, generally, whatever format was used for the previous x or print command—or hexadecimal, if there hasn't been a previous command). addr is the address whose contents you want to display.

Formatting information is given by nfu, which is a sequence of three items:

  • n is a repeat count that specifies how many data items to print;

  • f specifies what format to use for the output;

  • u specifies the size of the data unit (e.g., byte, word, etc.).

For example, let's investigate s in line 79 of our program. print shows that it's pointer to a struct tms:

79          s = &start_time;
(gdb) print s
$1 = (struct tms *) 0xf7fffae8

The easy way to investigate further would be to use the command print *s, which displays the individual fields of the data structure.

(gdb) print *s
$2 = {tms_utime = 9, tms_stime = 14,
tms_cutime = 0, tms_cstime = 0}

For the sake of argument, let's use x to examine the data here. The struct tms (which is defined in the header file time.h) consists of four int fields; so we need to print four decimal words. We can do that with the command x/4dw, starting at location s:

(gdb) x/4dw s
0xf7fffae8 <_end+-138321592>:  9  14  0  0

The four words starting at location s are 9, 14, 0, and 0—which agrees with what print shows.