Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / debugging

10 Even More Visual Studio Debugging Tips for Native Development

4.96/5 (41 votes)
30 Dec 2012CPOL8 min read 77.5K  
This article proposes a list of even more debugging tips for native development with Visual Studio.

My previous article on debugging tips for Visual Studio was met with so much interest. That encouraged me to share even more debugging techniques. Therefore, you can find below a list of other helpful tips for debugging native applications (continuing the numbering from the previous article). These tips work with Visual Studio 2005 or newer (and at least some of them with older versions too). You can get additional information about each technique if you follow the recommended readings.

  1. Data breakpoints
  2. Renaming threads
  3. Breaking on particular threads
  4. (Roughly) Timing Execution
  5. Formatting numbers
  6. Formatting (memory) data
  7. Breaking on system DLLs
  8. Loading symbols
  9. Memory leak reports in MFC
  10. Debugging ATL

Tip 11: Data Breakpoints

It is possible to instruct the debugger to break when data at a certain memory location changes. It is however only possible to create 4 such hardware data breakpoints at a time. Data breakpoints can be added only during debugging, either from the menu (Debug > New Breakpoint > New Data Breakpoint) or from the Breakpoints window.

Image 1

You can use either a memory address or an expression that evaluates to an address. Even though you can watch both values on the stack and on the heap, I'd say this feature is mostly helpful to find when values on the heap are changed. This can be a great help in identifying memory corruption.

In the example below, the value of a pointer is changed, instead of the value of the object it points to. To figure out where that happens, I set a breakpoint on the memory where the value of the pointer is stored, i.e., &ptr (notice this must happen after the pointer is initialized). When the data changes, which means someone alters the value of the pointer, the debugger breaks and I can figure which piece of code is responsible for that.

Image 2

Additional readings:

Tip 12: Renaming Threads

When you debug multi-threaded applications, the Threads window shows you what threads are created and which one is currently running. The more threads you have, the harder it could be to figure out what thread exactly you are looking at (especially when the same thread procedure is run by several threads, and you don't know exactly which thread instance is currently executing).

Image 3

The debugger allows you change the names of the threads. Use the context menu on a thread and rename it.

Image 4

It is also possible to programmatically name a thread, though this is a little bit trickier and must be done after the thread has started, otherwise the debugger will re-initialize it with its default naming convention. Define and use the following function for renaming a thread.

C++
typedef struct tagTHREADNAME_INFO
{
	DWORD dwType;        // must be 0x1000
	LPCSTR szName;       // pointer to name (in same addr space)
	DWORD dwThreadID;    // thread ID (-1 caller thread)
	DWORD dwFlags;       // reserved for future use, most be zero
} THREADNAME_INFO;

void SetThreadName(DWORD dwThreadID, LPCSTR szThreadName)
{
	THREADNAME_INFO info;
	info.dwType = 0x1000;
	info.szName = szThreadName;
	info.dwThreadID = dwThreadID;
	info.dwFlags = 0;

	__try
	{
		RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info);
	}
	__except (EXCEPTION_CONTINUE_EXECUTION)
	{
	}
}

Additional reading:

Tip 13: Breaking on Particular Threads

Another helpful technique for multi-threading applications is to filter the breakpoints to certain threads, processes or even computers. This is possible by using the Filter command on a breakpoint.

Image 5

The debugger allows you to specify any combination (AND, OR, NOT) of ThreadName, ThreadId, ProcessName, ProcessId and MachineName. Knowing how to set a thread name already can make this filtering even simpler.

Additional readings:

Tip 14: (Roughly) Timing Execution

In my previous article, I wrote about pseudo-variables in the Watch window. One not mentioned then is @clk, which shows the value of a counter and can help getting a rough idea of how much time the code between two breakpoints took to execute. The value is expressed in microseconds. However, this is by no means a method to profile execution. You should use the Visual Studio profiler and/or performance timers for that.

