Chapter 3. Debugging

Table of Contents

GDB
Example program
Basic usage
Getting more information
Walking through the program
More on setting breakpoints
Learning more
strace
Basics
Examples
Learning more
Valgrind (Memcheck)
Capabilities
Basics
Examples
Disadvantages
Learning more
Important option summary

I never put bugs in my programs[1]. I am sure you do the same. However, this chapter will cover some tools that are helpful when working on programs and libraries written by other people[2]. Note that some of these tools, such as GDB or Valgrind, are meant more for explitly compiled languages (e.g. C and C++) than others.

GDB

gdb is a tool to allow you to see what a program was doing just before it crashed, or to see the internals of a program while it runs.

Example program

It is easiest to explain with an example. A good example is gdb-example.c, which is listed below. It is a simple, yet buggy program that we will run under gdb.

#include "stdio.h"

void
print_scrambled(char *message)
{
  int i = 3;
  do {
    printf("%c", (*message)+i);
  } while (*++message);
  printf("\n");
}

int
main()
{
  char * bad_message = NULL;
  char * good_message = "Hello, world.";

  print_scrambled(good_message);
  print_scrambled(bad_message);
}

Basic usage

In order to run programs under gdb, they should be compiled with debugging symbols turned on and with optimization flags turned off (this is not strictly necessary, but not doing this can make gdb output impossible to read or follow).

104 newren@Miney:~/examples$ gcc -g gdb-example.c -o gdb-example

Because of a bug in the program, running it will result in a segmentation fault:

105 newren@Miney:~/examples$ ./gdb-example
Khoor/#zruog1
Segmentation fault

This is one of the areas where gdb comes in handy. By running the program under gdb, we can find out the exact line that the program is crashing on.

