5 Awesome Visual Studio Debugger Features

So you know all the basic debugger features: breakpoints, stepping through the
code, viewing
the values of variables. But there's a lot more your debugger
can do to make it quicker to step through code, easier to test alternate
solutions, and even avoid rerunning the program.

Here, you'll learn about some of Visual Studio's more advanced debugger
features, such as stepping out of a function, taking advantage of the autos
window, using run to the cursor to set temporary breakpoints, modifying
a variable's value to test potential fixes, and using Set Next
Statement to turn back the clock and re-execute code.

Sample (Buggy) Code

Each of the following examples use this code, which contains several bugs:

1. Step Out of the Current Function

And the compute_average function is not returning the correct value. If you
were to use a debugger, and you wanted to step into compute_average, you could,
of course, put a breakpoint inside compute_average, but what if it were called
from several places? Visual Studio has a very convenient feature of its
debugger, that will allow you to step into compute_average very quickly.

Set a breakpoint on that line:

Once the debugger hits it, then step into the function. getSum and getCount
will both be called before compute_average. Normally, you'd keep hitting next
to get out of getSum and getCount, but with Visual Studio, you can quickly hit
Shift-F11 to step out of the current function and return to the next call.

Once you step out of the current function, you're taken back to the original
breakpoint. But now you can step in again, to go to the next function.
Repeat until you've drilled down into the function you want.

When to Use This Trick

Obviously, there are times when it makes more sense to just set a breakpoint in
your target function. But when doing so would require ignoring hundreds of calls to that function to find the one you want,
it might not be worth it. Using
step out provides a quick way of getting into a function without having to find
the function and set a temporary break point (or use run to cursor).

2. Use the Auto Window to See Result of Functions

One of the most frustrating parts of debugging is when you have a function call
whose result isn't stored anywhere, and you really want to know what it
returned.

Sometimes, programmers write code like this, just to work around the issue:

(Obviously, in this case, the program prints out the value, so all is not lost.
This is rarely true in the general case.)

Fortunately, the autos window has good courtesy to display the result of a
function evaluation:

When to Use this Trick

Whenever you want to see a return value! Note that you the autos window will eventually erase the return value as you execute code, so be sure to check your return value immediately.

Did you know...

Most return values are also stored in the EAX register--you can look in EAX to find the return value, if you need it.

3. Run to Cursor

Run to cursor is a great way of avoiding the
step-step-step-step-step-oh-no-I-stepped-over-it problem, where you know where
you want to be, getting to it requires stepping multiple times, and you get
impatient.

In effect, run-to-cursor is just a shortcut for setting a breakpoint that is
immediately cleared once it's hit; nevertheless, it's a very convenient
feature. It is most useful when you have a piece of code that is called
frequently from different places, and you only care about one code path. Then
you can place a breakpoint at the start of that path and use run-to-cursor to
get to the point you really care about.

Going back to our sample program, we can use run-to-cursor rather than the
step-out trick, to get into compute_average. (Of course, we could just put a
breakpoint in compute_average; for the purpose of making this example sensible,
please imagine 15-20 calls to compute_average that all work correctly, taking
place before the broken call.)

When to Use this Trick

Any time you want a throwaway breakpoint or want to avoid single-stepping through a lot of code. Be careful, though, that you run to a part of the code that will actually be executed. Watch out for early returns from a function within a loop, for instance.

4. Modify Any Variable

Now that we're in the compute_average function, and we know that the value
being returned is waayyy too big, we can check the arguments to the
function. It turns out that sum is very large. We'll deal with that in a bit.
First, let's test and make sure that the function works if we do get the right
value.

Obviously, one way of doing this would be to pass in a new value. But Visual
Studio conveniently makes it easy to change any value in memory. In fact,
let's do that with the value of sum, and make sure that it returns the correct
value.

All you need to do is click on in the Value column of the auto or watch window
and change the value. (You can also do this when the variable's value pops up
while hovering over a variable in the source code.)

and voila:

Continuing execution of the program demonstrates that, in fact, we still get
the wrong answer--this time, it returns the value 1. This suggests that
truncation is taking place due to integer division.

Before recompiling, we can add in a cast to solve this problem:

return (double) sum / count;

When to Use this Trick

This trick is powerful, and is particularly helpful when you have found one bug, but want to prove that the rest of your code will work. This is particularly handy when your debugging session requires a great deal of setup--for instance, you have to perform a lot of UI to get the right inputs or your code takes a long time to build. (An alternative would be to consider writing a unit test that demonstrates the bug and doesn't require so much setup.)

On the other hand, you can't rely on this trick when you are inside a loop or a function that is called frequently--it's just too much of a pain to have to manually set variables all the time.

5. Set Next Statement

Set Next Statement is a real power tool. If you're debugging, and you've
accidentally (or not so accidentally) stepped past the point where something
interesting happens, you can sometimes "unwind" execution. What this really
means is that, maintaining the current state of the world, you can tell Visual
Studio to go back and start executing from a previous instruction.

You can also use set next statement to jump over code that you believe is
buggy, or to jump past a conditional test on an if statement to execute a path
of code that you want to debug without having to make the condition true.

Setting the next statement can be dangerous and lead to instability if the
stack or registers aren't in a valid state for the line being executed. And
because it won't restore the state of the world, if your code depends on that
state, changing the next statement might not be useful. On the other hand, if
you want to make a call that you're pretty confident won't behave differently,
it can be a great way of avoiding recreating a specific failure just because
you accidentally stepped too far.

For instance, take the following debugging state, where you're about to return
the value total from getSum:

If you had accidentally run too far, it might be very convenient to be able to
simply say, ok, let's start that again:

and then you can go back through executing the loop, perhaps setting the value
of total to 0, since you notice that it wasn't initialized, and then checking
to see if the program gives the correct sum (in which case, you can be pretty
confident that the lack of initialization was the problem).

When to Use this Trick

Many of the same considerations that apply to resetting variable values also apply here. However, you should also be aware that the further ahead or back in a function you set the next statement, the more likely the debugger will not be able to compensate and your program will crash. I usually use this trick to skip over very small chunks of code, or to go back only to a single previous function call.