Debugging in Linux with GDB

I came across this nice piece of tutorial regarding debugging with GDB. You can find the entire tutorial here.

To be able to debug a program, you need to compile it with debugging information enabled so that gdb can work with variables, their state and values, lines and functions. To do this, compile your program under gcc (or g++) with an extra ‘-g’ option:

example: gcc -g test.c -o test

Running gdb

Simply run gdb from the shell and pass the program name as parameter: gdb test

Once your program is loaded, start the execution by typing gdb command run.

An example:

If nothing is wrong your program will execute to completion, at which point gdb will get control back. But what if something does go wrong? In this case gdb will take control and interrupt the program, allowing you to examine the state of everything and hopefully find out why. To provoke this scenario we’ll use an example program:

#include
int wib(int no1, int no2)
{
int result, diff;
diff = no1 - no2;
result = no1 / diff;
return result;
}
int main(int argc, char *argv[])
{
int value, div, result, i, total;
value = 10;
div = 6;
total = 0;
for(i = 0; i < 10; i++)
{
result = wib(value, div);
total += result;
div++;
value--;
}
printf("%d wibed by %d equals %d\n", value, div, total);
return 0;
}

This program runs around a for loop 10 times, calculating a cumulative value using the ‘wib()’ function and finally printing out the result.

Enter it into your favorite text editor with the same line spacings, save as ‘test.c’, compile with ‘gcc -g test.c -o test’ and start gdb with ‘gdb test’. Running the program using ‘run’ will result in a message something like:

Program received signal SIGFPE, Arithmetic exception.
0x80483ea in wib (no1=8, no2=8) at test.c:7
7 result = no1 / diff;
(gdb)

Gdb indicates that the program gets an arithmetic exception at line 7 and usefully prints out the line and the values of the arguments to the wib() function. To see the source code around line 7 use the command ‘list‘, which usually prints 10 lines. Typing ‘list‘ again (or pressing return which repeats the last command) will list the next 10 lines of the program. From the gdb message something is going wrong with the divide at line 7 where the program divides the variable “no1″ by “diff”.

To see the values of variables the gdb ‘print’ command is used with the variable name. We can see what “no1″ and “diff” are equal to by typing ‘print no1′ and ‘print diff’, resulting in:

(gdb) print no1
$5 = 8
(gdb) print diff
$2 = 0

Gdb indicates that “no1″ equals 8 and “diff” equals 0. From these values and line 7 we can deduce that the arithmetic exception is due to a divide by zero. The listing shows the variable “diff” being calculated on line 6, which we can re-evaluate by supplying the “diff” expression to print as ‘print no1 – no2′. Gdb told us that the arguments to the wib function were both equal to 8 so we might wish to examine the main() function which calls wib() to see when this happens. In the meantime to allow our program to die naturally we tell gdb to carry on execution with the ‘continue’ command:

(gdb) continue
Continuing.
Program terminated with signal SIGFPE, Arithmetic exception.
The program no longer exists.

Another Example from gnome.org

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).

Leave a Reply