The previous article on debugging techniques for Visual Studio has aroused great interest so much that I decided to share more knowledge of debugging. In the following list you can see the debugging techniques for writing native development (followed by the previous article numbering). These techniques can be applied in VS2005 or later versions (some of which can be applied to older versions of course). If you continue, you can learn more about each technique.
Tip 11: Data breakpoints
The debugger will break when the memory location of the data is changed. However, this is the only data breakpoint that can create 4 of such hardware at a time. Data breakpoints can only be added during compilation, either through a menu (Compile > New Breakpoints > New data Breakpoint) or through a breakpoint window.
You can use either a memory address or an address expression. Even if you can see the two values on the stack, I think this function is usually useful when the value on the heap is changed. This is a great help in identifying memory corruption.
In the following example, the value of the pointer has changed to the value of the object being pointed to. In order to find out where the changes were made, I set a breakpoint at the location where the pointer value was stored, such as &ptr (note that this occurs after the pointer is initialized). When the data changes, the debugger terminates, thinking that someone changed the value of the pointer, and then discovers which code caused the change.
Tip 12: Line thread naming
When you debug a multithreaded application, the Threads window shows which threads were created, as well as the current thread. The more threads you have, the harder it is to find the thread you're looking for (especially when the same program is executed by multiple threads at the same time, you don't know which instance of the thread is currently executing)
The debugger allows you to rename a thread. Right-click a thread and rename it.
You can also name the thread programmatically, although it's tricky and must be done after the thread starts, or the debugger will re-initialize it with its default naming convention, and the following function shows how to define and use a thread.
Typedef struct tagTHREADNAME_INFO
{
DWORD dwType; // must be two bytes long
LPCSTR szName; // pointer to naming (same address space)
DWORD dwThreadID; // Thread ID (-1 call thread)
DWORD dwFlags; // Reserved for inactivity, in most cases 0
} 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)
{
}
}
Tip 13: Specify a thread to set breakpoints
Another useful technique for multithreaded applications is to set breakpoints on a specified thread, process, or even computer. You can use the filer command of breakpoints to implement this functionality.
The debugger allows you to use different combinations of thread name, thread ID, process name, process ID, and machine name (using and, or, not). Mastering how to set the thread name also makes this filtering technique easier to operate.
Tip 14: (inaccurate) timed execution
In my previous article I mentioned the use of pseudo-variables in the Watch window. One of the things that is not mentioned is @clk, which displays the value of a counter that is used to obtain the approximate time required for code execution between two breakpoints, in milliseconds (ms). However, this method cannot be used to configure program execution. You should use Visual Studio Profiler or the performance timer to complete these configurations.
Reset the timer by adding @clk=0 to the Watch window or the Immediate window. Therefore, if you need to calculate the time required to execute the last code, do the following:
Set breakpoints at the start of a code block
Set breakpoints at end of code block
Add @clk in the Watch window
When the first breakpoint is triggered, enter @clk=0 in the intermediate window
Run the program until you encounter a breakpoint at the end of the code block and view the value of @clk in the Watch window
Note that there are tricks on the web to add two expressions to the Watch window: @clk and @clk=0, it is said that the timer can be reset every time the breakpoint is executed. This technique can only be used in earlier versions of Visual Studio, but not in high-version vs, such as VS2005 (the author has tested, VS2005 does not support this technique), and later.
Tip 15: Format numbers
When you use watch or the Quick Watch window to view variables, these values are displayed in the default predefined visual format. When a variable is a number, the display form follows their type (int, float, double) and is displayed in decimal. However, you can set the debugger to use different types when displaying numbers, or use different binary.
Change the variable display type to add the following prefix before the variable:
by--unsigned char (unsigned byte)
wo--unsigned short (unsigned word)
dw--unsigned Long (unsigned double word)
Changing the input of variable display you can add the following prefixes before the variables:
D or i--signed decimal number
u--unsigned decimal number
o--unsigned octal number
x--lowercase hexadecimal number
x--uppercase hexadecimal number
Tip 16: Format memory data
In addition to the numbers, debugger can also display formatted memory data in the Watch window, up to a maximum of 64 bytes. You can format the data by adding the following suffix after the expression (variable or memory address):
MB or m--hexadecimal display of 16 bytes of data followed by 16 ASCII characters
Mw--8 Word (Word, usually 1 word = 2 BYTE) data
Md--4 Double Word (DWORD, usually 1 DWORD = 4 BYTE) of data
Mq--2 Four words (Quad word) data
Ma--64 an ASCII character
Mu--2 byte Unicode characters
Tip 17: Pausing at the system DLL call
Sometimes pausing when a function of a DLL is invoked is useful, especially for system DLLs (such as Kernel32.dll, User32.dll). Implementing this pause requires the use of the context operator provided by native debugger. You can set the breakpoint position, the variable name, or an expression:
{[function],[source code],[module]} breakpoint location
{[function],[source code],[module]} variable name
{[function],[source code],[module]} expression
Curly braces can be any combination of function names, source code, and modules, but commas cannot be omitted.
For example, if we need to pause on a createthread function call. This function is exported from Kernel32.dll, so the context operator should be like this: {,, Kernel32.dll}createthread. However, this does not work because the operator requires the name after the CreateThread decoration. You can use DBH.exe to get the decorated name of a particular function (compiler compilation Generation).
Here's how to get the decorated name of 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
It looks like the real name is _createthreadstub@24. So we can create breakpoints, {,, kernel32.dll}_createthreadstub@24.
Run the program, ignoring the message prompt about no related source code at the breakpoint location when a pause is encountered.
Use the call Stack window to see the code that called the function.
Tip 18: Load Symbols
When you debug a program, the Call Stack window may not display the entire call stack, ignoring information from the system DLL (for example, Kernel32.dll, User32.dll).
By loading the symbolic information for these DLLs, you get all the call stack information, and in the Call Stack window, use the context menu (right-click menu) to set the effect directly. You can download these symbols from predefined symbolic paths or from Microsoft's symbol servers (for system DLLs). After these symbols are downloaded and imported into debugger, the call stack is updated as follows:
These symbols can also be imported from the Module window.
Once loaded, these symbols are saved in the cache and can be configured in Tools>options>debugging>symbols.
Tip 19: Report A memory Leak in MFC
If you want to monitor memory leaks in an MFC application, you can use the macro debug_new to redefine the new operator, which is a modified version of the new operator that records the file name and number of rows in which it allocates memory. The debug_new built in release will parse into the original new operator.
The MFC Wizard generates the source code in # # # after Mina contains the following preprocessor directives:
#ifdef _debug#define New Debug_new#endif
The above code is the way to redefine the new operator.
Many STL header files are incompatible with the new operator defined here. If you include the <map><vector><list><string> header file after redefining operator new, you will get the following error (for example, <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 workaround is to use DEBUG_NEW to redefine the new operator after the STL files are included.
Tip 20: Debug ATL
When you develop an ATL COM component, you can see the invocation of QueryInterface, AddRef, and release of the COM object you are developing in debugger. The production of these calls is not supported by default, and you need to define two macros in a preprocessing definition or a precompiled header file. After the two macros are defined, calls to these functions appear in the Output window.
The two macros are:
_atl_debug_qi, displays the name of each queried interface. must be defined before the Atlcom.h header file is included.
_atl_debug_interfaces, whenever AddRef or release is invoked, displays the current interface's number of references and the class name, interface name, and so on. must be defined before atlbase.h is included.
The above is the entire content of this article, I hope you combine the previously shared articles to learn, proficiency in Visual Studio debugging skills.