How to Use gdb

Brad Vander Zanden


gdb is a GNU debugger that can be very helpful in finding problems with your programs. It allows you to do a number of useful things including:

  1. controlling the execution of your program by placing breakpoints and single stepping your program.
  2. printing the values of variables in your program, and
  3. determining where a segmentation fault or bus error occurs in your program.

In CS102 you probably debugged your program by inserting print statements into your program. While that technique often proves helpful, gdb provides far more functionality and is worth learning. We will start you out with a simple tutorial that shows you some of its features and then show you how you can use it to help you with debugging.

In order to use gdb you need to compile your files with the -g option. For example:

gcc -g -c student1.c
gcc -o student1 student1.o


A Sample GDB Session

Start by copying the files in ~bvz/cs140/www-home/fall-2008/labs/gdb to your directory and then typing make. make should compile the sample files in your directory into executables.

Now type:

gdb names_list
gdb is the command you type to invoke the gdb debugger and names_list is the name of the executable you wish to debug. In this case names_list has nothing wrong with it. We simply want to use it to show you the various ways in which you can use gdb. names_list prompts the user for five names and prepends the names to a list. The list starts with a sentinel node. The program then prints the names in the reverse order from which they were entered.


Listing the lines in a program

You can list the lines in your program using the list command, or l for short. list lists 10 lines of your program, centered about the next line to be executed. In this case it should print lines 3-12 of your program. You can make it print an arbitrary list of lines by typing:

l first_line_number, last_line_number
For example, typing l 10,23 will cause gdb to list lines 10-23 of the current file.

Sometimes you will want to list the lines in a different file. For example, names_list consists of the two files list.c and add_name.c. The file that was listed defaults to list.c because that was the first file I gave to gcc. However, I can look at lines in add_name.c by prefixing the line numbers with add_name.c. For example:

l add_name.c:6,12

Executing a Program in GDB

You start running a program using gdb's run command or r for short. Try it now on the names_list program. Simply type r at the gdb prompt:

(gdb) r
names_list does not require any command line arguments but if you have a program that requires command line arguments, you list them after the run command. For example:
r 3 gradefile

If your program is seg faulting then the run command will probably suffice to start your debugging session. When the program seg faults gdb will tell you the file, the procedure, and the line number where the seg fault occurred. We will explore debugging later. For the time being we will concentrate on showing you how you can control the execution of a program using gdb.

Frequently you will want to stop the program at some point during its execution and start single-stepping it. You can cause the program to stop at a certain line or when a certain function is called by creating breakpoints. A breakpoint is created using the break command or b for short. It takes either a line number or a function as an argument. For example, each of the follow commands will set a breakpoint in the names_list program:

b 15                  // set a breakpoint at line 15 in the current file
b add_name_to_list    // set a breakpoint on the function add_name_to_list
b add_name.c:8        // set a breakpoint at line 8 in add_name.c
When you place a breakpoint on a function, such as add_name_to_list, gdb will stop the program any time that the function add_name_to_list is called, no matter where in the program add_name_to_list is called.

When a breakpoint is created gdb will assign it a number. If you want to later delete the breakpoint you can do so by typing delete and the breakpoint number. Alternatively you can type clear and either the line number or the function to which the breakpoint is attached. For example:

delete 1
clear add_name_to_list
clear add_name.c:8
These three commands will delete the three breakpoints created earlier. Unfortunately I do not know of any way to cause gdb to list the current breakpoints in a program so you need to keep track of them in your head or by writing them down.


Continuing from a Breakpoint

You can cause the program to continue executing and proceed to the next breakpoint by typing continue, or c for short. Try it out by re-instating the breakpoints you typed in previously, starting the program, and then typing c each time you reach a breakpoint. You will need to re-type the previous breakpoints if you deleted them.


Printing the Values of Variables

You can print the values of variables in gdb using the print command, or p for short. You print items just as you would reference them in a C program. However, unlike printf, you do not have to provide formatting information.

To try out the printing command first delete your previous breakpoints and place a breakpoint at line 18 in list.c. Now re-run your program from scratch and enter a name when prompted. The program will break at line 18:

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
 
Starting program: /home/bvz/courses/140/spring-2005/labs/gdb/names_list
enter a name: brad
 
