10 More Visual Studio Debugging Tips for Native Development

This article proposes a list of debugging tips for native development with Visual Studio.

I have recently run onto this article by Ivan Shcherbakov called 10+ powerful debugging tricks with Visual Studio. Though the article presents some rather basic tips of debugging with Visual Studio, there are others at least as helpful as those. Therefore I put together a list of ten more debugging tips for native development that work with at least Visual Studio 2008. (If you work with managed code, the debugger has even more features and there are several articles on CodeProject that present them.) Here is my list of additional tips:

It is possible to instruct the debugger to break when an exception occurs, before a handler is invoked. That allows you to debug your application immediately after the exception occurs. Navigating the Call Stack should allow you to figure the root cause of the exception.

Visual Studio allows you to specify what category or particular exception you want to break on. A dialog is available from Debug > Exceptions menu. You can specify native (or managed) exceptions and aside from the default exceptions known to the debugger, you can add your custom exceptions.

Here is an example with the debugger breaking when a std::exception is thrown.

Sometimes you'd like to watch the value of an object (on the heap) even after the symbol goes of scope. When that happens, the variable in the Watch window is disabled and cannot be inspected any more (nor updated) even if the object is still alive and well.
It is possible to continue to watch it in full capability if you know the address of the object. You can then cast the address to a pointer of the object type and put that in the Watch window.

In the example bellow, _foo is no longer accessible in the Watch window after stepping out of do_foo(). However, taking its address and casting it to foo* we can still watch the object.

If you work with large arrays (let say at least some hundred elements, but maybe even less) expanding the array in the Watch window and looking for some particular range of elements is cumbersome, because you have to scroll a lot.
And if the array is allocated on the heap you can't even expand its elements in the Watch window.
There is a solution for that. You can use the syntax (array + <offset>), <count> to watch a particular range of <count> elements starting at the <offset> position (of course, array here is your actual object).
If you want to watch the entire array, you can simply say array, <count>.

If your array is on the heap, then you can expand it in the Watch window, but to watch a particular range you'd have to use a slightly different the syntax: ((T*)array + <offset>), <count> (notice this syntax also works with arrays on the heap). In this case T is the type of the array's elements.

If you work with MFC and use the "array" containers from it, like CArray, CDWordArray, CStringArray, etc., you can of course apply the same filtering, except that you must watch the m_pData member of the array, which is the actual buffer holding the data.

Many times when you debug the code you probably step into functions you would like to step over, whether it's constructors, assignment operators or others. One of those that used to bother me the most was the CString constructor.
Here is an example when stepping into take_a_string() function first steps into CString's constructor.

Luckily it is possible to tell the debugger to step over some methods, classes or entire namespaces.
The way this was implemented has changed. Back in the days of VS 6 this used to be specified through the autoexp.dat file.
Since Visual Studio 2002 this was changed to Registry settings. To enable stepping over functions you need to add some values in Registry (you can find all the details here):

The actual location depends on the version of Visual Studio you have and the platform of the OS (x86 or x64, because the Registry has to views for 64-bit Windows)

The value name is a number and represents the priority of the rule; the higher the number the more precedence the rules has over others.

The value data is a REG_SZ value representing a regular expression that specifies what to filter and what action to perform.

To skip stepping into any CString method I have added the following rule:

Having this enabled, even when you press to step into take_a_string() in the above example the debugger skips the CString's constructor.

Seldom you might need to attach with the debugger to a program, but you cannot do it with the Attach window (maybe because the break would occur too fast to catch by attaching), nor you can start the program in debugger in the first place. You can cause a break of the program and give the debugger a chance to attach by calling the __debugbreak() intrinsic.

void break_for_debugging()
{
__debugbreak();
}

There are actually other ways to do this, such as triggering interruption 3, but this only works with x86 platforms (ASM is no longer supported for x64 in C++).
There is also a DebugBreak() function, but this is not portable, so the intrinsic is the recommended method.

__asmint3;

When your program executes the intrinsic it stops, and you get a chance to attach a debugger to the process.

Memory leaks are an important problem in native development and finding them could be a serious challenging especially in large projects. Visual Studio provides reports about detected memory leaks and there are other applications (free or commercial) to help you with that. In some situations though, it is possible to use the debugger to break when an allocation that eventually leaks is done. To do this however, you must find a reproducible allocation number (which might not be that easy though). If you are able to do that, then the debugger can break the moment that is performed.

Let's consider this code that allocates 8 bytes, but never releases the allocated memory. Visual Studio displays a report of the leaked objects, and running this several times I could see it's always the same allocation number (341).

Debug and Release builds are meant for different purposes. While a Debug configuration is used for development, a Release configuration, as the name implies should be used for the final version of a program. Since it's supposed that the application meets the required quality to be published, such a configuration contains optimizations and settings that break the debugging experience of a Debug build. Still, sometimes you'd like to be able to debug the Release build the same way you debug the Debug build. To do that, you need to perform some changes in the configuration.
However, in this case one could argue you no longer debug the Release build, but rather a mixture of the Debug and the Release builds.

Conclusions

The debugging tips presented in this article and the original article that inspired this one should provide the necessary tips for most of the debugging experiences and problems. To get more information about these tips I suggest following the additional readings.

Share

About the Author

Marius Bancila is a Microsoft MVP for VC++. He works as a system architect for Visma, a Norwegian-based company. He is mainly focused on building desktop applications with VC++ and VC#. He keeps a blog at http://www.mariusbancila.ro/blog, focused on Windows programming. He is the co-founder of codexpert.ro, a community for Romanian C++ programmers.