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.
- Data breakpoints
- Renaming threads
- Breaking on particular threads
- (Roughly) Timing Execution
- Formatting numbers
- Formatting (memory) data
- Breaking on system DLLs
- Loading symbols
- Memory leak reports in MFC
- Debugging ATL
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.
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.
Additional readings:
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).
The debugger allows you change the names of the threads. Use the context menu on a thread and rename it.
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.
typedef struct tagTHREADNAME_INFO
{
DWORD dwType; LPCSTR szName; DWORD dwThreadID; DWORD dwFlags; } 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:
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.
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:
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:
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
Additional reading:
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
Additional readings:
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
.
Run the program and when it breaks, ignore the message that there is no source code associated with the breakpoint.
Use the Call Stack window to navigate to your code that made the call to the function.
Additional readings:
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).
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.
The symbols can also be loaded from the Modules window.
Once downloaded, the symbols are stored in a cache that can be configured from Tools > Options > Debugging > Symbols.
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
:
#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:
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.