Visual Studio原生开发的20条调试技巧(下)
我的上篇文章《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: 线程重命名
在调试多线程应用程序时,线程窗口会显示创建了哪些线程以及当前正在运行的线程。线程越多,想找到你想要的线程就越困难(尤其是当一段程序被多个线程同时执行的时候,你不能确切地知道哪个才是当前正在执行的线程实例)。
调试器允许修改线程的名字,可以在线程窗口使用线程的快捷菜单,给线程重命名。
也可以在程序里给线程命名,尽管有点棘手,而且必须在线程启动之后给它命名,否则调试器会以默认命名规范将它重新初始化。定义一个线程,并用下面的函数重命名该线程。
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)来设置过滤。了解怎样设置线程名字也使得这项过滤操作变得更加简单。
更多阅读:
- 怎样指定断点过滤器
- 设置断点过滤
技巧14: (粗略)估算执行时间
在上一篇文章中,我有写关于Watch窗口中的伪变量,有一个没提到的是@clk,它用于显示计数器的值,可以粗略地计算出两个断点之间的代码的执行时间,单位是微秒(μS)。但是,千万不要用这个方法来分析程序的执行效率,应该使用Visual Studio 分析工具或者性能计时器来分析。
可以在Watch 窗口或者即时窗口添加@clk=0来完成对计时器的重置。因此要想估算执行一段代码需要多长时间,可以按照下面的步骤来操作:
- 在代码块的开始位置设置断点
- 在代码块的结束位置设置断点
- 在Watch窗口添加 @clk
- 程序进入到第一个断点时,在即时窗口输入@clk=0
- 运行程序直到执行进入代码块末尾的断点,查看Watch窗口 @clk的值。
注意网上有一些技巧说在Watch窗口添加两个表达式:@clk和@clk=0,需要在每次执行断点的时候都要重置计时器。这种用法只适用于Visual Studio的老版本,在VS2005及以上版本不再适用。
更多阅读:
调试技巧-@CLK
技巧15:数字格式化
当你在Watch或者Quick Watch窗口查看变量时, 这些值是以默认的预定义可视化形式显示的。而对于数字,则是根据数据类型(integer, float, double),用十进制形式显示的。但是你可以使用调试器把数字用不同的类型或者进制数显示出来。
想要改变显示类型可在变量前加以下前缀:
- by –unsigned char (又称为unsigned byte)
- wo – unsigned shot(又称为 unsigned word)
- dw – unsigned long(又称为 unsigned double word)
要改变显示的进制数在变量前加下列前缀:
- d 或者 i– 有符号十进制数
- u – 无符号十进制数
- o - 无符号八进制数
- x – 小写十六进制数
- X – 大写十六进制数
更多阅读:
C++ 调试技巧
技巧16:(内存数据)格式化
除了数字,调试器还可以在Watch窗口显示格式化的内存数据,最多64 bytes。 你可以使用在表达式(变量或内存地址)后添加下列说明符作为后缀来格式化数据:
- mb 或者 m – 十六进制显示的16字节数据,后跟16个ASCII 字符
- mw – 8 words
- md – 4 double words
- mq - 2 quad-words
- ma – 64个ASCII字符
- mu – 2字节的UNICODE字符
更多阅读:
- C++中的格式说明符
- Developer Studio的调试技巧
技巧17: 系统DLL的中断
有时候在DLL中的函数被调用时进行中断是很有用的,像系统DLL(比如 Kernel32.dll 或者user32.dll).实现此中断,需要使用本机调试器提供的上下文运算符.你可以设定断点位置,变量名或者表达式:
1.{[函数],[源码],[模块]}位置
2. [函数],[源码],[模块]}变量名
3. [函数],[源码],[模块]}表达式
花括号里可以是函数名,源代码和模块的任意组合,但是逗号不能省略.
我们假设想要在CreateThread函数被调用时发生中断,这个函数是从kernel32.dll中导出的,所以上下文运算符应该为: {,,kernel32.dll}CreateThread. 然而,这样并不可行,因为上下文运算符需要CreatThread的修饰符,可以使用DBH.exe来获取一个特定函数的修饰符。
下面就是如何得到CreateThread函数的修饰符的:
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. 在Visual Studio 2010中,没有源代码如何设置断点
2. 上下文运算符(C/C++语言表达式)
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后包含了下面的预处理指令:
1
2
3
|
#ifdef _DEBUG #define new DEBUG_NEW #endif |
这就是怎样重新定义new运算符的。
然而,很多STL头文件和重新定义的new运算符和版本不兼容.如果你重新定义了new运算符后,又包含了<map>,<vector>,<list>,<string>等头文件的话,就会出现下面的错误(以<vector>为例):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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 > ] |
解决办法就是总是把包含这些STL头文件放在重新定义new运算符之前.
更多阅读:
DEBUG_NEW
技巧20: 调试ATL
在开发ATL COM组件时,你可以在调试器观察COM对象的QueryInterface,AddRef和Release的调用情况.默认情况下并不支持这些,但是你只要在预处理定义或者预编译头文件时定义两个宏,宏定义好之后,关于这些函数的调用信息就会显示在output窗口.
这两个宏如下:
- _ATL_DEBUG_QI: 显示你定义的对象里每一个被查询的接口的名字,必须在atlcom.h被包含之前定义.
- _ATL_DEBUG_INTERFACES: 在每次AddRef 或者Release被调用时,显示接口的当前引用计数以及对应的类名和接口名,必须在atlbase.h被包含之前定义.
更多阅读:
- 调试技巧
- ATL调试技巧
- _ATL_DEBUG_INTERFACES是如何工作的?
来自:http://blog.jobbole.com/45447/
Visual Studio原生开发的20条调试技巧(下)的更多相关文章
- Visual Studio 原生开发的10个调试技巧(二)
原文:Visual Studio 原生开发的10个调试技巧(二) 我以前关于 Visual Studio 调试技巧的文章引起了大家很大的兴趣,以至于我决定分享更多调试的知识.以下的列表中你可以看到写原 ...
- Visual Studio原生开发的10个调试技巧(二)
来源:oschina 发布时间:2013-08-10 阅读次数:397 51 我以前关于Visual Studio调试技巧的文章引起了大家很大的兴趣,以至于我决定分享更多调试的知识.以下的列表中你 ...
- (转)Visual Studio原生开发的10个调试技巧(二)
我以前关于Visual Studio调试技巧的文章引起了大家很大的兴趣,以至于我决定分享更多调试的知识.以下的列表中你可以看到写原生开发的调试技巧(接着以前的文章来编号).这些技巧可以应用在VS200 ...
- Visual Studio原生开发的10个调试技巧
这篇文章只介绍了一些有关Visual Studio的基本调试技巧,但是还有其他一些同样有用的技巧.我整理了一些Visual Studio(至少在VS 2008下)原生开发的调试技巧.(如果你是工作在托 ...
- Visual Studio原生开发的10个调试技巧(一)
最近碰巧读了Ivan Shcherbakov写的一篇文章,<11个强大的Visual Studio调试小技巧>.这篇文章只介绍了一些有关Visual Studio的基本调试技巧,但是还有其 ...
- Visual Studio原生开发的10个调试技巧(转)
本文由 伯乐在线 - JingerJoe 翻译自 Marius Bancila.转载请参见文章末尾处的要求. [感谢@_La_Isla_Bonita 的热心翻译.如果其他朋友也有不错的原创或译文, ...
- 10个Visual Studio原生开发调试技巧
10个Visual Studio原生开发调试技巧(1) 2013-05-29 13:30 佚名 开源中国 我要评论(1) 字号:T | T 以下的列表中你可以看到写原生开发的调试技巧(接着以前的文章来 ...
- 使用Visual Studio Code开发(编译、调试)C++程序
总体安装步骤 安装VSC(Visual Studio Code). 安装C/C++编译器(如MinGW-w64),然后配置好环境变量.//完成这步即可在VSC的终端(命令行)下编译.运行.cpp程序了 ...
- Visual Studio 2015开发Android App启动调试始终无法完成应用部署的解决方案
创建一个Android App项目后,直接启动调试发现Visual Studio Emulator for Android已成功运行,但应用始终处于Build中(等待时间超过1小时),并未如预期通过a ...
随机推荐
- Javascript字符串
## 定义 ``` var str = new String("abcdefg"); var str = "abcdefg"; ``` ## 常用方法 ### ...
- Loom工具类:Unity3D巧妙处理多线程
Loom代码不多,只有168行, 然而却具备了子线程运行Action, 子线程与主线程交互的能力! public static Thread RunAsync(Action a) public sta ...
- git的理解
1.对git的分支怎么理解. git的本地,git的跟踪,如果我们的本地的命令行进入某个分支的话,我们的本地对应的文件夹就显示某个,然后我们的ide打开的就是那个分支 2.git的在线视频教学 htt ...
- SDRAM读写一字(下)
SDRAM读写一字 SDRAM控制模块 上电后进行初始化状态,初始化完成后进入空闲状态,在此进行判断如下判断: 如果自刷新时间到,则进行自刷新操作,操作完成后重新进入空闲状态: 如果读使能有效则进行读 ...
- linux安装phpmyadmin
1 配置好MySQL 后启动mysql (service mysqld start); 2 下载phpmyadmin 包,解压只phpmyadmin (解压命令:zip -r abc.zip abc ...
- RabbitMQ官方中文入门教程(PHP版) 第二部分:工作队列(Work queues)
工作队列 在第一篇教程中,我们已经写了一个从已知队列中发送和获取消息的程序.在这篇教程中,我们将创建一个工作队列(Work Queue),它会发送一些耗时的任务给多个工作者(Works ). 工作队列 ...
- C#分割字符串
命名空间:System.String.Split 程序集:mscorlib( mscorlib.dll) 简单实例: string before = "12,50,30"; str ...
- Beta项目冲刺 --第五天
忙里偷得半日闲-- 队伍:F4 成员:031302301 毕容甲 031302302 蔡逸轩 031302430 肖阳 031302418 黄彦宁 会议内容: 1.站立式会议照片: 2.项目燃尽图 3 ...
- 1025WHERE执行顺序以及MySQL查询优化器
转自http://blog.csdn.net/zhanyan_x/article/details/25294539 -- WHERE执行顺序-- 过滤比较多的放在前面,然后更加容易匹配,从左到右进行执 ...
- 用ElasticSearch和Protovis实现数据可视化
搜索引擎最重要的目的,嗯,不出意料就是搜索.你传给它一个请求,然后它依照相关性返回你一串匹配的结果.我们可以根据自己的内容创造各种请求结构,试验各种不同的分析器,搜索引擎都会努力尝试提供最好的结果. ...