我的上篇文章《Vistual Studio原生开发的10个调试技巧》引发了很多人的兴趣,所以我决定跟大家分享更多的调试技巧。接下来你又能看到一些对于原生应用程序的很有帮助的调试技巧(接着上一篇文章来编号)。这些技巧需要应用在Vistual Studio 2005 或者更新的版本中(当然也有一些适用于旧版本)。如果你能阅读本文中推荐的一些相关文章,就可以知道每一个技巧的更多信息。

  • 11. 数据断点
  • 12. 线程重命名
  • 13. 给指定线程设置断点
  • 14.(粗略)估算执行时间
  • 15.  数字格式化
  • 16. (内存)数据格式化
  • 17.系统DLL中断
  • 18.加载符号表
  • 19.  监测MFC中的内存泄漏
  • 20.  调试ATL

技巧11: 数据断点

当数据所在的内存位置发生变化时,可以通知调试器进行中断,但是每次只能创建4个字节这样的硬件数据断点。数据断点只能在调试期间添加,可以通过菜单(Debug>New Breakpoint>New Data Breakpoint) 或者断点窗口来添加。

你可以使用内存地址或者地址表达式。尽管栈上和堆上的值你都可以看到,但是我认为当堆上的数值发生变化时,这个功能才会更有用处。它对于识别内存损坏有很大的帮助。

下面的例子中,指针的值发生了变化,不再是它所指向对象的值。为了找出在什么地方发生改变的,我在存储指针值的位置设置了一个断点,即&ptr(注意必须在指针初始化之后)。数据发生变化就意味着有人修改了指针的值,调试器发生中断,我就能找出是哪段代码引起的改变。

更多阅读:

1.怎样查明指针是否损坏内存

2.怎样查明指针在什么地方发生改变

技巧12: 线程重命名

在调试多线程应用程序时,线程窗口会显示创建了哪些线程以及当前正在运行的线程。线程越多,想找到你想要的线程就越困难(尤其是当一段程序被多个线程同时执行的时候,你不能确切地知道哪个才是当前正在执行的线程实例)。

调试器允许修改线程的名字,可以在线程窗口使用线程的快捷菜单,给线程重命名。

也可以在程序里给线程命名,尽管有点棘手,而且必须在线程启动之后给它命名,否则调试器会以默认命名规范将它重新初始化。定义一个线程,并用下面的函数重命名该线程。

C++
typedef struct tagTHREADNAME_INFO {DWORD dwType; // must be 0x1000LPCSTR 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){} }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

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)
{
}
}

更多阅读:

设置线程名字(非托管)

技巧13: 给指定线程设置断点

对于多线程应用程序来说,另一个有用的技巧就是给指定的线程,进程,甚至是计算机中的断点设置过滤.可以通过断点的Filter命令来实现此功能.

调试器允许你指定线程名,线程ID,进程名,进程ID和机器名的任意组合(使用AND,OR,NOT)来设置过滤。了解怎样设置线程名字也使得这项过滤操作变得更加简单。

更多阅读:

  1. 怎样指定断点过滤器
  2. 设置断点过滤

技巧14: (粗略)估算执行时间

在上一篇文章中,我有写关于Watch窗口中的伪变量,有一个没提到的是@clk,它用于显示计数器的值,可以粗略地计算出两个断点之间的代码的执行时间,单位是微秒(μS)。但是,千万不要用这个方法来分析程序的执行效率,应该使用Visual Studio 分析工具或者性能计时器来分析。

可以在Watch 窗口或者即时窗口添加@clk=0来完成对计时器的重置。因此要想估算执行一段代码需要多长时间,可以按照下面的步骤来操作:

  1. 在代码块的开始位置设置断点
  2. 在代码块的结束位置设置断点
    1. 在Watch窗口添加 @clk
    2. 程序进入到第一个断点时,在即时窗口输入@clk=0
    3. 运行程序直到执行进入代码块末尾的断点,查看Watch窗口 @clk的值。

注意网上有一些技巧说在Watch窗口添加两个表达式:@clk和@clk=0,需要在每次执行断点的时候都要重置计时器。这种用法只适用于Visual Studio的老版本,在VS2005及以上版本不再适用。

更多阅读:

调试技巧-@CLK

