Debugging Lab #3
More gdb Techniques and Debugging Code that uses Pointers

In the previous lab, you learned several techniques to debugging code using GDB. This lab will give you additional practice, give some new commands that you may find useful, and also give some information about how you can use GDB to find the reason for the dreaded Segmentation Fault.

Conditional Breakpoints
Hopefully by now, you are familiar enough with setting and using breakpoints that they are (almost) second nature. Breakpoints can give you a snapshot view of the state of your code a specific point. However, suppose you are running your code, and it goes along great, but then at the 100th or 1000th iteration of a loop, things go awry.

There are three ways you can handle this:
1.  Type continue 100 (or 1000) times until you get to the specific iteration of the loop where the problem occurs
2.  Add a few more lines of code such as:
if (i == 100)
    i = i;      // Set a breakpoint on this line
3. Set a conditional breakpoint in GDB

The first way works, but could be rather tedious, especially when you get to the last continue and realize you miscounted and your error occurs without you having the chance to look at it.
The second way can be useful and at times you may wish to use it. But, it does involve editing and recompiling your code which you may not wish to do at the moment.
The third way is also useful, and does not require you to recompile your code. However, if you exit gdb for some reason and then come back, you will have to set up the conditional breakpoint again, which is a little (but not much) more work than setting a breakpoint with option #2.