106 newren@Miney:~/examples$ gdb gdb-example
GNU gdb Red Hat Linux (5.3.90-0.20030710.41rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/tls/libthread_db.so.1".
(gdb) run
Starting program: /home/newren/examples/gdb-example
Khoor/#zruog1
Program received signal SIGSEGV, Segmentation fault.
0x0804835b in print_scrambled (message=0x0) at gdb-example.c:8
8           printf("%c", (*message)+i);
(gdb) backtrace
#0  0x0804835b in print_scrambled (message=0x0) at gdb-example.c:8
#1  0x080483c3 in main () at gdb-example.c:20
(gdb) quit
The program is running.  Exit anyway? (y or n) y
107 newren@Miney:debugging/gdb$

Here, we did not need to run the backtrace command since gdb already showed that the crash occurred at line eight of gdb-example.c in the function print_scrambled. However, it is often useful to know which function calls occurred in order to get to the line where the program crashed. The backtrace command produces a list of the function calls, which is known as either a backtrace or a stack trace.

Reading backtraces is fairly straightforward. The data associated with each function call in the list is known as a frame. The outermost frame is the initial function that your program started in, and is printed at the bottom of the list. Each frame is given a number (0, 1, 2, etc.). Following the frame number is an associated memory address, which is almost entirely useless and which you can ignore. Then each frame contains the name of the function that was called, its arguments, the name of the file where the function appears, and line number. So, the stack trace for our program says that at line 20 of gdb-example.c in function main, the print_scrambled function was called--and that the program got to line eight of gdb-example.c inside the print_scrambled function.

Getting more information

Getting a backtrace may provide enough information, but it is sometimes helpful to get more information, such as the values of arguments or local variables. Another convenience provided by gdb is listing a small segment of the code around where the program is currently stopped so you can see which statements have been executed and which ones are about to be. The following gdb session demonstrates how to do these things as well as how to switch to previous stack frames using the up command.

142 newren@Miney:~/examples$ gdb gdb-example
GNU gdb Red Hat Linux (5.3.90-0.20030710.41rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/tls/libthread_db.so.1".
(gdb) run
Starting program: /home/newren/examples/gdb-example
Khoor/#zruog$
Program received signal SIGSEGV, Segmentation fault.
0x0804835b in print_scrambled (message=0x0) at gdb-example.c:8
8           printf("%c", (*message)+i);
(gdb) backtrace
#0  0x0804835b in print_scrambled (message=0x0) at gdb-example.c:8
#1  0x080483c3 in main () at gdb-example.c:20
(gdb) list
3       void
4       print_scrambled(char *message)
5       {
6         int i = 3;
7         do {
8           printf("%c", (*message)+i);
9         } while (*++message);
10        printf("\n");
11      }
12
(gdb) info locals
i = 3
(gdb) info args
message = 0x0
(gdb) up
#1  0x080483c3 in main () at gdb-example.c:20
20        print_scrambled(bad_message);
(gdb) list
15      {
16        char * bad_message = NULL;
17        char * good_message = "Hello, world.";
18
19        print_scrambled(good_message);
20        print_scrambled(bad_message);
21      }
(gdb) info locals
bad_message = 0x0
good_message = 0x80484a1 "Hello, world."
(gdb) info args
No arguments.
(gdb) quit
The program is running.  Exit anyway? (y or n) y
143 newren@Miney:~/examples$

From the gdb output, it is fairly clear that the list, info locals, and info args commands get information about the currently selected stack frame. Besides using the up command to go choose a previous frame, you can also use the down command to choose a later one or use the frame command (with a numeric argument) to choose which stack frame to switch to.

Walking through the program

gdb can also allow you to walk through the program while it is running so that you can trace its steps carefully. The following gdb session illustrates this, using the break, print, next, and step commands.

143 newren@Miney:~/examples$ gdb gdb-example
GNU gdb Red Hat Linux (5.3.90-0.20030710.41rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/tls/libthread_db.so.1".
(gdb) break main
Breakpoint 1 at 0x804839c: file gdb-example.c, line 16.
(gdb) run
Starting program: /home/newren/examples/gdb-example
Breakpoint 1, main () at gdb-example.c:16
16        char * bad_message = NULL;
(gdb) print bad_message
$1 = 0x8048410 "U\211%G%@VS"
(gdb) next
17        char * good_message = "Hello, world.";
(gdb) print bad_message
$2 = 0x0
(gdb) next
19        print_scrambled(good_message);
(gdb) next
Khoor/#zruog$
20        print_scrambled(bad_message);
(gdb) step
print_scrambled (message=0x0) at gdb-example.c:6
6         int i = 3;
(gdb) step
8           printf("%c", (*message)+i);
(gdb) step
Program received signal SIGSEGV, Segmentation fault.
0x0804835b in print_scrambled (message=0x0) at gdb-example.c:8
8           printf("%c", (*message)+i);
(gdb) print (*message)+i
Cannot access memory at address 0x0
(gdb) quit
The program is running.  Exit anyway? (y or n) y
144 newren@Miney:~/examples$

The break command sets a breakpoint--a location in the program where gdb should stop when it gets to there. Breakpoints can be set at the beginning of a function or at specific lines in program file. There are many things that can be done with breakpoints, such as making them conditional or temporary. In this example, a common and simple usage case was shown that had gdb stop at the beginning of the main function.

The next and step commands were used to make gdb move forward in the program. For statements that do not involve functions, the next and step commands are identical and merely make gdb execute one statement. For statements that involve a function, however, the two commands are different. next tells gdb to execute the entire function, while step tells gdb to move inside the function.

The print command displays the value of variables or expressions. In the example, the bad_message variable was shown both before and after it was initialized. Later in the example, gdb responded that it could not display the expression (*message)+i because a pointer (the message variable) had a NULL (meaning invalid) value. In fact, this is the bug in this program--print_scrambled does not check to see whether its argument contains a valid value.

More on setting breakpoints

Finally, as mentioned above, gdb has a variety of ways to set breakpoints. The example below demonstrates setting breakpoints at a specific line number and in a function in a library used by the program.

144 newren@Miney:~/examples$ gdb gdb-example
GNU gdb Red Hat Linux (5.3.90-0.20030710.41rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/tls/libthread_db.so.1".
(gdb) break gdb-example.c:19
Breakpoint 1 at 0x80483d2: file gdb-example.c, line 19.
(gdb) break printf
Function "printf" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
 
Breakpoint 2 (printf) pending.
(gdb) run
Starting program: /data/home/newren/floss-development/developing-with-gnome/examples/debugging/gdb/gdb-example
Breakpoint 1, main () at gdb-example.c:19
19        print_scrambled(good_message);
(gdb) where
#0  main () at gdb-example.c:19
(gdb) cont
Continuing.
 
Breakpoint 3, 0x004692a6 in printf () from /lib/tls/libc.so.6
(gdb) where
#0  0x004692a6 in printf () from /lib/tls/libc.so.6
#1  0x08048394 in print_scrambled (message=0x80484c9 "Hello, world.")
    at gdb-example.c:8
#2  0x080483dd in main () at gdb-example.c:19
(gdb) cont
Continuing.
 
Breakpoint 3, 0x004692a6 in printf () from /lib/tls/libc.so.6
(gdb) where
#0  0x004692a6 in printf () from /lib/tls/libc.so.6
#1  0x08048394 in print_scrambled (message=0x80484ca "ello, world.")
    at gdb-example.c:8
#2  0x080483dd in main () at gdb-example.c:19
(gdb) delete 3
(gdb) cont
Continuing.
Khoor/#zruog1
 
Program received signal SIGSEGV, Segmentation fault.
0x08048383 in print_scrambled (message=0x0) at gdb-example.c:8
8           printf("%c", (*message)+i);
(gdb) quit
The program is running.  Exit anyway? (y or n) y

The cont command (shorthand form of "continue") just instructs gdb to continue running until it either hits another breakpoint or the program ends. There where command is identical to the backtrace command (it is merely an alias). The delete command removes a breakpoint, given the number of the breakpoint (the command "info breakpoints" can come in handy in connection with delete).

Learning more

This short introduction only touched upon a few of the abilities of GDB. The easiest way to learn more is to use the help command inside gdb. Alternatively, you can read more about gdb at http://www.gnu.org/software/gdb/gdb.html. There are also graphical frontends to gdb, such as DDD (The Data Display Debugger) and GVD (The GNU Visual Debugger).



[1] except for when I do, which is unfortunately quite often.

[2] As a programmer, I find it helpful to also be schizophrenic so that I can always blame the bugs in my programs on someone else.