一个好的调试器,能够帮助程序员处理很多自动化的工作。试想下列的情形:
- 错误是发生在一个循环当中,只在循环遍历了若干次以后,才会出现。
- 错误只在程序中某个变量为一个特定的值,才会出现,而这个变量的值是在程序运行的过程中随机设置的。
- 多个线程都要调用同一个函数,而你只想在某几个线程执行这个函数的时候,中断程序的执行。
在上面列出来几种情况当中,如果调试器不能提供一个有效的方法帮助我们设置断点的话,调试这种程序将会是很痛苦的一件事。在第一种情况当中,用户不得不在循环中设置断点,并且要记住自己按下F5的次数,1,2,3…,499,300,301…。第二种情况下,用户还得靠一些运气成分才能发现错误原因。
CLR Debugger的开发人员正是考虑到以上情形,给CLR Debugger添加了这些功能,条件断点(Conditional Breakpoint)和断点过滤器(Breakpoint Filters)。
1.1.1. 根据断点的触发次数中断程序的执行
条件断点允许你设置程序在断点处中断的条件,你可以设置断点在触发若干次以后,调试器才中断程序的执行,也可以设置调试器根据一条返回布尔值的语句来中断程序的执行。我将以下面的程序为例,讲解如何设置条件断点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
using System; public class ConditionalBreakpoint { public static void Main() { Random random = new Random(); int k = 0; for (int i = 0; i < 100; ++i) { int j = random.Next(); if (i % 4 == 0) k = i; Console.WriteLine("{0} + {1} = {2}", i, j, i + j); } } } |
表 1-3 条件断点的演示代码
在程序的第14行设置断点,在CLR Debugger底部的“Breakpoints”窗口中右键点击刚刚设置的断点,在右键菜单里面选择“Hit Count…”菜单,出现下图所示的“Breakpoint Hit Count”对话框
图 1-7“Breakpoint Hit Count”对话框
在图1-7中,CLR Debugger允许用户设置4种根据断点触发次数中断程序的条件,第一种是默认的“总是中断”情形;第二种设置“只在断点触发了若干次以后才中断程序”的情形;第三种设置“只在断点的触发次数是若干次的倍数时才中断程序”的情形;第四种设置“当断点出发了若干次以后才中断程序”的情形。
1.1.2. 设置布尔条件中断程序的执行
在表1-3中的程序里,我们看到程序在循环里面随机设置变量j的值,为了只在j等于某个特定值中断程序的执行,我们需要这样做:
在程序的第14行设置断点,在CLR Debugger底部的“Breakpoints”窗口中右键点击刚刚设置的断点,在右键菜单里面选择“Condition…”菜单,出现下图所示的“Breakpoint Condition”对话框:
图 1-8“Breakpoint Condition”对话框
CLR Debugger里面自带了一个表达式解释器,因此当你选择了“Is true”单选框后,可以在“Breakpoint Condition”的“Condition”文本框里面设置一些比较复杂的布尔条件表达式—表达式的语法与C#的语法相同。
而如果你选择的是“Has changed”单选框,这时你不仅可以跟踪某个变量的值是否已经更改了,而且还可以跟踪某个表达式的值是否已经更改了。有兴趣的读者可以将“Condition”文本框的值设置成“k”和“k > 28”,然后分别使用这两个条件断点启动程序,在“Watch”窗口中观察变量“i”的值,来感受CLR Debugger强大的条件断点设置功能。
1.1.3. 只允许断点在某个线程中才能被触发
通常来说,当你在某个函数里设置了断点以后,无论是哪一个线程执行到断点处,都会触发断点,根据断点的条件中断程序的执行。但是 CLR Debugger允许你只在某个线程中设置断点,当你在调试器里面同时调试两个以上的程序时,CLR Debugger甚至还允许设置断点在哪一个程序中起作用。
在CLR Debugger底部的“Breakpoints”窗口中右键点击一个要过滤的断点,在右键菜单里面选择“Filter…”菜单,出现下图所示的“Breakpoint Filter”对话框:
图 1-9 “Breakpoint Filter”对话框
正如在“Breakpoint Filter”对话框中描述的那样,在“Filter”文本框里面,你只可以设置在哪一台机器上、哪一个程序和哪一个线程中设置断点。根据调试机器名来设置断点是为了支持远程调试,远程调试将在本章后面讲解。
上图5个属性,ThreadId需要特别说明一下,ThreadId并不是托管程序中,.NET 框架中System.Threading.Thread.ManagedThreadId,两者不能等同。简单来说,ManagedThreadId是线程在CLR中的标识符,而ThreadId却是线程在操作系统中的标识符。因此ThreadId需要从调试器中的“Threads”窗口中获取。
断点的使用
命中次数(Hit Counts)
右击断点,可以设置Hit Counts(命中次数),会弹出如下的对话框
当条件满足的时候断点会被命中(即即将被执行),这个命中次数是断点被命中的次数。默认是始终break,选项有如下的几种:始终break;当命中次数达到多少次时break;当命中次数是多少的倍数时break;当命中次数大于等于多少的时候break。
于是在上篇中的条件也可以这样实现,设置命中次数等于50的时候break,按F5后,断点被触发,此时i=50。
断点过滤器
我们可以限制断点在特定的处理器和进程中。可以设置机器名、进程id、进程名、线程id、线程名中的某些条件来过滤一些断点。
注意:ThreadId需要特别说明一下,ThreadId并不是托管程序中,.NET 框架中System.Threading.Thread.ManagedThreadId,两者不能等同。简单来说,ManagedThreadId是线程在CLR中的标识符,而ThreadId却是线程在操作系统中的标识符。因此ThreadId需要从调试器中的“Threads”窗口中获取。
断点条件
我们可以设置断点达到的条件,如下图,我们设置表达式为i==5(注意是判相等,而不是赋值的等于),按F5,断点再次被触发,此时i=50。
还有一个选项是已经被改变,则里面条件是具体的变量,如我们的代码如下
private void ConditionDebug()
{
int hitCount = 0;
for (int i = 0; i < 100; i++)
{
if (i==49)
{
hitCount = 1;
}
}
Console.Write("Hit Count={0}", hitCount);
}
我们在代码里如果i==49,就将hitCount的值改变,同时设置断点的条件为
则当断点再次被触发的时候此时i=50。这个通常被用在找变量的时在什么时候发生改变。
断点的位置
可以设置断点的位置,如下图,设置程序到达那个文件的第几行第几个字符时触发断点。
断点触发时…
我们可以设置断点到达时做一些其他的事情,如打印消息,运行一个宏。
自定义调用堆栈
堆栈跟踪时vs一步步执行你的程序是对当前的方法调用继承关系的直观显示。在调试程序时,我们会经过一个又一个方法,包括方法的嵌套调用。堆栈跟踪会对这当中的每一层方法作出记录。选择“调试-->窗口-->调用堆栈”,或者是快捷键Ctrl+Alt+C就可以看到当前的堆栈跟踪状态。这里会将每个方法单独显示为一行,并且带有行号和参数值。每一个新的方法调用被称为堆栈帧。
堆栈跟踪是广为人知的调试工具,它的优点在于你可以双击任意一行跳转到程序中该层调用方法的代码。于是你可以看到程序是如何执行到这一位置的,同时可以看到方法接受的参数值。并且可以使用Ctrl+C将一个或者全部堆栈帧复制到剪贴板,并将这个方法的调用信息发送给工作伙伴。
项目属性中的Debug选项卡
如果你的项目是Console项目(控制台应用程序)或者是WinForm项目,则右击项目解决方案,选择属性,会出来如下的项目属性窗体。
我们可以设置“启动动作”、“启动选项”和“是否启用调试”。
Start Action有三个选择项:
Start Project:默认选项,设置为启动项目
Start external program:调试的时候启动内部程序
Start browser with URL:调试的时候打开URL地址
使用Trace.axd调试ASP.NET
在以前asp时候,我们为了查看某个变量的值,通常会使用Response.Write方法。可能现在许多ASP.NET程序员也习惯在后台使用Response.Write方法将变量的值写出来,其实微软提供了很好的调试工具,即Trace.axd。它的功能主要是:配置 ASP.NET 代码跟踪服务以控制如何收集、存储和显示跟踪结果。
关键的几个选项:
1、localOnly 默认为false。这个很好理解。如果为true,只在本地输出跟踪信息。
2、enabled 是否启用跟踪。
3、pageOutput 指定在每一页的结尾是否呈现跟踪输出。如果是 false ,则只能通过跟踪实用工具访问跟踪输出。
4、requestLimit 指定在服务器上存储的跟踪请求的数目。最大为10000,默认为10
5、traceMode 指定显示跟踪信息的顺序。SortByCategory或 SortByTime(默认)
关于更多可以参考
http://msdn.microsoft.com/zh-cn/library/6915t83k%28VS.80%29.aspx
下面以一个小Demo来说明怎么使用Trace.axd来调试ASP.NET
1. 建立一个Web项目,取名为WebTraceTest
2. 编辑web.config文件,添加trace节点(在)
内容如下:
<trace enabled="true" localOnly="true"
pageOutput="true"
requestLimit="15"
mostRecent="true" />
3. 新建一个页面,取名为Test.aspx,在里面增加一个文本框和一个按钮(都是服务器端的控件)
按下F5,开始调试,会发现出现如下界面
5. 在文本框中输入文字,如Alexis,点击按钮,会发现Form Collection中会有详细的信息,如下:
说明:使用Trace.axd我们可以获得以下信息:
Request Details:请求的详细信息
Trace Information:跟踪信息
Control Tree:控件树
Session State:会话状态
Application State:应用程序状态
Request Cookies Collection:请求Cookie集合
Response Cookies Collection:响应Cookie集合
Headers Collection:标头集合
Response Headers Collection:响应标头集合
Form Collection:窗体集合
Querystring Collection:QueryString集合(即Url中?后面的字符串的信息)
Server Variables:服务器变量
将Visual Studio与一个运行中的进程连接
当你按下F5对程序开始调试时,VS.NET会对项目进行生成(如果有必要的话)并以调试模式启动程序。也就是说,只要项目位于debug版本的程序集中,VS.NET就与运行得程序之间建立了连接,以便对断点等与调试相关的方法作出反应。
不过有些时候,我们需要或者想要对正在运行得Visual Studio之外启动的进程进行调试。当进程位于debug版本的程序集中,这是可以做到的。
1. 选择“工具—>调试进程”列出所有正在运行得程序,如下图
2. 选择自己感兴趣的进程,点击连接,此时Visual Studio自动切换到了调试模式。
3. 打开Progress窗口,发现我们刚刚选择的进程在列表中,如下图
这一技巧可以让你对Windows服务进程进行调试。编写Windows服务进程时,你无法按F5启动调试,因为它们必须先通过管理工具安装后启动才能运行。如果你在调试模式下生成并安装服务程序,就可以使用这一技巧进行调试。
而且你可以对SQL存储过程使用同样的方式进行调试。如果你安装了SQL Server调试组件,并且有足够的权限,就可以连接到SQL Server的进程,并在服务器中为存储过程设置断点来一步步执行。
调试Visual Studio中的多个项目
在实际开发中,我们往往分了许多层,有许多的项目集合在一个解决方案下。我们可以右击要调试的项目选择“调试-->运行新实例”来实现调试这个项目。我也可以右击解决方案,选择多项目调试,如下图
我们还可以设置项目的期待顺序。在客户端/服务器(CS结构)程序中,我们可以使用这一方法来确保服务器端程序在客户端程序之前运行。
只在特定类型的异常时中断
一个健壮的程序会在运行时处理所有可能出现的异常。不过开发者在调试复杂的程序时会觉得这样有些麻烦。因为所有的异常都被处理掉了。在出现任何异常时,Visual Studio不会再进行处理,或者中断代码来对用户作出提示。
幸运的是Visual Studio有个选项可以让开发者指定他们关心的异常类型。选择菜单栏à调试à异常,或者使用快捷键Ctrl+Alt+E。如下图
我们可以看到一个树状结构列出所有VS可以监视到的异常。
后面的两个勾选框的意思分别为是否被抛出和用户是否不处理。
参考:
《Visual Studio.NET使用技巧手册》
http://msdn.microsoft.com/