调试技巧
1 Visual Studio的调试技巧
VS调试器是一功能非常强大的调试工具。使用调试器,可以中断程序的执行以检测代码逻辑的正确性,计算和编辑程序过程中的变量。在使用VS调试代码的过程中,多使用VS提供的快捷键,减少鼠标移动时间,提高调试的效率。
1.1 常用快捷键:
F5 | 执行到下一个断点 |
Shift + F5 | 停止调试 |
Ctrl + F7 | 编译单个文件 |
Ctrl + Break | 取消(编译\调试) |
F9 | 添加删除断点 |
Ctrl + F9 | 禁用断点 |
Ctrl + Shift + F9 | 清除项目中的所有断点 |
F10 | 单步调试 |
Ctrl + F10 | 运行到光标 |
Ctrl + Shift + F10 | 设置光标处为下一语句 |
F11 | 进入函数调用 |
Shift + F11 | 退出函数调用 |
Shift + Alt + F | 查找选中符号的引用代码 |
Shift + Alt + O | 查找文件 |
Shift + Alt + S | 查找符号 |
Alt + G | 跳到符号的声明或者实现处 |
Alt + O | 打开当前文件相关联的文件 |
Alt + M | 列举当前文件的函数 |
Ctrl+Tab | 切换打开文件 |
Alt + 7 | 打开调用堆栈窗口 |
Alt + F9 | 打开断点窗口 |
Ctrl+Alt+W,1 | 打开Watch 1窗口 |
1.2 断点
l 简单断点
简单断点的设置很简单,找到对应的源码,F9.
l 地址断点
对于一些没有源码的调试,就可以通过地址断点让程序中断。通过“反汇编”窗口或者通过调用堆栈设置地址断点。
l 数据断点:
使用数据断点可以在存储于指定内存位置上的值发生更改时促发中断。只可以在中断模式下设置数据断点。设置数据断点步骤:
1)在相应的文件中设置断点中断程序,在断点窗口上new—>new data Breakpoint
2)在弹出的数据断点窗口中输入地址,自己根据数据的类型设置数据的大小
3)数据断点的应用例子
某个全局变量在某一步操作之后不知为何就被修改了,由于代码很复杂,无法通过单步调式找出对应的代码,此时就可以使用数据断点来中断程序。结合后面的“跟踪点”可以跟踪数据被修改过程。
l 给断点附加条件:
有时需要给断点添加一些条件,使得断点在条件满足的情况下才促发中断。可以给断点添加的条件有3种:断点条件,断点命中次数,断点筛选。具体操作:选中断点右键
1) 断点条件:
给断点添加一个表达式,当表达式成立,或者表达式的值发生变化时才促发。例如:在一个大循环i(1-10000)中,只有当i是1000的倍数且大于等于5000时,才中断程序。
2) 断点命中次数:
允许开发者指明程序中断的条件:条件被第N次满足时,或者条件被满足的次数是N的倍数时,或者条件被满足的次数大于等于N次时。 例如:某个函数前后被两个函数调用过,只希望第二次调用的时候才促发中断,此时就可以使用断点命中次数来实现了。
3) 断点筛选:
通过筛选器可以指明断点只在某台特定的机器,或某个特定的进程或线程中才能被触发。在多进程,多线程调试中会比较很有用。
1.3 跟踪点
在有些情况下是不太适宜促发中断来调试的。这个情况下跟踪点调试就变得很有意义了,它在断点触发的时候可以不产生中断,紧紧是在output窗口上输出一些信息或者促发宏的运行,让程序继续执行。实际上跟踪点是在断点的基础上扩展的一个功能,其可以设置两种跟踪行为:输出信息,执行宏。跟踪点的创建:
:
1.3.1 输出信息:
很多原本要使用Trace 或者OutputDebugString代码来输出的功能,都可以使用跟踪断点来输出。 输出的信息格式如下:
功能 | 格式 |
输出变量值 | {变量名} |
输出调用的函数 | $CALLER |
输出调用堆栈 | $CALLSTACK |
输出函数名 | $FUNCTION |
输出线程ID | $PID |
输出进程名称 | $PNAME |
输出文件当前的位置 | $FILEPOS |
输出CPU当前时间: | $TICK |
例如:
l 监控变量m_data的变化过程
l 利用$TICK就可以测试一个函数运行的时间
1.3.2 运行一个宏:
通过宏可以自定义出更强大的调试功能。跟踪点能促发的宏都记录在宏管理器里。通过”Tools”à”Macros”à”Macros explorer ” 打开宏管理器。通过举一个自动输出所有的局部变量的宏来说明宏的使用。
l 新建宏工程
打开宏管理器,右键快捷菜单:
l 编辑宏
将下列VB代码复制到Module1代码中间,保存
Sub DumpLocals()
Dim outputWindow As EnvDTE.OutputWindow
outputWindow = DTE.Windows.Item(EnvDTE.Constants.vsWindowKindOutput).Object
Dim currentStackFrame As EnvDTE.StackFrame
currentStackFrame = DTE.Debugger.CurrentStackFrame
outputWindow.ActivePane.OutputString("*Dumping Local Variables*" + vbCrLf)
For Each exp As EnvDTE.Expression In currentStackFrame.Locals
outputWindow.ActivePane.OutputString(exp.Name + " = " + exp.Value.ToString() + vbCrLf)
Next
End Sub
l 设置跟踪宏
l 运行程序,结果如下:
1.4 变量窗口
调试器提供了很多变量窗口,这些窗口用于显示、计算和编辑变量与表达式。最常用的是watch窗口(Ctrl+Alt+W,1 2 3 4)。
1.4.1 使用格式说明符更改值的显示格式
格式说明符 | 意义 |
d,i | 有符号十进制整数 |
u | 无符号十进制整数 |
o | 八进制整数 |
X, x | 十六制整数 |
f | 有符号浮点型 |
e | 科学计数法 |
c | 单个字符 |
s | 字符串 |
su | Unicode 字符串 |
hr | RESULT 或 Win32 错误代码 |
wc | 窗口类标志 |
wm | Windows 消息 |
eax | 函数调用的返回值 |
l 使用,wm 查看消息类型
若没有,wm,value为1,看不出是什么消息
l 使用wc查看窗口样式
若没有,wc, 只能看出dw的值为72335360, 看不到想要的信息。
l 使用hr查看错误信息
不仔细看帮助文档的话,很难看出来LoadImage为什么会失败,res\\toolbar.bmp文件明明存在。遇到这种API调用失败的,首先可以在Watch窗口中输入伪变量$err查看错误码(1814), 接着使用hr来查看错误描述。从这个错误信息上就可以看出在可执行文件上找不到对应的资源,而不是找不到对应的文件。最后通过仔细阅读帮助文档就可以是加载方式不对,应该加上LR_LOADFROMFILE标识。
l 查看数组内容
一个数组指针在watch窗口中只能看到第一个元素的数据,要想看到后面的,可以使用 “数组指针 ,数组长度”的方式查看后面元素的数据。
1.4.2 计算:
l 调用函数,只能调用模块内部的非内联函数。
l 表达式计算, 例如:sizeof(pGroup)。
l 伪变量:可以再watch窗口name输入伪变量。相关的系统值就会被计算出来。
$err:获取错误码, 相当于GetLastError())
$handles:显示应用程序中分配的句柄数)
$TID:显示当前线程的线程 ID
$vframe:显示当前堆栈帧的地址
1.4.3 编辑
直接编辑内存值可以不必为调试变量的每一个取值而不停地终止测试/修改代码/重新调试,尤其当需要反复测试一个变量的正常取值时。若对于一些需要不断的被修改的变量。例如:在某些绘制函数里
通过内存编辑修改eState的值来测试效果。然而Ondraw是不断的被调用的,需要不断反复的修改eState。 针对这种情况可以在代码中添加一个局部静态变量,将静态变量的值赋给eState,以后只需要再内存中编辑静态变量的值就解决上述的反复问题了。
两个直接编辑内存变量的方式:
l 直接在变量的值上双击,再输入要改的值
l 鼠标停留在要改的变量上,在弹出的变量值上单击,输入要改的值
1.5 VS异常处理:
在Debug调试模式下,当调试程序发生异常时,VS调试根据“异常配置”的设置做出相应的反应。
通过DebugàExceptions…(Ctrl+Alt+E)打开。
若勾选相应的异常项,当异常促发时,程序立即被中断,无论这种异常是否已经在代码中被处理。若没有勾选,则代码没有促发这种异常,则中断,若已经处理了,则中断。