技巧15:数字格式化

当你在Watch或者Quick Watch窗口查看变量时, 这些值是以默认的预定义可视化形式显示的。而对于数字,则是根据数据类型(integer, float, double),用十进制形式显示的。但是你可以使用调试器把数字用不同的类型或者进制数显示出来。

想要改变显示类型可在变量前加以下前缀:

  1. by –unsigned char (又称为unsigned byte)
  2. wo – unsigned shot(又称为 unsigned word)
  3. dw – unsigned long(又称为 unsigned double word)

要改变显示的进制数在变量前加下列前缀:

  1. d 或者 i– 有符号十进制数
  2. u – 无符号十进制数
  3. o –  无符号八进制数
  4. x – 小写十六进制数
  5. X – 大写十六进制数

更多阅读:

C++ 调试技巧

技巧16:(内存数据)格式化

除了数字,调试器还可以在Watch窗口显示格式化的内存数据,最多64 bytes。 你可以使用在表达式(变量或内存地址)后添加下列说明符作为后缀来格式化数据:

  1. mb 或者 m – 十六进制显示的16字节数据,后跟16个ASCII 字符
  2. mw – 8 words
  3. md – 4 double words
  4. mq –  2 quad-words
  5. ma – 64个ASCII字符
  6. mu – 2字节的UNICODE字符

更多阅读:

  1. C++中的格式说明符
  2. Developer Studio的调试技巧

技巧17: 系统DLL的中断

有时候在DLL中的函数被调用时进行中断是很有用的,像系统DLL(比如 Kernel32.dll 或者user32.dll).实现此中断,需要使用本机调试器提供的上下文运算符.你可以设定断点位置,变量名或者表达式:

1.{[函数],[源码],[模块]}位置

2. [函数],[源码],[模块]}变量名

3. [函数],[源码],[模块]}表达式

花括号里可以是函数名,源代码和模块的任意组合,但是逗号不能省略.

我们假设想要在CreateThread函数被调用时发生中断,这个函数是从kernel32.dll中导出的,所以上下文运算符应该为:  {,,kernel32.dll}CreateThread. 然而,这样并不可行,因为上下文运算符需要CreatThread的修饰符,可以使用DBH.exe来获取一个特定函数的修饰符。

下面就是如何得到CreateThread函数的修饰符的:

C++
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/Symbolsindex address name1 10b4f65 : _BaseCreateThreadPoolThread@122 102e6b7 : _CreateThreadpoolWork@123 103234c : _CreateThreadpoolStub@44 1011ea8 : _CreateThreadStub@245 1019d40 : _NtWow64CsrBasepCreateThread@126 1019464 : ??_C@_0BC@PKLIFPAJ@SHCreateThreadRef?$AA@7 107309c : ??_C@_0BD@CIEDBPNA@TF_CreateThreadMgr?$AA@8 102ce87 : _CreateThreadpoolCleanupGroupStub@09 1038fe3 : _CreateThreadpoolIoStub@16a 102e6f0 : _CreateThreadpoolTimer@12b 102e759 : _CreateThreadpoolWaitStub@12c 102ce8e : _CreateThreadpoolCleanupGroup@0d 102e6e3 : _CreateThreadpoolTimerStub@12e 1038ff0 : _CreateThreadpoolIo@16f 102e766 : _CreateThreadpoolWait@1210 102e6aa : _CreateThreadpoolWorkStub@1211 1032359 : _CreateThreadpool@4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

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

看上去实际名字应该是_CreateThreadStub@24,这样我们就可以创建断点,{,,kernel32.dll}_CreateThreadStub@24

运行程序,发生中断时会有消息提示断点处无相关源代码,直接忽略它。
1
运行程序,发生中断时会有消息提示断点处无相关源代码,直接忽略它。

使用调用栈窗口查看调用该函数的代码。
1
使用调用栈窗口查看调用该函数的代码。

更多阅读:
1
更多阅读:
1.  在Visual Studio 2010中,没有源代码如何设置断点
1
1.  在Visual Studio 2010中,没有源代码如何设置断点
2.  上下文运算符(C/C++语言表达式)
1
2.  上下文运算符(C/C++语言表达式)
3.  怎样给函数设置断点
1
3.  怎样给函数设置断点

