继续分享 5 个实用的 vs 调试技巧
继续分享 5 个实用的 vs 调试技巧
前言
我在上一篇文章《5 个非常实用的 vs 调试技巧》 中分享了 5
个我认为非常值得了解的 vs
调试技巧,本周继续分享 5
个很基础但同样实用的调试技巧。
1. 条件断点
作用简介:
顾名思义,带条件的断点。满足条件才中断。条件断点非常非常有用,使用得当,可以极大缩短我们调试问题的时间。比如,有一个大循环,只在第 1024
次循环的时候有问题,我们如果单步(在 vs
中可以按 F10
),恐怕手得按残了。又比如,我们想在特定条件下中断。这时候条件断点就是我们的救星。
2. 内存断点
作用简介:
顾名思义,针对内存设置的断点。对于调试逻辑复(hun)杂(luan)的程序,非常非常有用。比如,有一个全局变量的值,在代码中有 N
个地方会改动它,在调试程序的时候,不知道这个全局变量在哪里被改变了,如果能在改动的那一刻中断下来该有多好啊!这可是内存断点的专长!
![memoryaccess-breakpoint memoryaccess-breakpoint](./images/memoryaccess-breakpoint.gif)
打开方式:
调试的时候,通过 调试 -> 窗口 -> 断点
即可打开断点窗口。在 vs2013
中对应的快捷键是 ctrl + alt + b
。打开后可以 通过 新建 -> 新建数据访问断点(D)...
创建一个数据访问断点。
注意:
-
只有在程序中断到调试器的时候才允许新建数据访问断点。
-
输入的是内存地址,可以直接输入地址值,也可以通过
&
获取地址。 -
vs
中好像只支持指定的内存范围的值发生变化时才中断。windbg
中的ba
命令更强大,感兴趣的小伙伴儿可以查看windbg
的帮助文档。
3. 异常开关
作用简介:
异常最多分发两轮,每轮都会优先分发给调试器。如果调试器没处理,会继续分发给异常处理函数。具体的分发过程可以参考《软件调试》。
比如,在下面的示例代码中。我在 ExceptionDemo()
中加上了 try {} catch {}
来捕获一些异常。在 FunctionE()
中的某一行设置好断点,如果一切正常是可以断下来的。但是在 FunctionD()
中有可能抛出异常,如果根据设置,vs
不处理这个异常,该异常会被 ExceptionDemo()
处理,还没运行到设置断点的地方就被异常改变了执行流程。
#include "stdafx.h"
#include <exception>
bool application_quit = false;
int g_runningLoop = 0;
void FunctionA();
void FunctionB();
void FunctionC();
void FunctionD();
void FunctionE();
void ExceptionDemo()
{
try
{
while (!application_quit)
{
FunctionA();
}
}
catch (std::exception)
{
}
}
void FunctionA() { FunctionB(); }
void FunctionB() { FunctionC(); }
void FunctionC() { FunctionD(); }
void FunctionD()
{
if (++g_runningLoop > 6)
{
throw std::exception("too many loops!");
}
FunctionE();
}
void FunctionE()
{
if (g_runningLoop > 10)
{
application_quit = true;
}
}
![exception exception](./images/exception.gif)
p.s.
虽然在代码中增加 try {} catch {}
有助于提高程序的健壮性,但有时候可能不利于我们发现问题,有些问题可能就被“默默”吞掉了。
打开方式:
调试的时候,通过 调试 -> 异常(X)...
即可打开异常设置对话框。在 vs2013
中对应的快捷键是 Ctrl + Alt + E
。
**注意:**只有在调试的时候才能设置,不调试的时候是看不到异常设置菜单的。
4. 调试时修改值
作用简介:
假设我们正在调试如下代码,跟踪到了 if (bRich)
这一行,期待的 bRich
的值是 true
,而实际值是 false
。我们可以手动修改 bRich
的值为 true
来强行进入 if
分支,而不是 else
分支。(BTW,改完就真的有钱了么?)
#include "stdafx.h"
#include <iostream>
bool HaveIMakeEnoughMoney()
{
return false;
}
void ManualModifyValueDemo()
{
auto bRich = HaveIMakeEnoughMoney();
if (bRich)
{
std::cout << "Finally, I'm rich!" << std::endl;
}
else
{
std::cout << "Oops, I'm still poor!" << std::endl;
}
}
![manual-modify-variable manual-modify-variable](./images/manual-modify-variable.gif)
小贴士:不仅可以通过悬浮窗口改变变量的值,我们还可以通过监视窗口
,内存窗口
等其它方式改变变量的值。
5. 拖动到指定位置执行
作用简介:
相信,大家都有过手滑的情况,本来想的是单步步入(在 vs
中按 F11
)特定函数,没想到却按成了 F10
,华丽丽的错过了想调试的函数,这时候我们可以拖回来。又或者如上面的代码,当执行到第24
行的时候,发现 totalMoney
的值不是我们想要的,我们想重新回到前面跟踪一下totalMoney
的值是怎么来的,而我们又不想重新走一遍整个流程(因为可能很慢)。这时候我们可以手动拖动黄色小箭头到第 22
行。请看下图:
![drag-to-specific-line drag-to-specific-line](./images/drag-to-specific-line.gif)
注意:
拖动功能是通过设置 eip(rip)
的值来实现的,拖动需谨慎,有些情况下可能导致程序崩溃!
测试工程下载地址
百度云盘 链接: https://pan.baidu.com/s/1MSjUNPF-JHoY1t3l1xXFeg 提取码: jew2
CSDN:https://download.csdn.net/download/xiaoyanilw/12640122
总结
本次介绍的 5
个调试技巧虽然都很基础,但是却非常实用,而且使用频率比较高。不知道你是否有所收获呢?
参考资料
《软件调试》