Visual Studio调试方式详解

目录

1、概述

2、Debug下的调试

3、Release下的调试

4、附加到进程调试

5、总结


 使用IDE调试代码是开发人员必须掌握的技能,是软件遇到问题时一个最直接的排查方式,今天我们就来讲一下使用Visual Studio调试代码的几种方式。

c1f3a6b4dae7443eb59f400638d659d0.png

1、概述

       软件运行出现问题时,调试是最直接的排查方法。调试过程中可以设置断点,可以单步调试,可以查看代码中相关变量的值,进而快速有效地定位问题。此外,在不熟悉代码的情况下,还可以通过断点调试,搞清楚代码的运行轨迹和流程。

       使用Visual Studio可以对C++代码进行Debug下调试Release下调试附加到进程调试,这三类调试可以用在不同的场景下,下面会一一讲述。以下内容均是以调试C++代码来展开的。

2、Debug下的调试

       在进行日常C++代码开发时,均是在Debug下进行的,从代码的开发、调试到功能联调基本都是在Debug下完成的。

       在Release程序测试过程中发现的问题,如果是必现的或者很好复现的(按照指定的操作步骤很好复现),可以尝试到Debug下复现,如果Debug下可以复现,则可以是直接在Debug下调试代码去逐步地排查。

3、Release下的调试

       Debug版本的程序和Release版本的程序是有很多差异的,其中最典型的一点就是内存管理上的差异。在Debug下申请的内存,系统会额外多分配一些内存,用于存放一些调试信息。

882738758f7540ecabad276be8f8a914.png

       此外,Debug程序申请的内存会被初始化,而Release程序申请的内存则不会被初始化,是分配到内存时内存中的随机值。这些因素可能会导致Debug程序和Relese程序在运行上的差异,比如Debug下运行没问题,而Release下运行是有问题的,例如变量在定义时没有初始化,在Debug下变量的内存会被自动初始化,而Release下变量的值将是个随机值,随机值会导致程序不可预见的错误。

       对于只有Release下运行才有的问题,可以尝试使用Visual Studio进行Release下的调试。在进行Release调试之前,需要到工程属性中将优化关掉,如下所示:

7730296d3efa46649222215d0ca65ac2.png

因为为了提升程序的运行速度,Release下是默认开启优化的。

       编译器在优化时,可能会将部分C++代码优化掉,比如函数调用会被替换掉、局部变量会优化掉,如果不关闭优化,调试代码会出现部分代码被跳过的问题,部分变量无法查看值得问题。当然有极少数问题,可能是优化引起的,关闭优化后可能问题就没有了,这种问题虽然很少见,但我们在实际项目中确实有遇到过。

       除了关闭优化,我们还要搭建Release编译环境,将底层各个模块Release版本的库拷贝过来,然后要在本地编译本地维护的release相关模块的库,本地维护的需要调试源代码的模块也要将其release下的优化关闭掉。

4、附加到进程调试

       如果我们不想在最上层的exe模块进行整体的Release调试,可以只调试某一个模块,只需要在Release下编译一个模块。或者是问题出在底层的模块中,比如组件组的模块、协议组的模块、网络组的模块、音视频编解码组的模块或者是开源组件的模块等,这些组的开发维护人员只需要使用附加调试的方式去调试他们负责的模块。

       只需要在机器上安装exe安装包(release版本),他们本地用VS编译一下Release版本的库,将库拷贝到exe程序所在的路径中,然后将exe程序启动起来,这样exe程序使用的就是刚才编译出来的库了。接着在打开库源码的VS中,点击菜单栏中的调试->附加到进程,在弹出的窗口中找到exe进程:

b057d5941ca0494e80c5198d15b00de6.png

点击附加按钮即可,就可以打断点调试了。

       有人可能会问将dll库拷贝到exe路径中,为啥附加调试时可以调试该dll库的源码呢?其实之所以能调试,是因为编译时生成的调试信息都保存到pdb文件中了,编译生成dll时会将本地编译生成的pdb文件的完整路径写到对应的二进制文件中,如下所示:(二进制文件以文本的方式打开会出现乱码,不妨碍查看对应的pdb文件的路径)

e7fe0f99745e4821b08ee1365c24131c.png

对于我们的测试程序TestDlg.exe,我们使用Notepad++打开,以.pdb关键字搜索,就能搜到写入到TestDlg.exe二进制文件中的pdb完整路径: 

C:\Users\Administrator\Desktop\TestDlg\Release\TestDlg.pdb,如上所示。

所以,将编译好的dll文件拷贝到exe路径中,VS根据dll库中写入的pdb文件的路径可以找到pdb文件,可以找到调试信息,所以可以在VS中调试源码。

       对于附加调试,需要exe程序先启动起来,然后将VS附加到exe进程上调试。但如果问题出现在dll库初始化阶段,程序启动时就会去初始化底层的dll库,如果等程序启动完成后再去附加,时间上可能就晚了,dll库初始化的代码已经跑过了。

       这种场景下我们有办法,可以在exe程序main函数的入口处调用API函数MessageBox弹出一个模态框,将代码阻塞住,给附加进程创造一个时机。等进程附加上去后,再点击MessageBox的确定按钮,让exe继续往下跑,调用dll库的初始化接口,这样就能调试到dll库的初始化代码了。这是一个技巧。

5、总结

       Debug下的调试、Release下的调试和VS的附加调试,这三类调试都需要掌握的,它们使用在不同的场景下。在最开始做功能开发与调试时,一般主要使用Debug调试。在软件功能开发完成进入测试后,测试人员跑的都是Release版本的程序,但我们一般不轻易使用Release下的调试。

       如果程序运行时发生了异常崩溃,则我们的程序中安装的异常捕获模块是可以捕获并生成包含异常上下文信息的dump文件,我们只需要取来dump文件使用windbg分析就好了。如果异常捕获模块没捕获到异常,我们则需要将windbg附加到目标进程上调试运行,如果程序中发生异常崩溃,windbg会第一时间感知到,这样就可以去分析了。

       在某些少数情况下,即使拿到dump文件或者Windbg动态调试也分析不出来问题时,可以让出问题的模块的开发维护人员去手动附加调试模块的代码,看看能不能找到排查问题的线索。如果程序运行过程中业务出现逻辑上的异常,比如执行了不该走进去的代码或者该执行的代码没有被执行到,亦或是代码中数据交互出现问题,也可以通过完善的日志系统去分析定位。日志可以打印到控制台窗口(比如telnet)上,也可以将日志生成到文件中。

       如果是将日志写到文件中,需要考虑一些日志控制机制,日志文件占用的磁盘空间不能太大,每个日志文件也不能太大,否则打开时会非常慢,所以当向文件中写入日志时,如果文件大小达到设定的上限,则需要切文件(比如从log.txt切换到log1.txt)。考虑到文件不能占用太大的磁盘空间,需要有个循环覆盖的机制。

© 版权声明
THE END
喜欢就支持一下吧
点赞2W+赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容