10 even more Visual Studio debugging tips for Native Development
Original Chinese address: http://www.oschina.net/translate/10-even-more-visual-studio-debugging-tips-for-native? From = 20130127
My previous articles on Visual Studio debugging skills have aroused great interest, so I decided to share more debugging knowledge. In the following list, you can see the Native Development debugging skills (followed by previous articles ). These skills can be applied to vs2005 or later versions (of course some of them can be applied to earlier versions ). If you continue, you can learn the details of each technique.
- Data breakpoint
- Thread rename
- Specific process interruption
- Approximate execution time
- Number formatting
- Memory data formatting
- System DLL interruption
- Load symbol table
- Memory leakage report in MFC
- Debug ATL
Tip 11: Data breakpoint
When the memory location of the data changes, the debugger will interrupt. However, this is the only way to create a data breakpoint for a hardware such as 4 at a time. Data breakpoints can only be added during compilation. You can use the menu (compile> New breakpoint> new data breakpoint) or the breakpoint window to add data breakpoints.
You can use a memory address or address expression. Even if you can see two values on the stack, I think this function is useful when the value on the stack is changed. This helps identify memory corruption.
In the following example, the pointer value has been changed to the object value. To find out where the changes are made, I set a breakpoint at the position of the pointer value storage, such as & PTR (note that this occurs after the pointer is initialized ). When the data is changed, the debugger terminates when someone changes the pointer value and then finds out which code has caused the change.
Additional reading:
- How can I find out if my pointers has upt a memory address?
- How can I find out where my pointer is getting changed?
Tip 12: rename a thread
When you debug a multi-threaded application, the threads window displays the created threads and the current threads. The more threads you need, the more difficult you will find the thread you are looking for (especially when the same program is executed by multiple threads at the same time, you do not know which thread instance is being executed)
The debugger allows you to rename a thread. Right-click a thread and rename it.
You can also name a thread in programming mode. Although this is tricky and must be done after the thread starts, otherwise the debugger will reinitialize it with its default naming rules, the following functions show how to define and use a thread.
typedef struct tagTHREADNAME_INFO
{
DWORD dwType; // must be two bytes in length
LPCSTR szName; // pointer to name (same address space)
DWORD dwThreadID; // Thread ID (-1 calling thread)
DWORD dwFlags; // Reserved for future use, 0 in most cases
} 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 the thread to set the breakpoint
Another useful technique for Multithreaded Applications is to set breakpoints on specified threads, processes, and even computers. You can use the breakpoint filer command to implement this function.
The debugger allows you to use different combinations of thread name, thread ID, process name, process ID, and machine name (using and, or, not connection ). Mastering how to set the thread name makes this filtering technique easier.
Secondary reading:
- How to: specify a breakpoint Filter
- Setting a breakpoint Filter
Tip 14: (inaccurate) scheduled execution
In my previous article, I mentioned using pseudo variables in the watch window. One of the unmentioned ones is @ CLK, which displays the value of a counter to obtain the approximate time required for code execution between two breakpoints, in milliseconds (MS ). However, this method cannot be used for configuration program execution. You should use Visual Studio profiler or performance timer to complete these configurations.
Reset the timer by adding @ CLK = 0 in the watch window or immediate window. Therefore, if you need to calculate the time required for the last code execution, do the following:
- Set a breakpoint at the beginning of a code block
- Set a breakpoint at the end of the 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 the breakpoint at the end of the code block is reached, and view the value of @ CLK in the watch window.
Note that you need to add two expressions in the watch window: @ CLK and @ CLK = 0. It is said that the timer can be reset at the position where the breakpoint is executed each time. This technique can only be used in earlier versions of Visual Studio, but cannot be used in later versions of Visual Studio, such as vs2005 (tested by the author and not supported by vs2005) and later versions.
Secondary reading:
Tip 15: format the number
When you use the watch or quick Watch window to view variables, these values are displayed in the default predefined visual format. When a variable is a number, it is displayed in decimal format according to its type (INT, float, double. However, you can set the debugger to use different types for displaying numbers, or use different hexadecimal values.
To change the display type of a variable, you can add the following prefix before the variable:
- By -- unsigned char (unsigned byte)
- Wo -- unsigned short (unsigned word)
- DW -- unsigned long (unsigned double word)
You can add the following prefix before the variable to change the OS:
- D Or I -- signed decimal number
- U -- unsigned decimal number
- O -- unsigned octal number
- X -- lowercase hexadecimal number
- X -- uppercase hexadecimal number
Secondary reading:
C ++ debugger tips
Tip 16: Format memory data
In addition to numbers, debugger can also display formatted memory data in the watch window, up to 64 bytes. You can add the following suffix after the expression (variable or memory address) to format the data:
- MB or M-hexadecimal 16-byte data, followed by 16 ASCII characters
- MW -- 8 words (word, usually 1 word = 2 byte) data
- MD -- four dual-words (DWORD, usually 1 DWORD = 4 byte) data
- MQ -- two four-character (quad word) data
- Ma -- 64 ASCII characters
- Mu -- 2-byte Unicode Character
Additional reading:
- Format specifiers in C ++
- Debugging tips in developer Studio
Tip 17: Pause the system DLL call.
Sometimes it is useful to pause a function of a DLL when it is called, especially system DLL (such as kernel32.dll and user32.dll ). To implement this pause, you must use the context operator provided by native debugger. You can set the breakpoint location, variable name, or expression:
- {[Function], [Source Code], [module]} breakpoint location
- {[Function], [Source Code], [module]} variable name
- {[Function], [Source Code], [module]} expressions
The braces can be any combination of the function name, source code, and module, but the comma cannot be omitted.
For example, if we need to pause the createthread function call. This function is exported from kernel32.dll, so the context operator should be like this: {, kernel32.dll} createthread. However, this is not the case because the operator requires the name after createthread. Available
Dbh.exe to get the modifier of a specific function (compiled by the compiler ). The following describes how to get the createthread modifier:
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. Therefore, we can create breakpoints,{, Kernel32.dll} _ createthreadstub @ 24.
When a program is paused, the message about no source code at the breakpoint is directly ignored.
Use the call Stack window to view the code for calling this function.
Additional reading:
- How to set breakpoints without source code in Visual Studio 2010
- Context operator (C/C ++ language expressions)
- How to: Set a function breakpoint
Tip 18: load the symbol
When you debug a program, the call Stack window may not display all call stacks, ignoring the information of system DLL (such as kernel32.dll and user32.dll.
By loading the DLL symbolic information, you can obtain all call stack information. In the call Stack window, you can use the context menu (right-click menu) to directly set this effect. You can download these symbols from a predefined symbolic path or Microsoft's symbolic server (for system DLL. After these symbols are downloaded and imported to the 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.
In the source code generated by the MFC wizard, the # include post-mina contains the following pre-processing commands:
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
The code above is how to redefine the new operator.
Many STL header files are incompatible with the new operator defined here. If you include header files such as <map> <vector> <list> <string> after the new operator, the following error occurs (take <vector> as an example ):
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 use debug_new to redefine the new operator after including these STL files.
Additional reading:
Tip 20: Debug ATL
When developing the atl com component, you can view the QueryInterface, addref, and release calls of the COM object in the debugger. By default, these calls are not supported. You need to define two Macros in the pre-processing definition or pre-compilation header file. After these two macros are defined, the call to these functions will be displayed in the output window.
The two macros are:
- _ Atl_debug_qi,Displays the name of each interface to be queried. It must be defined before the atlcom. h header file is included.
- _ Atl_debug_interfaces,When addref or release is called, it displays the number of times the current interface is referenced, the class name, the interface name, and other information. It must be defined before atlbase. H is included.
Secondary reading:
- Debugging tips
- ATL Debugging techniques
- How does _ atl_debug_interfaces work?
Conclusion
Tips mentioned in this article and the previous article does not include all debugging skills, but it can help you solve most of your native application problems.