Setting a Conditional Breakpoint
Start gdb and type "help break" at the (gdb) prompt. You will see the help information for the "break" command. You should already be familiar with the "break [LOCATION]" portion of the break command. However, notice the [if CONDITION] portion of the command (ignore the [thread THREADNUM] portion - we won't be getting to that level in this class). A little farther down in the help text, you will see:
CONDITION is ________________________ (fill in the rest of the line).
This states that you can add a condition, such as (i == 100) to your breakpoint.

Now, exit gdb and compile the Lab9.c file so that you can debug it using gdb. Then run the program using command line option 2:
bash$ ./Lab9 2
The program will print the sum of the numbers from 0 to N, where N is the value [0..25]. However, there is a bug (of course) in the code. For what value of N does the output value start to give incorrect output? _____________

Let's set a few conditional breakpoints to see what happens right before the value goes bad. Using vi or vim, look at the code in the function DebugOption2(). Of course you are dilligent enough to see what the problem is right away (at least we hope you are), but bear with us for a while. Find the line number of the file where the line of code is:
num = 0;
We want to set a conditional breakpoint at that line. We know that things work correctly through N = 15 (where N is represented as i in the for loop). So, we should set a breakpoint where the execution will pause on that line when (i = 16). Based on what you have learned so far, write the full gdb command to set a conditional breakpoint at the proper source code line. (Remember to use a boolean expression and that (i = 16) is not considered a boolean expression.) ________________________

Now that you have figured out the correct syntax for the conditional breakpoint, set the breakpoint in gdb and run the program with the command line argument of "2."
(gdb) run 2
If you set your breakpoint correctly, you will see something like:
Starting program: /home/student/Labs/Lab_9/Lab9 2
The sum of the numbers from 0 to 0 is: 0
The sum of the numbers from 0 to 1 is: 1
...
The sum of the numbers from 0 to 14 is: 105
The sum of the numbers from 0 to 15 is: 120

Breakpoint 1, DebugOption2 () at Lab9.c:83
83        num = 0;
(gdb)

(Of course, you now also see the proper line number, if you hadn't figured it out on your own using vi.)
Print the value of i. If everything worked correctly, its value should be 16.

Inspecting the code in the source file at this point shows a nested for-loop. This time, the loop variable is j. We can see by the output above, that the sum of the numbers from 0 to 15 still has the correct value. It is when the value goes to 16 that things go haywire. Let's put in another conditional breakpoint on the line
num += j;
This time, have the breakpoint stop when num is equal to 120. Write the command to set the breakpoint: ____________________________

Set the breakpoint in gdb and continue running the code using the continue command. Write the last line output before the (gdb) prompt returns: ____________________

Use the gdb command to display the num, i and j variables.  Write all of their values:   num = ___________    i = ________    j = ______________

Assuming there is no bug, what will the value of num be after executing the statement num += j?    num = ______________
Execute a step command in gdb.  What is the actual value of num now?  num = ______________
Why is the value what it is?   _____________________________

Fix the bug in the source code by changing num to be the proper type. Exit gdb, recompile the code and run it again, with 2 as the command line parameter.  Do you get the proper output now? _______________

You have now learned and used the gdb commands you will likely use 95% of the time. There are of course others, but most of your time spent in gdb will be using these commands.

However, the real talent in debugging lies in knowing what to do and when. You may find or learn about techniques to do certain debugging tasks (an example follows below), but the art of debugging can only be learned through hours/days/months/years of practice and experience. Get started!

Segmentation Faults
As you use pointers more often, you will encounter Segmentation Fault (seg fault) errors. These errors may seem scary, but with gdb, it is usually not difficult to discover the source of the problem.

Generally, seg faults can happen when a divide by 0 occurs, an array is indexed out of bounds or when a NULL pointer is de-referenced. A divide by 0 should be fairly self explanatory. However, an example of dereferencing a NULL pointer is given below.

typedef struct _debugLab
{
    int i;
    char c;
} debugLab;

Later on in the code:
   debugLab *dl = NULL;
   dl->i = 35;

Note that since *d1 is set to NULL, it has no memory address that it can go to in order to set the variable i in the debugLab structure. This is a serious problem for the computer, so it performs a segmentation fault and ends the program, usually dumping a large core file that takes up disk space. (Core files can be very useful for debugging, but this technique goes slightly beyond the scope of this current assignment).

Finding the Seg Fault
Run the Lab9 code on the Unix command line, but this time use option 3 as a command line argument. You should see a segmentation fault message.

Now, load Lab9 into gdb and type the command "run 3."  Notice that gdb tells you exactly the line of code where the seg fault occurs. What line is it? ___________________

Before inspecting the actual line of code where the problem occurs, lets look at some of the other variables that did not cause the seg fault. Print the variable dl3 using the print command. Note that the entire structure is printed with the values of the internal variables of the structure. Write the values of i and c: _________________

Now print the value of dl2 using the following command:
(gdb) print dl2
What is its value? ___________________
Note that dl2 is a pointer, so a memory address is printed. To see the contents of the memory pointed to by that address, use the following command:
(gdb) print *dl2
What are the values of i and c? ___________________

Now, let's look at the actual line of code causing the seg fault. Using gdb, print the value of dl1 using the following command:
(gdb) print dl1
What is its value? ___________________

Note that dl1 is a pointer. Now, try to dereference dl1 and print its value using the following command:
(gdb) print *dl1
Write the message given: _______________________________________________

Because dl1 is set to NULL, it can't dereference memory address 0x0. Therefore a segmentation fault occurs. Not all segmentation faults will be as easy to find as this one, but knowing how to step through code and inspecting pointer variables along the way can help you narrow down where and why such errors occur.

Now its Your Turn
Exit gdb and try running the Lab9 executable using 4 as the command line parameter:
bash$ ./Lab9 4
What happens?  ______________________________

Now, use the techniques you have learned to find the error in the code using gdb.

What line does the segmentation fault occur?  ___________________

What is the value of i at the point of segmentation fault?  ___________________

Set appropriate breakpoints (conditional or otherwise), use appropriate gdb commands and try to determine what the bug in the code is. Once you find the source of the problem, copy the line of code that causes the problem below:

__________________________________________

Fix the code, recompile and run the executable. Write the output of the correctly running executable below:

_______________________________________________________


Conclusion
Congratulations! You have learned about setting conditional breakpoints and how to use gdb to track segmentation faults in your code. Fill out the hard-copy of the answer sheet and submit it to your TA no later than the beginning of your next lab session.