技巧18:加载符号表

在调试程序的时候,调用栈窗口不会显示完整的调用栈,跳过了系统DLL(比如kernel32.dll 和 user32.dll)的信息。

可以通过加载这些DLL的符号表来获得完整的调用栈信息,直接在调用栈窗口使用快捷菜单就能完成。你可以从预先指定的符号路径或者微软的符号服务器(如果是系统DLL)来下载符号。符号下载完成后,直接导入到调试器,调用栈就会得到更新。</span>

这些符合也可以从组件Modules窗口导入。

一旦下载完成,符号会保存在缓存中,可以在Tools>Options>Debugging>Symbols中配置。

技巧19:监测MFC中的内存泄漏

如果你想要在MFC应用程序中检测内存泄漏,需要使用宏DEBUG_NEW来重新定new运算符,这是new运算符的修改版本,记录了每个对象内存分配的文件名和行号.在发行版中DEBUG_NEW会解析成new运算符.

向导生成的MFC源文件在#includes后包含了下面的预处理指令:

C++
#ifdef _DEBUG #define new DEBUG_NEW #endif
1
2
3

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

这就是怎样重新定义new运算符的。

然而,很多STL头文件和重新定义的new运算符和版本不兼容.如果你重新定义了new运算符后,又包含了<map>,<vector>,<list>,<string>等头文件的话,就会出现下面的错误(以<vector>为例):

