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.