It is possible to reset the clock by adding @clk=0 in the Watch window or executing that in the Immediate window. Therefore, to figure how much time some piece of code took to execute, do the following:

  • Set a breakpoint at the beginning of the block
  • Set a breakpoint at the end of the block
  • Add @clk in the Watch window
  • When the first breakpoint is hit, type @clk=0 in the Intermediate window.
  • Run the program until the breakpoint at the end of the block is hit and check the value of @clk in the Watch window.

Notice there are tips on the web that instruct you to add two expressions in the Watch window: @clk followed by a @clk=0, which would allegedly reset the clock each time a breakpoint is hit. This used to work in older versions of Visual Studio, but no longer works (for sure in VS2005 and newer).

Additional reading:

Tip 15: Formatting Numbers

When you watch variables in the Watch or Quick Watch window, the values are displayed using the default pre-defined visualizers. When it comes to numbers, these are displayed according to their types (integer, float, double) and using the decimal base. But you can force the debugger to show the numbers as a different type, or with a different numeric base, or both.

To change the displayed type, prefix the variable with:

  • by for unsigned char (aka unsigned byte)
  • wo for unsigned short (aka unsigned word)
  • dw for unsigned long (aka unsigned double word)

To change the displayed base suffix the variable name with:

  • , d or , i for signed decimal
  • , u for unsigned decimal
  • , o for unsigned octal
  • , x for lowercase hex or , X for uppercase hex

Image 6 Image 7

Additional reading:

Tip 16: Formatting (Memory) Data

Apart from numbers, the debugger can also show formatted memory values, up to 64 bytes, in the Watch window. You can use one of the following specifiers after an expression (variable or memory address) to format the data:

  • mb or m - 16 bytes in hex followed by 16 ASCII characters
  • mw - 8 words
  • md - 4 double words
  • mq - 2 quad-words
  • ma - 64 ASCII characters
  • mu - 2-byte UNICODE characters

Image 8

Additional readings:

Tip 17: Breaking on System DLLs

Sometimes, it's useful to break when some function in a DLL, like a system DLL (think kernel32.dll or user32.dll) is called. To do this, one must use the context operator provided by the native debugger. You can qualify a breakpoint location, variable name or expression:

  • {[function],[source],[module] } location
  • {[function],[source],[module] } variable_name
  • {[function],[source],[module] } expression

The braces can contain any combination of function name, source and module, but the commas must not be omitted.

Let's say we want to break when CreateThread is called. This function is exported from kernel32.dll, and therefore the context operator should look like this: {,,kernel32.dll}CreateThread. However, this does not work as the operator requires the decorated name of CreateThread. One could use DBH.exe to figure out what the decorated name for a specific function is.

Here is how you can figure the decorated name for CreateThread:

C:\Program Files (x86)\Debugging Tools for Windows (x86)>dbh.exe -s:srv*C:\Symbo
ls*http://msdl.microsoft.com/Download/Symbols -d C:\Windows\SysWOW64\kernel32.dl
l enum *CreateThread*
Symbol Search Path: srv*C:\Symbols*http://msdl.microsoft.com/Download/Symbols

 index            address     name
     1            10b4f65 :   _BaseCreateThreadPoolThread@12
     2            102e6b7 :   _CreateThreadpoolWork@12
     3            103234c :   _CreateThreadpoolStub@4
     4            1011ea8 :   _CreateThreadStub@24
     5            1019d40 :   _NtWow64CsrBasepCreateThread@12
     6            1019464 :   ??_C@_0BC@PKLIFPAJ@SHCreateThreadRef?$AA@
     7            107309c :   ??_C@_0BD@CIEDBPNA@TF_CreateThreadMgr?$AA@
     8            102ce87 :   _CreateThreadpoolCleanupGroupStub@0
     9            1038fe3 :   _CreateThreadpoolIoStub@16
     a            102e6f0 :   _CreateThreadpoolTimer@12
     b            102e759 :   _CreateThreadpoolWaitStub@12
     c            102ce8e :   _CreateThreadpoolCleanupGroup@0
     d            102e6e3 :   _CreateThreadpoolTimerStub@12
     e            1038ff0 :   _CreateThreadpoolIo@16
     f            102e766 :   _CreateThreadpoolWait@12
    10            102e6aa :   _CreateThreadpoolWorkStub@12
    11            1032359 :   _CreateThreadpool@4

