Visual Studio高效调试手段与技巧总结,值得收藏!

Posted IT老张

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Visual Studio高效调试手段与技巧总结,值得收藏!相关的知识,希望对你有一定的参考价值。

目录

1、对0xCCCCCCCC、0xCDCDCDCD和0xFEEEFEEE等常见异常值的辨识度

2、遇到Debug下的报错弹框,点击重试,查看函数调用堆栈

3、调试时,程序和调试器都发生了闪退,可以尝试到Output窗口中找线索

4、被废弃的API函数IsBadReadPtr和IsBadWritePtr

5、条件断点与临时过滤条件

6、数据断点

7、附加调试

8、查看汇编代码


       你还在为没有有效的调试手段,不能快速定位问题而发愁吗?用了多年的Visual Studio,是不是还没掌握其有效的调试方法和技巧呢?本文将根据多年使用Visual Studio的经验,带着你逐一认识和掌握Visual Studio多种实用的调试方法和技巧,从而帮助你们去高效地解决开发过程中遇到的各种难题。

1、对0xCCCCCCCC、0xCDCDCDCD和0xFEEEFEEE等常见异常值的辨识度

       常见的异常值如下所示:

这些值是微软调试器故意在某些场景下设置的特殊值,便于程序员快速地辨识存在的问题。其中,0xCCCCCCC是微软调试器在Debug下填充到未初始化的栈内存中的,当字符串看就是 “烫烫烫烫……”;0xCDCDCDCD是微软调试器在Debug下填充到未初始化的堆内存中的,当字符串看就是 “屯屯屯屯……”;0xFEEEFEEE是微软编译器填充到已经释放的堆内存中的。

       所以遇到值为0xCCCCCCC或者0xCDCDCDCD的变量时,可能是这些变量没有初始化就被访问或使用了,这是非法的。遇到值为0xFEEEFEEE变量,有可能是该变量占用的内存已经被系统释放了,不能再使用了。

2、遇到Debug下的报错弹框,点击重试,查看函数调用堆栈

       我们在调试代码的过程中,会时常遇到这样的报错弹框:

       其实很简单,只要点击重试按钮,调试器就会中断下来,然后去查看此时的函数调用堆栈,通过函数调用堆栈就能大概看出是执行了什么代码触发了异常。

      但有时光看函数调用堆栈,可能并不能看出问题所在,我们可以尝试去看Output输出窗口中输出的信息,编译器和程序可能会有些调试打印信息输出到Output窗口中的。比如我们在使用duilib界面框架编写窗口xml文件时,写了非法的xml节点:

 导致duilib库中解析xml时产生了异常,duilib会将产生异常的xml节点附近的上下文打印到Output窗口中,通过这个上下文我们就可以直接到xml中找到问题节点了

3、调试时,程序和调试器都发生了闪退,可以尝试到Output窗口中找线索

       比如程序发生“stack overflow”线程栈溢出,有可能函数递归调用触发的,也有可能是函数之间发生死循环调用触发的,程序会直接闪退,调试器也会直接退出调试状态,此时无法查看异常时的信息,也查看不到异常时的函数调用堆栈,更查看不到变量的值。

        不过此时可以尝试到Output窗口中看看,看看能否找到一些线索,对于线程栈溢出时的闪退,会在Output窗口中输出“Stack overflow”异常提示信息:

这样我们就知道发生线程栈溢出了。

       那我们如何才能找出发生线程栈溢出时的函数调用堆栈呢?下面就轮到强大的Windows调试器windbg上场了,可以重新运行程序,然后将windbg附加到程序的进程上,复现线程栈溢出的问题,一旦问题复现,windbg会立即检测到该stack overflow的异常,中断下来,在windbg输入kn/kv/kp命令,就可以查看此时的函数调用堆栈了知道函数调用堆栈,就知道是什么操作引发的异常了。

4、被废弃的API函数IsBadReadPtr和IsBadWritePtr

       有人经常会问,有没有系统API能判断当前内存地址是否可读可写,Windows之前是提供过IsBadReadPtr和IsBadWritePtr这样的API接口,但接口的返回值并不可信,这两个现在已经废弃了,不能用了。

       有的第三方老的库可能还调用了这两个接口,在Debug调试时可能会触发一个异常,但是该异常不是致命的,可以直接跳过去的,程序可以继续详细运行。这种异常在使用VS或Windbg调试时都会引发中断。VS中只需要取消勾选“遇到此异常不再中断”即可跳过:

windbg动态调试时只需要执行g命令即可跳过去。

       所以在使用VS或Windbg调试时,没有发现明显的问题,可能时调用IsBadReadPtr和IsBadWritePtr引发的,直接跳过去就好了。这样的问题,我们会时不时地遇到。