Breakpoint 1, main () at list.c:18
18              add_name_to_list(name_list, name);
(gdb)
Now let's print the contents of name_list and name:
(gdb) p name_list
$13 = (LIST *) 0x20af0
(gdb) p name
$14 = "brad\000\001\005l\000\000\000\003???,\000\000\000\004"
(gdb)
name_list is a pointer which points to memory starting at 0x20af0 and name is a 20 character array where only the first five characters are meaningful ("brad\000"). We can see that "brad" was properly read into name.

We can print the contents of the node pointed to by name_list by dereferencing the pointer:

(gdb) p *name_list
$15 = {name = 0x20af8 "", next = 0x0}
(gdb)
Remember that our list has a sentinel node so everything is as it should be. The sentinel node's next field is the null pointer, thus making it an empty list.

Now type continue, or c, and enter another name:

(gdb) c
Continuing.
enter a name: nels
 
Breakpoint 1, main () at list.c:18
18              add_name_to_list(name_list, name);
(gdb)
At this point "brad" should have been added to the list but "nels" should not yet have been added to the list. However, "nels" should be contained in name. We can check all this out using the print command:
(gdb) p name   // "nels" is in name as it should be
$16 = "nels\000\001\005l\000\000\000\003???,\000\000\000\004"
(gdb) p *name_list   // the sentinel node points to something
$17 = {name = 0x20af8 "", next = 0x20b00}
(gdb) p *name_list->next     // and that something is "brad"
$18 = {name = 0x20b10 "brad", next = 0x0}
(gdb)
Note that brad's node does not point to anything, which is correct. Also note how I was able to "chase" a pointer by typing
p *name_list->next

Let's try one more iteration of the program:

(gdb) c
Continuing.
enter a name: pat
 
Breakpoint 1, main () at list.c:18
18              add_name_to_list(name_list, name);
(gdb) p *name_list->next
$19 = {name = 0x20b30 "nels", next = 0x20b00}  // the first node is "nels"
(gdb) p *name_list->next->next 
$20 = {name = 0x20b10 "brad", next = 0x0}      // the second node is "brad"
(gdb) p name    // "pat" is in name
$21 = "pat\000\000\001\005l\000\000\000\003???,\000\000\000\004"
(gdb)
Notice that I can get arbitrarily far in the list by typing a series of next's.


Single Stepping Through a Program

Once you have gotten a program to stop you will often want to single step through the program, executing one statement at a time, rather than continuing to the next breakpoint. gdb provides you with two commands for doing so:

  1. step (s for short): step executes each statement and, if it encounters a function call, it will step into the function, thus allowing you to follow the flow-of-control into subroutines.

  2. next (n for short): next also executes each statement but if it encounters a function call it will execute the function as an atomic statement. In other words, it will execute all the statements in the function and in any functions that that function might call. It will seem as though you typed continue with a breakpoint set at the next statement. The one exception to this statement is that if there is a breakpoint nested in the function, then gdb will break when it reaches that breakpoint.

To see the difference between the two types of commands, re-run your program from scratch by typing r. It should break after it prompts you for a name and you enter one:

(gdb) r
Starting program: /home/bvz/courses/140/spring-2005/labs/gdb/names_list
enter a name: brad
 
Breakpoint 1, main () at list.c:18
18              add_name_to_list(name_list, name);
First try using the step command:
(gdb) s
add_name_to_list (names=0x20af0, name_to_add=0xffbff6a0 "brad") at add_name.c:5
5           LIST *new_node = (LIST *)malloc(sizeof(struct list));
(gdb)
We have stepped into the function add_name_to_list and are about to execute the first statement. Before executing this statement try printing new_node. Since it has not been initialized, it will have a random value. When I printed the value of new_node I got the following result:
(gdb) p new_node
$1 = (LIST *) 0x300
Now execute the first statement in add_name_to_list by typing s and then print out the new value of new_node and the contents of the memory pointed to by new_node:
(gdb) s
7           new_node->name = strdup(name_to_add);
(gdb) p new_node
$2 = (LIST *) 0x20b00    // your address will probably be different
(gdb) p *new_node
$3 = {name = 0x20b08 "", next = 0x0}
(gdb)
Notice that we did not step into malloc because it is a system-defined function rather than a user-defined function. gdb is usually smart enough not to step into system-defined functions because they have not been compiled with the -g flag and hence could not be interpreted by gdb. Also note that the contents of the memory allocated by malloc have reasonable values. malloc does no initialization so it is purely luck that these memory locations have reasonable values. Often they will point to garbage.