Looks like the actual name is _CreateThreadStub@24. So we should create a breakpoint at {,,kernel32.dll}_CreateThreadStub@24.

Image 9

Run the program and when it breaks, ignore the message that there is no source code associated with the breakpoint.

Image 10

Use the Call Stack window to navigate to your code that made the call to the function.

Image 11

Additional readings:

Tip 18: Loading Symbols

When you debug your application, the Call Stack window might not display a full call stack, but skip information about system DLLs (such as kernel32.dll and user32.dll).

Image 12

It is possible to get the full stack by loading the symbols for these DLLs. This can be done directly from the Call Stack window, using the context menu. You can either download from the pre-specified symbol path, or from Microsoft's symbols server (if it's a system DLL). After the symbols are downloaded and loaded into the debugger, the Call Stack updates.

Image 13

The symbols can also be loaded from the Modules window.

Image 14

Once downloaded, the symbols are stored in a cache that can be configured from Tools > Options > Debugging > Symbols.

Image 15

Tip 19: Memory Leak Reports in MFC

If you want to get memory leak reports in MFC application, you can re-defined the new operator with a DEBUG_NEW macro, which is a modified version of the new operator that keeps track of the filename and line number for each object that it allocates. In a release build, DEBUG_NEW resolves to operator new.

The wizard generated MFC source files contain the following pre-processor directives after the #includes:

C++
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

This is how you can re-define the new operator.

However, many STL headers are incompatible with this version of operator new. If you include <map>, <vector>, <list>, <string> and others after you re-define operator new, you get errors like this (shown for <vector>):

1>c:\program files (x86)\microsoft visual studio 9.0\vc\include\xmemory(43) : 
   error C2665: 'operator new' : none of the 5 overloads could convert all the argument types
1>        c:\program files\microsoft visual studio 9.0\vc\include\new.h(85): 
   could be 'void *operator new(size_t,const std::nothrow_t &) throw()'
1>        c:\program files\microsoft visual studio 9.0\vc\include\new.h(93): or       
   'void *operator new(size_t,void *)'
1>        while trying to match the argument list '(const char [70], int)'
1>        c:\program files (x86)\microsoft visual studio 9.0\vc\include\xmemory(145) : 
   see reference to function template instantiation '_Ty *std::_Allocate<char>(size_t,_Ty *)' 
   being compiled
1>        with
1>        [
1>            _Ty=char
1>        ]
1>        c:\program files (x86)\microsoft visual studio 9.0\vc\include\xmemory(144) : 
           while compiling class template member function 'char *std::allocator<_Ty>::allocate
           (std::allocator<_Ty>::size_type)'
1>        with
1>        [
1>            _Ty=char
1>        ]
1>        c:\program files (x86)\microsoft visual studio 9.0\vc\include\xstring(2216) : 
           see reference to class template instantiation 'std::allocator<_Ty>' being compiled
1>        with
1>        [
1>            _Ty=char
1>        ]

The solution is to always include these STL headers before redefining new with DEBUG_NEW.

Additional reading:

Tip 20: Debugging ATL

When you develop ATL COM components, you can get some help from the debugger to watch calls for QueryInterface, AddRef and Release on your COM objects. This support is not enabled by default, but you can specify two macros to the preprocessor definitions or the pre-compiled header file. When those macros are defined, information about these calls is displayed in the Output window.

These two macros are:

  • _ATL_DEBUG_QI displays the name of each interface that is queried for on your objects. It must be defined before atlcom.h is included.
  • _ATL_DEBUG_INTERFACES display current reference count for the interface together with the class name and interface name, every time AddRef or Release is called. It must be defined before atlbase.h is included.

Additional readings:

Conclusion

The tips provided in this article and the previous one (although it does not cover everything that can be said about debugging) should prepare you for most debugging situations you could encounter with native applications.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)