5、条件断点与临时过滤条件

       VS中可以给断点设置过滤条件,满足条件后断点就会中断下来。添加条件断点的方法是,先添加一般断点,然后右键点击该断点,给断点设置条件,如下所示:

 一般我们主要使用“条件”、“命中次数”和“筛选器”三种类型。可以设置命中断点的表达式条件,也可以设置断点的命中次数(包含大于、等于和小于),也可以给断点设置筛选器。

       设置筛选器的界面如下:

 在设置筛选条件时,可以设置线程id,这在多线程调试时比较有用。当然要事先打断点,将目标线程的id记录下来,然后再来设置过滤线程的条件。

        但是条件断点中设置的条件表达式比较苛刻,只能设置简单的表达式,很多复杂点的表达式都提示不能设置这样的条件。我觉得还是人为添加临时的if过滤条件代码最灵活(断点设置在if语句的body部分),我们平时经常使用这种方法,比如duilib库中的CEditUI控件的DoEvent接口,这个控件很多窗口都会使用,但我们只想调试某个窗口中的CEditUI控件对象,没法直接在要调试的接口中直接打断点,不然会中断很多次,所以我们在CEditUI::DoEvent接口中人为添加if过滤条件的代码,通过控件名称将目标对象过滤出来(编写窗口布局xml时为给每个控件设置不同的控件name)

然后将断点设置在if语句的body部分。这种添加临时if过滤条件代码的方式很灵活,可以设置各种复杂的过滤条件。

6、数据断点

       我们有时会遇到变量值被无故被篡改,导致程序逻辑出异常或出现崩溃。在最初排查问题时,搜索了操作变量的所有代码,都没有问题,并添加了打印,运行程序后打印出来的日志显示也没问题,但是变量还是被无缘无故地篡改了!

       这种情况一般都是内存越界导致的,有全局内存越界、栈内存越界和堆内存越界,直接排查代码是很难找到问题的。遇到这种情况,应该第一时间想到数据断点,第一时间请数据断点上场。

       设置数据断点其实就是监控目标变量的内存,一旦内存被修改,就会命中该数据断点,调试器就会中断下来。设置数据断点是有技巧的,被监控的目标变量需要在被分配内存后才能被监控,一般在类的构造函数中先设置一般断点,命中断点后,目标监控变量就分配了内存,就可以通过表达式&a去设置数据断点了

一旦被监控的内存被修改,就会命中对应的数据断点,此时去查看函数调用堆栈就可以看到是什么代码篡改了目标变量的内存了。

7、附加调试

       很多时候直接调试源代码是最直接最有效的,所以除了使用VS直接调试代码之外,还支持将VS附加到目标进程上调试。这特别适用底层库(比如网络库、协议库、媒体编解码库等)的调试。

       一般情况下,当程序发生崩溃时,我们会取来程序异常捕获模块捕获到的保存异常上下文信息的dump文件,先用windbg对异常进行分析,在排查不出问题时,如果问题好复现,我们会建议发生崩溃的模块的维护组尝试去使用VS手动附加调试一下,看看到底是什么问题。

       附加调试之前,先将库编译出来,将库拷贝到目标程序所在目录中,然后重新启动程序。再将打开库代码的VS附加到目标进程上调试。如果是要调试release版本(有的问题在debug下复现不了,旨在release下才有问题,所以需要进行release版本的调试),则需要在工程属性中将优化关闭掉。
        VS附加调试的入口:点击菜单栏中的“调试”,然后再点击“附件到进程”菜单项,打开附加到进程的窗口:

然后点击要附加到的目标进程,点击确定就可以了。
       有人可能会问,直接将包含调试信息的库拷贝到目标进程的路径中,为啥还能调试呢?其实本质上,函数变量符号及调试信息都是放置在pdb文件中拷贝过去的库文件中会默认写入其使用的pdb文件的绝对路径,不管你将库文件拷贝到本机的哪个位置,都能根据绝对路径找到pdb文件,都能加载到库的调试信息。
       这个附加调试要求先将目标进程启动,但是有时我们可能需要调试启动时初始化的代码,但等程序启动起来后,初始化的代码都执行完了,都没有机会调试了。这里有个技巧,可以在被调试进程的main函数中添加messagebox,先将目标进程阻塞住,等待VS附加到进程中然后点击messagebox中的OK按钮继续运行,就能调试初始化的代码了。

8、查看汇编代码

       一般情况下,在分析软件异常时,软件是崩溃在某一条汇编指令上,有时很难通过C++代码看出为啥会导致崩溃,这时就需要去查看二进制的汇编代码的上下文,看看到底为啥会导致那句汇编代码崩溃了。

       我们平时一般会使用反汇编工具IDA Pro去打开二进制文件,去查看二进制文件对应的汇编代码。其实我们也可以在VS中查看C++代码对应的汇编代码,现在要查看汇编代码的C++代码处添加断点,当命中断点时,点击右键,在弹出的右键菜单中点击“转到反汇编”菜单项:

即可看到C++代码对应的汇编代码了,这对学习C++语句的汇编代码也是很有好处的。

       通过调试时查看汇编代码,可以将C++代码与汇编代码对照看,从而搞清楚很多场景下的汇编代码是怎么编写的,这对我们走读汇编代码的上下文很有帮助。当我们遇到读不懂的汇编代码时,我们会在VS中写一些C++测试代码,然后这些C++代码的汇编代码是什么样子的,通过类比就能搞清楚了。

以上是关于Visual Studio高效调试手段与技巧总结,值得收藏!的主要内容,如果未能解决你的问题,请参考以下文章

Visual Studio调试之断点技巧篇补遗

Visual Studio调试之断点技巧篇

Visual Studio 2015中的常用调试技巧分享

Visual Studio高级调试技巧

Visual Studio高级调试技巧

Visual Studio调试效率技巧