Previous Page
Next Page

20.2. A Sample Debugging Session

This section describes a sample GDB session to illustrate the basic operation of the debugger. Many problems in C programs can be pinpointed using just a handful of debugger commands. The program in Example 20-1, gdb_example.c, contains a logical error. We'll use this program in the following subsections to show how GDB can be used to track down such errors.

Example 20-1. A program to be debugged in a GDB session
// gdb_example.c:
// Test the swap( ) function, which exchanges the contents of two int variables.
// -------------------------------------------------------------
#include <stdio.h>
 
void swap( int *p1, int *p2 );         // Exchange *p1 and *p2
 
int main( )
{
  int a = 10, b = 20;
/* ... */
  printf( "The old values: a = %d; b = %d.\n", a, b );
 
  swap( &a, &b );
 
  printf( "The new values: a = %d; b = %d.\n", a, b );
/* ... */
  return 0;
}
 
void swap( int *p1, int *p2 )         // Exchange *p1 and *p2.
{
  int *p = p1;
  p1 = p2;
  p2 = p;
}

20.2.1. Symbol Information

GDB is a symbolic command line debugger. "Symbolic" here means that you can refer to variables and functions in the running program by the names you have given them in your C source code. In order to display and interpret these names, the debugger requires information about the types of the variables and functions in the program, and about which instructions in the executable file correspond to which lines in the source files. Such information takes the form of a symbol table, which the compiler and linker include in the executable file when you run GCC with the -g option:

$ gcc -g gdb_example.c

In a large program consisting of several source files, you must compile each module with the -g option.

20.2.2. Finding a Bug

The following command runs the program from Example 20-1:

$ ./a.out

The program produces the following output:

The old values: a = 10; b = 20.
The new values: a = 10; b = 20.

Although the swap( ) function call is plain to see in the source code, the contents of the variables a and b have not been swapped. We can look for the reason using GDB. To begin the debugging session, start GDB from the shell, specifying the name of the executable file as a command-line argument to the debugger:

$ gdb ./a.out
GNU gdb 6.1
Copyright 2004 Free Software Foundation, Inc.
...
(gdb)

The debugger loads the program executable, but waits for your instructions before running it. GDB prints (gdb) at the beginning of a new line to prompt you for a debugging command. You can start by entering the command list, or just its initial l for short, to list a few lines of source code of the program you are debugging. By default, the listing shows 10 lines, centered on the source line that is ready to be executed next. In our example, the program has just been started, and the next line is line 8, where the function main( ) begins:

(gdb) l
5
6       void swap( int *p1, int *p2 );         // Exchange *p1 and *p2
7
8       int main( )
9       {
10        int a = 10, b = 20;
11        /* ... */
12
13        printf( "The old values: a = %d; b = %d.\n", a, b );
14
(gdb)

If you follow one list command with another, GDB prints the next few lines of source code.

Before you instruct GDB to run the program, you should tell it where you want it to stop. You can do this by setting a breakpoint. When the debugger reaches the breakpoint, it interrupts the execution of your program, giving you an opportunity to examine the program's state at that point. Furthermore, once the program has been interrupted at a breakpoint, you can continue execution line by line, observing the state of program objects as you go.

To set a breakpoint, enter the command break, or b for short. Breakpoints are usually set at a specific line of source code or at the beginning of a function. The following command sets a breakpoint at line 15 of the current source file, which is the line containing the swap( ) function call:

(gdb) b 15
Breakpoint 1 at 0x80483aa: file gdb_example.c, line 15.

The command run, or r, starts the program:

(gdb) r
Starting program: /home/peter/src/c/gdb/a.out
Breakpoint 1, main ( ) at gdb_example.c:15
15           swap( &a, &b );

Upon reaching the breakpoint, the debugger interrupts the execution of the program and displays the line containing the next statement to be executed. As we suspect the bug in our example is in the swap( ) function, we want to execute that function step by step. For this purpose, GDB provides the commands next, or n, and step, or s. The next and step commands behave differently if the next line to be executed contains a function call. The next command executes the next line, including all function calls, and interrupts the program again at the following line. The step command, on the other hand, executes a jump to the function called in the line, provided that the debugging symbols are available for that function, and interrupts the program again at the first statement in the function body. In our example session, the command step takes us to the first statement in the swap( ) function:

(gdb) s
swap (p1=0xbffff234, p2=0xbffff230) at gdb_example.c:24
24         int *p = p1;

The debugger displays the values of the function arguments (here, these are the addresses of the variables a and b), and once again the next line to be executed. At this point we can check to see whether the values of the variables referenced by the function's pointer arguments are correct. We can do this using the print command (p for short), which displays the value of a given expression:

(gdb) p *p1
$1 = 10
(gdb) p *p2
$2 = 20

The expression *p1 has the value 10, and *p2 has the value 20. The output of GDB's print command has the form $number = value, where $number is a GDB variable that the debugger creates so that you can refer to this result later. (See the section "Displaying Data," later in this chapter, for more information about GDB's variables.)

If we now type n (for next) three times, the debugger executes lines 24, 25, and 26:

(gdb) n
25         p1 = p2;
(gdb) n
26         p2 = p;
(gdb) n
27      }
(gdb)

As long as the program flow has not yet returned from the swap( ) function to main( ), we can use the print command to display the contents of the local variables:

(gdb) p *p1
$3 = 20
(gdb) p *p2
$4 = 10

Now *p1 has the value 20, and *p2 has the value 10, which seems correct. We can continue the examination of the program state with two more print commands:

(gdb) p p1
$5 = (int *) 0xbffff230
(gdb) p p2
$6 = (int *) 0xbffff234
(gdb)

As these print commands show, the values of the pointers p1 and p2 have been swapped, and not the contents of the memory locations referenced as *p1 and *p2. That was the bug in swap( ). The function needs to be amended so that it exchanges the int values addressed as *p1 and *p2, rather than the pointer values stored in p1 and p2. A correct version would be the following:

void swap( int *p1, int *p2 )         // Exchange *p1 and *p2.
{
   int tmp = *p1;
   *p1 = *p2;
   *p2 = tmp;
}

The command continue, abbreviated c, lets program execution continue until it reaches the next breakpoint or the end of the program:

(gdb) c
Continuing.
The new values: a = 10; b = 20
Program exited normally.
(gdb)

As the (gdb) prompt indicates, the debugger is still running. To stop it, enter the command quit or q. The quit command terminates the debugger even if the program you are debugging is still running. However, GDB does prompt you for confirmation in this case:

(gdb) q
The program is running.  Exit anyway? (y or n) y
$


Previous Page
Next Page