1&gt;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&gt; c:\program files\microsoft visual studio 9.0\vc\include\new.h(85): could be 'void *operator new(size_t,const std::nothrow_t &amp;) throw()' 1&gt; c:\program files\microsoft visual studio 9.0\vc\include\new.h(93): or 'void *operator new(size_t,void *)' 1&gt; while trying to match the argument list '(const char [70], int)' 1&gt; c:\program files (x86)\microsoft visual studio 9.0\vc\include\xmemory(145) : see reference to function template instantiation '_Ty *std::_Allocate&lt;char&gt;(size_t,_Ty *)' being compiled 1&gt; with 1&gt; [ 1&gt; _Ty=char 1&gt; ] 1&gt; c:\program files (x86)\microsoft visual studio 9.0\vc\include\xmemory(144) : while compiling class template member function 'char *std::allocator&lt;_Ty&gt;::allocate(std::allocator&lt;_Ty&gt;::size_type)' 1&gt; with 1&gt; [ 1&gt; _Ty=char 1&gt; ] 1&gt; c:\program files (x86)\microsoft visual studio 9.0\vc\include\xstring(2216) : see reference to class template instantiation 'std::allocator&lt;_Ty&gt;' being compiled 1&gt; with 1&gt; [ 1&gt; _Ty=char 1&gt; ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

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

解决办法就是总是把包含这些STL头文件放在重新定义new运算符之前.

更多阅读:

DEBUG_NEW

技巧20: 调试ATL

在开发ATL COM组件时,你可以在调试器观察COM对象的QueryInterface,AddRef和Release的调用情况.默认情况下并不支持这些,但是你只要在预处理定义或者预编译头文件时定义两个宏,宏定义好之后,关于这些函数的调用信息就会显示在output窗口.

这两个宏如下:

  1. _ATL_DEBUG_QI: 显示你定义的对象里每一个被查询的接口的名字,必须在atlcom.h被包含之前定义.
  2. _ATL_DEBUG_INTERFACES: 在每次AddRef 或者Release被调用时,显示接口的当前引用计数以及对应的类名和接口名,必须在atlbase.h被包含之前定义.

更多阅读:

  1. 调试技巧
  2. ATL调试技巧
  3. _ATL_DEBUG_INTERFACES是如何工作的?

结束语

尽管这两篇文章并不是包含了所有的调试技巧,但是足以帮你解决原生开发中调试时遇到的大多数问题.

Visual Studio原生开发的20条调试技巧相关推荐

  1. Visual Studio原生开发的20条调试技巧(下)

    我的上篇文章<Vistual Studio原生开发的10个调试技巧>引发了很多人的兴趣,所以我决定跟大家分享更多的调试技巧.接下来你又能看到一些对于原生应用程序的很有帮助的调试技巧(接着上 ...

  2. Visual Studio原生开发的10个调试技巧

    最近碰巧读了Ivan Shcherbakov写的一篇文章,<11个强大的Visual Studio调试小技巧>.这篇文章只介绍了一些有关Visual Studio的基本调试技巧,但是还有其 ...

  3. Visual Studio 2015开发Android App启动调试始终无法完成应用部署的解决方案

    创建一个Android App项目后,直接启动调试发现Visual Studio Emulator for Android已成功运行,但应用始终处于Build中(等待时间超过1小时),并未如预期通过a ...

  4. Visual Studio中11个强大的调试技巧和方法

    调试是软件开发周期中很重要的一部分.它具有挑战性,同时也很让人疑惑和烦恼.总的来说,对于稍大一点的程序,调试是不可避免的.最近几年,调试工具的发展让很多调试任务变的越来越简单和省时. 这篇文章总结了可 ...

  5. Visual Studio 2015开发Android App问题集锦

    Visual Studio 2015开发Android App 启动调试始终无法完成应用部署的解决方案 创建一个Android App项目后,直接启动调试发现Visual Studio Emulato ...

  6. 在Visual Studio上开发Node.js程序(2)——远程调试及发布到Azure

    [题外话] 上次介绍了VS上开发Node.js的插件Node.js Tools for Visual Studio(NTVS),其提供了非常方便的开发和调试功能,当然很多情况下由于平台限制等原因需要在 ...

  7. Vue.js+Node.js全栈开发教程:通过Visual Studio Code开发调试Node应用

    目前,有多种开发工具可以支持Node.js应用的开发,比如: jetBrains WebStorm.Eclipse.Visual Studio Code等.这些开发工 具原则上是"条条大路通 ...

  8. 在Visual Studio上开发Node.js程序

    在Visual Studio上开发Node.js程序 原文:在Visual Studio上开发Node.js程序 [题外话] 最近准备用Node.js做些东西,于是找找看能否有Visual Studi ...

  9. Visual Studio Code 开发 .NET Core 看这篇就够了

    来源:依乐祝 cnblogs.com/yilezhu/p/9926078.html 在本文中,我将带着大家一步一步的通过图文的形式来演示如何在Visual Studio Code中进行.NET Cor ...

最新文章

  1. R语言使用caret包构建GBM模型:在模型最优参数已知的情况下,拟合整个训练集,而无需进行任何重采样或参数调优
  2. makefile小技巧
  3. PHP登录状态判定的思路
  4. 男人25岁,别让“努力“毁了你的辉煌
  5. Redis pub/sub机制在实际运用场景的理解(转载)
  6. mysql中的联结_MySQL的联结(Join)语法
  7. flex states的用法
  8. 数据科学入门与实战:玩转pandas之七数据透视
  9. 国外程序猿整理的C++大全
  10. SpringMVC开发框架中使用@ResponseBody注解后返回的json字符串中文乱码问题
  11. leetcode : Rotate List
  12. 2017计算机办公自动化试题,2017年办公自动化考试试题及解答
  13. HTML 日期格式转换
  14. 用计算机怎么按四分之三次方等于多少,八十一分之十六的负四分之三次方怎么算,要具体过程,答案是多少?...
  15. jspx框架使用总结-页面开发
  16. matlab实现icp算法,ICP算法(Iterative Closest Point)及VTK实现
  17. 苹果iphone5/iphone5s充电器(A1443)及电路原理图
  18. 你控制不了情绪,怎么过得好这一生?
  19. Codeforces Round #811 (Div. 3) E. Add Modulo 10
  20. 开机后黑屏看不到桌面_电脑开机后不显示桌面图标怎么回事 电脑开机后不显示桌面图标解决办法大全!...

热门文章

  1. html js css倒计时,js+css3倒计时动画特效
  2. 计算机二级2021辅导书,2021该如何准备计算机二级考试?
  3. Codeforces Round #208 (Div. 2)D. Dima and Hares
  4. mysql之慢日志查询
  5. SpringBoot入门教程(十)应用监控Actuator
  6. Uva12716 素数筛思想的应用
  7. 整型的赋值超出该类型的取值范围
  8. CVE-2014-6271 漏洞告警
  9. 关于MGS4和XSI
  10. Windows Mobile 6 中为开发人员提供的新功能(1)