Here is a helpful shortcut you can use while single-stepping. Type s to execute the strdup command. Now hit return twice. Notice that it single-steps for you. gdb remembers the last command that you entered and if you hit return, it will repeat that command. This feature is handy when you want to single-step through a number of statements.

We are now at the end of the function so let us make sure that the new node was prepended to the list and that it has the appropriate memory contents:

(gdb) p *new_node    // the strdup and assignment to next worked properly
$5 = {name = 0x20b10 "brad", next = 0x0}
(gdb) p *names       // print the sentinel node for the names list
$6 = {name = 0x20af8 "", next = 0x20b00}  // it points to our new node
(gdb) p *names->next  // now look at the first node in the list
$8 = {name = 0x20b10 "brad", next = 0x0}
Everything looks fine so let's continue by typing c
(gdb) c
Continuing.
enter a name: nels
 
Breakpoint 1, main () at list.c:18
18              add_name_to_list(name_list, name);
(gdb)
This time we will single step by using next rather than step:
(gdb) n
15          for (i = 0; i < NUM_NAMES; i++) {
(gdb)
Notice that we have executed add_name_to_list without stepping into it and have now gone to the top of the loop. Let's make sure everything worked before we continue:
(gdb) p *name_list    // print the sentinel node so we can find the first node in the list
$9 = {name = 0x20af8 "", next = 0x20b20}   
(gdb) p *name_list->next  // the first node should be the new node
$10 = {name = 0x20b30 "nels", next = 0x20b00} // it is. 
(gdb) p *name_list->next->next   // now check the second node
$11 = {name = 0x20b10 "brad", next = 0x0}  // it's also ok
Note that the next field for the new node points to the previous node we prepended. If you do not believe me, check the memory address, which is 0x20b00, with the memory address that was given to new_node the first time we visited add_name_to_list. They are the same.

Instead of typing continue, try typing n a couple more times until scanf gets executed:

(gdb) n
16              printf("enter a name: ");
(gdb) n
17              scanf("%s", name);
(gdb) n
enter a name: sue

Undefined command: "ue". Try help.
Depending on the version of gdb that you are using you may not get the above error message and everything might work fine. However, some versions of gdb will give you the above error message. The problem is that gdb will only give THE FIRST CHARACTER to scanf and then gdb will try to interpret the rest of the character string. To circumvent this problem, place a breakpoint after the statements which read keyboard input and "continue" through them. Doing so will allow all the keyboard input to be read in before control is returned to you. Notice that I cleverly told you to place your breakpoint at line 18, which is the first statement after the scanf.


Determining Your Location in a Program

You can find where you are in a program using the backtrack command or bt for short. The backtrace command will show you the current stack of functions that are active. For example, re-create the breakpoint at line 8 of add_name and then run the program until it reaches the breakpoint:

(gdb) b add_name.c:8
Breakpoint 2 at 0x10830: file add_name.c, line 8.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
 
Starting program: /home/bvz/courses/140/spring-2005/labs/gdb/names_list
enter a name: brad
 
Breakpoint 1, main () at list.c:18
18              add_name_to_list(name_list, name);
(gdb) c
Continuing.
 
Breakpoint 2, add_name_to_list (names=0x20af0, name_to_add=0xffbff6a0 "brad")
    at add_name.c:8
8           new_node->next = names->next;
(gdb)
Now type backtrace:
(gdb) bt
#0  add_name_to_list (names=0x20af0, name_to_add=0xffbff6a0 "brad")
    at add_name.c:8
#1  0x0001078c in main () at list.c:18
(gdb)
It tells you that you are currently in add_name_to_list at line 8 in add_name.c and that add_name_to_list was called from main at line 18 in list.c. Note that the backtrace command also tells you the values of the arguments passed to each active function. backtrace is typically the first command you will type when debugging a seg fault or bus error because you will want some idea of where you are in the program.

You can use the up and down commands to move around in the stack. These commands are necessary if you want to print a variable that is local to another function. For example, if I try to print main's name variable while I'm stopped at the current breakpoint, I will get the following error message:

(gdb) p name
No symbol "name" in current context.
(gdb)
However, if I type "up 1" I will be moved up to the stack record for main. On some versions of gdb I can then print the value of name:
(gdb) up 1
#1  0x00010764 in main () at list1.c:18
18              add_name_to_list(name_list, name);
(gdb) p name
$27 = "brad\000\001\005l\000\000\000\003???,\000\000\000\004"
Unfortunately some versions of gdb, including the one on our cetus machines, sometimes get it wrong:
(gdb) p name
$26 = "???0\000\001\ap?4 \210????????"
That is irritating and should not happen.


Debugging Programs

Now that you've learned the basics of gdb, let's see how you can use it to debug a program. Quit gdb and then re-enter it using the program segfault1:

(gdb) q
The program is running.  Exit anyway? (y or n) y
cetus3> gdb segfault1
GNU gdb 6.0
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 "sparc-sun-solaris2.9"...
(gdb) r
Starting program: /home/bvz/courses/140/spring-2005/labs/gdb/segfault1
 
Program received signal SIGSEGV, Segmentation fault.
0x00010670 in main () at segfault1.c:6
6           *name = 10;
(gdb)
gdb tells us that we received a segfault at line 6 in segfault1.c. It also shows us that the statement being executed was *name = 10. The fact that we are trying to assign to a dereferenced pointer should give us a strong clue that the pointer does not point to a legitimate piece of memory. We can test our hypothesis by checking the memory address pointed to by name:
(gdb) p name
$1 = (int *) 0x0
(gdb)
Sure enough, name is a null pointer, which means that we have not allocated memory for it using malloc. Try mallocing memory for name and re-run the program. It should work and print out 10.

Often a buggy program will not seg fault but we can still use gdb to try to find the cause of the bug. As an example, try running bad_names_list, which is a version of names_list into which I have introduced a couple of errors. First try running it and entering a name that is much longer than 20 characters:

gdb bad_names_list
(gdb) r
Starting program: /home/bvz/courses/140/spring-2005/labs/gdb/bad_names_list
enter a name: BradleyTannerVanderZandenNelsBlakeVanderZanden
BradleyTannerVanderZandenNelsBlbkeVanderZanden
 
Program exited normally.
Is this result what you expected? It should not be. First, the program is supposed to prompt you for five names but it only prompted you for one. Second, notice that you entered ...NelsBlake... but it printed as ...NelsBlbke.... To try to get a feel for what is happening, let's place a breakpoint at line 18 of list1.c, which is the line immediately after the one which reads a name. When we do so and print name, we get:
(gdb) b 18
Breakpoint 2 at 0x10750: file list1.c, line 18.
(gdb) r
Starting program: /home/bvz/courses/140/spring-2005/labs/gdb/bad_names_list
enter a name: BradleyTannerVanderZandenNelsBlakeVanderZanden
 
Breakpoint 2, main () at list1.c:18
18              add_name_to_list(name_list, name);
(gdb) p name
$10 = "BradleyTannerVanderZ"
Hopefully the fact that the name you entered has been truncated should be a big red flag that something is wrong with name. At this point you might check the declaration for name and find that it is only 20 characters. scanf does not know that name is only 20 characters so it reads in characters past the memory allocated for name. From the diagrams of memory that we have drawn in class you should have an inkling that the memory for the integer variable i, which precedes name in the declaration list, has been clobbered. Sure enough, if you try printing the value of i you will find that it is not 0 as you would expect:
(gdb) p i
$11 = 1933732961
However, suppose the truncation problem does not help you. You might then try typing n a couple times:
(gdb) n
main () at list1.c:15
15          for (i = 0; i < NUM_NAMES; i++) {
(gdb) n
21          for (name_list_ptr = name_list->next;
(gdb)
You can see that the loop has exited unexpectedly early. Presumably you will now check the value of i and find that it is much too large. Hopefully this information, combined with the suspiciously truncated version of name will give you the clues you need to find the problem.

Using the information that i has been overwritten, can you figure out why "Blake" changed to "Blbke"? It is because i has been incremented by 1 and the portion of the string that overwrote i was "Blake". Adding 1 to the ascii character 'a' causes the character 'b' to be created.

This error should show you why it is dangerous to declare fixed length arrays that will contain character strings. It is difficult to know in advance how large they will be. Of course you have to declare the character array to be of some size so you should make it very large, like maybe 1000 characters. Fixed length errors are one of the holes that hackers use to attack operating systems. They create a string that is too long for a fixed length array and the extra characters can end up overwriting instructions in the OS, thus causing the OS to do something it is not supposed to do.

Even if we correct this overflow error, there is another problem with bad_names_list. Exit gdb and re-enter it with bad_names_list. Run the program and enter any five names:

(gdb) r
Starting program: /home/bvz/courses/140/spring-2005/labs/gdb/bad_names_list
enter a name: brad
enter a name: sue
enter a name: mary
enter a name: nancy
enter a name: beth
beth
beth
beth
beth
beth
 
Program exited normally.
Notice that when the program is done it prints the last name that was entered five times rather than the five individual names that were entered.

We can use gdb to discover what happened. Start by putting a breakpoint on line 18 and run the program:

(gdb) b 18
Breakpoint 3 at 0x10750: file list1.c, line 18.
(gdb) r
Starting program: /home/bvz/courses/140/spring-2005/labs/gdb/bad_names_list
enter a name: brad
 
Breakpoint 3, main () at list1.c:18
18              add_name_to_list(name_list, name);
(gdb)
Execute the add_name_to_list function and check the contents of the list:
(gdb) n
15          for (i = 0; i < NUM_NAMES; i++) {
(gdb) p *name_list->next
$13 = {name = 0xffbff690 "brad", next = 0x0}
All looks well so let's continue:
(gdb) c
Continuing.
enter a name: beth
 
Breakpoint 3, main () at list1.c:18
18              add_name_to_list(name_list, name);
Again, execute add_name_to_list and check the contents of the list:
(gdb) n
15          for (i = 0; i < NUM_NAMES; i++) {
(gdb) p *name_list->next
$14 = {name = 0xffbff690 "beth", next = 0x20ac8}
(gdb) p *name_list->next->next
$15 = {name = 0xffbff690 "beth", next = 0x0}
(gdb)
The problem reveals itself. Both elements of the list are named "beth". Take a closer look at the the address for the name field. It is the same in both cases. Whenever you see a problem like this you should hypothesize that you have not used strdup somewhere where you should have. Sure enough, take a look at add_name1.c:
(gdb) l add_name1.c:1,20
1       #include "list.h"
2       #include 
3
4       void add_name_to_list(LIST *names, char *name_to_add) {
5           LIST *new_node = (LIST *)malloc(sizeof(struct list));
6
7           new_node->name = name_to_add;
8           new_node->next = names->next;
9           names->next = new_node;
10      }
11
Note that on line 7 we assign the memory address of name_to_add to new_node->name. The problem is that the memory address of name_to_add is the memory address of the variable name in main. That means that all of our nodes' name fields are pointing to main's name variable. Hence their value will always be the last string assigned to name.

This error is a common one with strings. Instead of allocating new memory for the string and copying the string to the new memory location, we have simply copied a pointer to the string. Copying a pointer is called a shallow copy while allocating new memory and copying the string to the new memory is called a deep copy. The solution to the problem is to rewrite line 7 as:

new_node->name = strdup(name_to_add);
If we recompile the program it will now work.

Conditional Breakpoints

Often a program will have to go through many iterations of a loop before it reachs the point in the program where something breaks. It can be irritating to have to keep typing continue until you finally reach the point where the error occurs. To address this problem gdb allows you to add a condition to a breakpoint that will cause the breakpoint to be activated only if the condition is met. For example, suppose that a bug appeared in bad_names_list at the point when I entered "mary". I could cause the program to stop only when "mary" has been input by entering the following breakpoint and condition:

(gdb) b 18
Breakpoint 3 at 0x10750: file list1.c, line 18.
(gdb) condition 3 (strcmp(name, "mary") == 0)
The condition keyword says to place a conditional breakpoint on breakpoint 3 with the indicated condition. Now try running the program:
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
 
Starting program: /home/bvz/courses/140/spring-2005/labs/gdb/bad_names_list
enter a name: brad
enter a name: sue
enter a name: mary
 
Breakpoint 3, main () at list1.c:18
18              add_name_to_list(name_list, name);
Perfect!


Additional Help

You can locate additional information about gdb in three ways:

  1. using the help command in gdb
  2. typing man gdb in your unix shell
  3. surfing the internet