VS调试器未命中断点问题总结

    本文主要总结我在实际工作遇到的一种程序调试问题——VS调试器未命中断点。我主要谈的是用VS调试C++程序。

一、程序分支

    调试程序的时候,最常用的就是设置断点(F9, bp - break point)并执行(F5,go or run)。程序员在预先需要程序执行流停下来的代码行设置断点(F9),然后按F5开始执行程序流,程序执行到断点的代码行会停下来,这个时候,程序员可以通过查看变量、内存、调用堆栈等方式来观察程序执行的情况。

    一般来说,上述调试方式中,调试器未命中断点,极大的可能是你设置断点不在程序执行流中,或者说程序执行流走的是其他分支,没有经过你设置断点的分支。这种情况下,你就需要去分析程序的分支逻辑和设置更多的断点来一个个排查。


二、输出目录和工作空间不一致

    除了调试exe工程,有时候,我们也需要调试DLL工程,这个时候,我们会将相应的exe文件拷贝到DLL工程的debug目录下,并设置该目录为VS调试器的工作空间,然后设置启动项为exe。通过上述一系列设置,我们就可以开始调试DLL工程代码了。

    有时候,我们会发现一个奇怪的现象:在按下F5启动程序之前,我在DLL工程代码中设置了几个断点,并显示了“红色实心圆点”,但是,一当我们按下F5启动程序,之前设置的“红色实心圆点”瞬间变成了“空心圆点”,然后,你会发现你设置的断点未命中。

    事实上,这时我们犯了一个很低级的错误。但是,我们往往会在一个低级错误面前浪费大量时间,故此,我将这种情况记录下来,以免再犯类似低级错误。

    这个错误是这样形成的:我们在拷贝exe的时候,将之前的生成的DLL文件也一起拷贝到了debug目录下。我们想当然的认为,只要自己用VS“重新生成”一下,就会覆盖老的DLL(拷贝过来的)文件。不幸的是,有时候,你的DLL工程的“输出目录”并不是那个debug目录,这样,你重新生成的DLL文件并未覆盖之前的文件,然后就出现了上述奇怪的现象了。

    这类低级错误的解决办法就是:检查“工程属性->常规->输出目录”和检查“工程属性->调试->工作目录”,看看是否一致。


三、线程意外死亡

    在多线程的程序中,同时存在多个程序执行流,有时候,你的断点未命中,是因为你设置断点的线程在运行时意外死亡了。这种情况,仅仅通过分析代码逻辑分支,是很难发现问题的。关于这中情况,我的另一篇博客,讲述了一个完整的案例:

    http://blog.csdn.net/sagittarius_warrior/article/details/52526287


四、异常设置

    由异常(exception)引起的断点未命中问题,也很隐蔽。我们知道,当程序抛出异常时,程序执行流会打破既定的流程,直接跳转到最近一层的catch语句。这种行为给程序员跟踪程序执行流带来了极大的挑战,所以我们在设计“try-catch”时,需要特别小心,最好进行异常分类和添加log,便于跟踪。

    一般情况下,如果上层没有设计“try-catch”,下层抛出了异常,特别是调用第三方库抛出的异常,往往会引起程序奔溃。如下图



    这是我调用第三方库“Crypto++”时,抛出的一个未处理异常导致的程序奔溃。该对话框清晰的显示了这个异常的自带信息“CryptoPP::InvalidCiphertext”。除此之外,VS调试器的一个强大之处在于,它有时候能跨工程,在发生异常时,将程序停在抛出异常的代码行。因此,我可以轻松的找到异常是在“Crypto++”的“void StreamTransformationFilter::LastPut(const byte *inString, size_t length)”函数中的这个代码块中:




    通过查看“调用堆栈”,也可以轻松获知程序的调用流程。

    但是,这一切事实上并没有这么容易得到。我在一开始遇到这个问题的时候,VS调试器并没有抛出这个异常对话框,程序也没有停在抛出异常的代码行,而是直接跳出了我调用导入函数的函数(我在该函数里设置了断点,断点未命中),然后继续执行。这个现象显示的是如此诡异,让我当时有点懵。

    为了排除模块干扰,我单独新建了一个win32 console工程,复现这个场景。令人沮丧的是,在这个实验工程工程中,VS如上表现,抛出了异常对话框。但是,在我的大工程中,还是那么诡异。

    偶然地,我发现,VS IDE里面有一个“异常设置窗口”,它隐藏在菜单项“调试->窗口->异常设置”里。打开这个窗口如下:



    我们需要关注其实是“C++ Exceptions”。但是,VS调试器会根据你的工程的类型(比如win32 console,windows)的不同,默认设置需要捕获哪些C++异常。如果你的工程调用了第三方库,两者的工程类型不一致,可能导致VS调试器不会捕获一些特殊的异常。这里的工程类型实际上,可以通过查看“工程属性->链接器->系统->子系统”获知。

    这就很好的解释了,为什么在我的工程里,VS调试器未捕获“Crypto++”抛出的那个异常。查看该异常类可知,InvalidCiphertext是从std::exception派生的类。回到上图的“C++ Exceptions”列表,std::exception默认是不开启的,另外值得注意的是该列表的第一项——“不在此列表的所有C++ Exceptions”。

    事实上,当我勾选“std::exception”后,再次执行程序,VS调试器还是没有捕获异常,这个说明该选项对std::exception派生的异常类不适用。当我勾选第一项时,再次执行程序,VS调试器终于反应正常了,捕获到了该异常。

    这类由异常引起的不按程序流执行,未命中断点,又因为异常设置的问题未捕获异常的诡异问题,是本文需要强调的重点和难点。


扩展:

    常见的C++调试器,除了VS外,还有windbg和gdb。关于这两个调试器,可以参看我另两篇博文:

    http://blog.csdn.net/sagittarius_warrior/article/details/51646833

    http://blog.csdn.net/sagittarius_warrior/article/details/52512843

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值