Debugging Using GDB
While testing your program, you may encounter errors ("bugs")
in one of two ways:
- the program may crash, or
- it may produce incorrect output.
In either case, you can use the debugger gdb,
or the x-windows version, xxgdb to help track down the error.
Example 1: Using gdb after a crash
- Create a new directory and copy into it all the files from
~cs536-1/public/GDB. The files
tree.h and
tree.cc
define operations on binary-search trees (in which each node
contains an integer). Binary-search trees are implemented using
the type TreeNode (a structure containing data,
left, and right fields), and the
type Tree (a pointer to a TreeNode).
The program in
main.cc
includes the declaration of a tree T, initialized to NULL.
It opens the file named on the command line,
then reads the numbers from that file, inserting each one into
T (by calling the function Insert).
After all the numbers have been inserted, it calls the function
Size to print the size of the tree.
- Create the executable bug (type: make bug).
- Run bug with the file data (type:
bug data). Running bug should cause a core dump.
(Note that data contains just one value--this is the
smallest input that causes an error.)
- Use xxgdb to find the problem. First type:
xxgdb bug core
xxgdb will open a new window;
it will print a message in the bottom part of the window,
ending with information about where your program was when it
crashed (at line 26 in the file tree.cc).
The actual code (in function Insert) will be displayed in the
top part of the window, with a bomb icon to indicate the point of
the crash. Use the "print" command to see the current value of
Insert's parameter T (either type: p T, or
select variable T with the mouse and click on the
print button in the middle of the window).
You will see that the value of T is the hexidecimal
number 0x0, which is just 0, i.e., T is NULL.
The statement that caused the crash is trying to dereference a NULL
pointer (not good).
- To find out why T is NULL here, run the program again
after setting a breakpoint at the beginning of
function Insert. To do this, type: b Insert
then type: r data (to tell xxgdb to run the
program with the command-line argument data).
- The program will stop at the beginning of the first call to
Insert and xxgdb will indicate the next statement
to be executed using an arrow in the left margin of the code.
(If you use plain gdb, it will print the next statement
to be executed.)
Use the print command to look at the value of T
(type: p T). It is 0x0 (NULL). That's OK--you haven't
inserted anything into T yet, so it should be NULL at
this point.
- Use the "next" command to execute one statement at a time (either
type: n or click on the next button).
After each step, use the print command to look at the
value of T. Note that the call to NewTreeNode is
executed as a single step.
If you had wanted to step into NewTreeNode, you would have
used the "step" command, s, instead of the "next"
command, n.
- After the call to NewTreeNode, you will see that
T is still NULL. This is very strange!
NewTreeNode should have allocated new memory for T
to point to, but it doesn't seem to have done anything.
Use the list command (type: l NewTreeNode) to
look at the code for NewTreeNode.
Can you see what vital statement has been omitted from this code?
(If not, try recompiling tree.cc with the -Wall
flag. You will get a warning message that should help you find the
error.)
Example 2a: Using gdb to understand bad output
- Exit from xxgdb using the "quit"
command (type: q or click on the quit button;
if you are using plain gdb it will ask if you really want
to quit--type: y).
Fix the problem you discovered in Example 1 by editing
tree.cc.
- Make a new version of bug (type: make bug).
- Run bug using data as the input file.
It will not crash, but the output will be:
- The size of the BST is: 0
and the size really should be 1, since the single value, 6, from the
data file should have been inserted.
It seems that either the Insert function or the Size
function is not working.
We can find out whether Insert is working by calling the
Print function after calling Insert to see
whether the value 6 has been inserted.
(We could do this by editing the program and adding a call to
Print, but then we'd have to take the call out again later.
It's better to do it with xxgdb.)
- Use xxgdb again to debug the program (type:
xxgdb bug).
- Scroll down in the main program to find the place to call
Print. (If you are using plain gdb, use the "list"
command--type: l main--to look at the main program.
By default, the list command lists the next 10 lines.
To list more lines, either keep typing l, or use
l xxx, yyy,
which will list all lines from xxx to yyy.)
- Use the "break" command to set a breakpoint after the while loop
that inserts values into the tree:
click anywhere between the end of the while loop and the beginning of
the next statement, then click on the break button.
(If you are using plain gdb, type: b 29 to set
a breakpoint at line 29.)
- Run the program on input data (type: r data).
It will stop just before executing the line where you set the
breakpoint.
- Use the "call" command (type: call Print(T)) to call
function Print. You should see that the tree is empty.
You can also verify this by looking at the value of T (type:
p T).
For some reason T is still NULL.
It seems that Insert is not working.
-
First, remove the breakpoint you set in main
(breakpoint 1) by typing: d 1.
(You can get breakpoint numbers by typing: info b, or by
clicking on the show brkpts button.)
Then set a new breakpoint at the beginning of Insert
(type: b Insert), and start the program again
(type: r).
- Use the "next" and "print" commands to step through Insert,
looking at the value of T.
You should see that after the call to NewTreeNode,
T is no longer NULL. You can also use the "print"
command to look at the values of the fields in the structure pointed
to by T once they've been set (e.g., type:
p T->data).
- Keep using the "next"
command, and you'll step out of Insert back to the next
statement in main after the call Insert(T, k);.
Look at the value of T now.
Do you understand what is missing in the definition of
Insert that is causing this problem?
Example 2b: Using gdb to understand bad output
- Exit from xxgdb using the "quit" command.
Fix the problem you discovered in Example 2a by editing
tree.h and tree.cc.
-
Remake bug and try it again with input data.
It still gives the wrong answer!
- Repeat steps 4 - 8 from Example 2a above.
If you made the correct fix to Insert, you should now
see that the value 6 has been inserted into T.
So it seems that there is also a problem with the Size
function.
- Set a breakpoint at the beginning of Size
(type: b Size) and use the "continue"
command (type: c) to have gdb continue execution
until it hits that breakpoint.
- If you are using plain gdb, use the "list"
command to look at the code for Size
(if you are using xxgdb, the code will be displayed in the
top part of the window).
Remember that since the data file only contained one value,
T is a tree with one node.
Think about the value that is going to be returned by this call to
Size. Can you find and fix the error?