lab
EECS 280: Debugging Tutorial
How to track a bug in your visual debugger
Let’s say your program isn’t working - maybe it crashes, or maybe it does the wrong thing. Debugging is the process of tracking precisely where your program’s actual behavior deviates from what you intended. It’s the mix of science with a very subtle art to
- Ask the right questions about what might be going wrong, and
- Pick an appropriate tool to get the information you need to check your suspicions.
Getting Started
We’ve written the spell-checker program from a previous lab with a number of bugs. In this tutorial we’ll show our overall debugging philosophy in action using a visual debugger.
Start by creating a new project with spellchecker.cpp
and
words.txt
, which are available with the Lab 04 starter files.
You might want to refer to the setup guide for Project
1.
Note: If you are using VS Code, set externalConsole
in
launch.json
to true
to enter input.
Detecting a bug
Before we start debugging, it makes sense to make sure there is actually a bug in our program, so try compiling and running the spell checker to see what happens.
Segmentation fault
Well, that wasn’t supposed to happen. A segmentation fault (often shortened to segfault) basically means your program did something bad with memory. This can be anything from dereferencing a null pointer to running off the end of an array. The challenge for debugging this type of bugs is that the program just crashes - it doesn’t give you any feedback about where the problem occurred.
Fixing Bug 1
However, since we are using a debugger, we can inspect things like the call stack, the contents of memory, and the values of variables. The basic idea is that when we run the spell checker in our debugger, when it crashes, we check where it was when that happened.
Now we have several pieces of information about our program when we
reached the segfault. First, we see that the segfault occurred on line
21 of spellcheck.cpp
.
We also notice that dereferencing ptr1
and ptr2
, the pointers used
to traverse the two C-style strings, apparently causes the segfault.
Here’s a hypothesis for the bug:
Hypothesis: ptr1
or ptr2
is running off the end of an array
Another piece of information we have is the last value of our variables when it crashed.
We observe that ptr1
and ptr2
hold the same address. We also
notice that the addresses stored in both str1
and str2
are
actually the same as well. It turns out that the compiler is allowed
to set up different string literals with the same content ("lizard"
in the testing function) by reusing the same memory. This isn’t
guaranteed, but will often happen.
Now, we will verify our hypothesis that we’re going off the end of our
array. Take a look at the call stack and select the caller
test_strcmp_eecs280
stack frame.
This should take you to the place where this function was called, in this case here:
We can now set a breakpoint at this line. Debuggers allow us to break (i.e. “pause”) a program’s execution at some point of interest and inspect its state. When we run it again, execution will stop at the first breakpoint it reaches.
Note: When paused, the debugger will highlight the next line it will execute.
Once paused at a line, use the step over and step into commands to move through the program. Both move forward to the next piece of code, but the step over command will step over function calls while the step into command will step into function calls.
Now, step into the srcmp_eecs280
function to check its behaviour.
Then step over to the while loop and take a look at how ptr1
and
ptr2
are initialized.
We can now keep track of ptr1
and ptr2
as we step through the loop
multiple times.
As we suspected, we fail to exit the while loop once we reach the end of
the character array. The "strcmp..."
is just the next location in memory
as you can see from the address. The loop advances the pointers as long
as they point to the same character, but since ptr1
and ptr2
point to
the same place, this will go on indefinitely. We forgot to check for the
sentinel!
If the words are the same, the loop should stop at the null character. Change the condition to:
21 while (*ptr1 && *ptr1 == *ptr2) {
Fixing Bug 2
After fixing the previous bug, we can compile and run the program
again. There is no segfault this time, but it still doesn’t work - it
claims nothing is spelled correctly and it also fails to recognize
that “quit” should exit the program (if in a terminal use ctrl-c
to
stop it).
Based on a quick inspection of the program’s behavior, we might form these hypotheses:
Hypothesis 1: The find_word
function is broken.
Hypothesis 2: The find_word
function gets the wrong argument.
The find_word
function is responsible for opening the dictionary
file and checking to see if a given word is found in that file. Set a
breakpoint at the beginning of the function and rerun the program, and
you will verify Hypothesis 2 from above (find_word
gets the wrong
argument).
Now it makes sense to switch focus to the code that called find_word
to see why the wrong argument is passed in. Once again click the
main
in the call stack.
Checking the local variables, we see that user_word
is incorrect
here as well:
Now by looking at the code in main
, we come to a new hypothesis
below.
Hypothesis: The get_user_word
function is broken.
Investigate get_user_word
by setting a breakpoint at the start of
that function.
Actually, it might be more useful to break at the end of the function, right before the return. We can set a breakpoint at the appropriate line and use the continue button, which resumes execution of the program until the next breakpoint.
Note: The program will also pause temporarily to wait for you to
enter input needed by cin
.
Right before the function returns, we see that the actual behavior of our program deviates from the expected, correct behavior. Once you find precisely where this happens, you can just go ahead and fix the bug (make sure that the function is returning the right thing).
Note: More descriptive variable names might have prevented this bug in the first place!
Fixing Bug 3
After fixing the previous bug, run the program again and test it with the following words.
test
hello
Did you notice anything strange? It claims that “hello” is not a correctly spelled word.
Let’s continue with the same hypothesis as last time:
Hypothesis: find_word
is still receiving the wrong input.
This is simple to check by setting a breakpoint at find_word
and
checking the arguments.
It is clear that it is receiving the correct word now. Some other possibilities we could consider:
Hypothesis 1: “hello”is somehow missing from words.txt
.
Hypothesis 2: The strcmp_eecs280
function doesn’t work for
"hello"
.
Hypothesis 3: Something in find_word
itself is incorrect.
What do we do next? Generally, we will start by checking the hypotheses that are easier to test and make our way to the ones that require more work until hopefully we have found the bug.
Therefore we can do the following for the first two hypotheses.
Hypothesis 1 - Open words.txt
and see that “hello” is there.
Hypothesis 2 - Add a test to check the result of the comparison in question.
Since strcmp_eecs280
passes this test when we run the code (i.e. the
assert doesn’t crash our program) we reject Hypothesis 2, so we
narrow our focus a bit more.
Hypothesis 1: “hello”is somehow missing from
words.txt
.
Hypothesis 2: The strcmp_eecs280
function doesn’t work for
"hello"
.
Hypothesis 3: Something in find_word
itself is incorrect.
We’ll leave tracking down the final bug for you - somehow find_word
never notices that "hello"
is in fact in our dictionary file. Think
about what information would be useful to have, and then pick the
strategy that gets you that information most efficiently.
Once you find this bug, make sure to fix it in spellcheck.cpp
!
Final Note
At this point you may be familiar with debugging by adding print statements to your code. This will still work, but using a debugger to check the value of variables is usually faster.
On the other hand, staring at your code without actively examining its behavior, or making random code changes in hopes of fixing the bug, will not get you anywhere.
Happy debugging!