Debugging Using GDB

While testing your program, you may encounter errors ("bugs") in one of two ways:
  1. the program may crash, or
  2. 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

  1. 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.
  2. Create the executable bug (type: make bug).
  3. 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.)
  4. 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).
  5. 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).
  6. 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.
  7. 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.
  8. 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

  1. 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.
  2. Make a new version of bug (type: make bug).
  3. 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.)
  4. Use xxgdb again to debug the program (type: xxgdb bug).
  5. 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.)
  6. 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.)
  7. Run the program on input data (type: r data). It will stop just before executing the line where you set the breakpoint.
  8. 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.
  9. 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).
  10. 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).
  11. 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

  1. Exit from xxgdb using the "quit" command. Fix the problem you discovered in Example 2a by editing tree.h and tree.cc.
  2. Remake bug and try it again with input data. It still gives the wrong answer!
  3. 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.
  4. 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.
  5. 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?