找回密码
 立即注册

QQ登录

只需一步,快速开始

graper

高级会员

45

主题

63

帖子

1348

积分

高级会员

积分
1348

活字格认证

graper
高级会员   /  发表于:2010-4-27 16:40  /   查看:7174  /  回复:0
用 IntelliTrace 调试应用程序

[原文作者]:Justin Marks
[原文链接]:Debugging Application with IntelliTrace
下载示例代码
     人们是如何修正代码中的Bug的?设置一个断点、在调试器里运行程序、进行一些单步执行--并祈祷这问题是在你的控制范围内, 然后你才能干些事情
    自从ENIAC(世界第一台积分电子计算机)被发明以来我们一直就是这么Debug的了。这种乏味而耗时的Debug办法到目前为止我们用得还行,不过,现在是让Debug变得简单些的时候了。随着 Visual Studio 2010 Ultimate 的发布,新的 IntelliTrace 功能通过让程序员们能更好的洞察他们的应用程序的运行,把调试事业带入了21世纪。
    跟 Windows SysInternals 的 Process Monitor 之类的其它监视和跟踪工具很像,Visual Studio 2010 会搜集一个应用程序在运行时的数据,以帮助程序员们去诊断错误。这些搜集到的数据被称作 IntelliTrace 事件。这些事件 被搜集为默认调试经验的一部分,加上别的部分,它们让程序员们可以不需要重启debugger就可以穿越到运行时去看看应用程序里面发生了什么事情。
    在这篇文章中,我将向你介绍 IntelliTrace,并为你演示作为日常开发活动一部分,它是如何为程序员们提供好处的。我将展示 IntelliTrace 是如何提供一个那些在一个应用程序运行时发生事件们的时间线,以及程序员们可以如何使用这些事件去做有针对性的调试。然后,我将谈到那些“为了搜集关于应用程序的一系列更深入的信息,以得到一个完整的运行时历史”而可以被程序员改动的设置。最后,我将演示如何使用一个以前由别人--比如一个测试人员--建立的 IntelliTrace 记录文件去调试一个应用程序,而不用去运行该应用程序以重现错误。
    当 Visual Studio 评估团队开始为 Visual Studio 2010 做计划的时候,我们花费了大量的时间与客户讨论他们是如何诊断他们的应用程序中的问题的。尽管每个人都有自己的一套方式以及偏爱的工具,但是有一点却是压倒性的清楚:传统的诊断应用程序中问题的方法既困难、耗时又代价高昂。程序员们收到的Bug报告几乎从来也没有任何重现问题的步骤,大多数就是由象“我正在使用这个程序,但是它突然Crash了”这样的句子组成。在极少数有重现步骤的情况下,你也可能会面对 Bug 发生在特殊环境下的问题,而这又会导致一些需要解决的新问题。此外,bug常常是因为对一个 framework 或者别的代码是如何运作的误解而产生的。
    明确了这些要点之后,我们有了一个新的调试器功能的构想:它需要能够在问题发生的时候就搜集到正确的信息。我们的目标是交给程序员准确的重现步骤、系统环境设置、以及公布他们使用的 framework 或者代码的行为,以彻底的提高程序的可诊断性。随着 Visual Studio 2010 Ultimate 的发布,IntelliTrace 通过让程序员们能够更好的洞察应用程序和 framework 的行为、以及打开一个由测试人员搜集的 IntelliTrace文件去解决“没有重现步骤”的情况的能力,为我们带来了调试体验的极大提高。
IntelliTrace 介绍
    当一个程序员需要对代码运行的一个更深入的理解时,IntelliTrace提供了一种可以“调高(仪表的)刻度”的办法来搜集一个应用程序的完整运行时历史。
为了阐明它,我将使用 Tailspin Toys 示例应用程序来为你演示 IntelliTrace 可以搜集哪些类型的信息。我将在 Visual Studio 中打开这个 Solution 并开始调试。当这个 Web 站点启动的时候,我将浏览到 “About us” 页,然后就会收到一个服务器错误。那么你会怎么诊断这个问题呢?如果你象我这样,那么本能的第一反应可能是去设置web.config文件以禁用显示自定义错误,然后重启调试器。可如果这个问题是断断续续无规则的出现的呢?如果你能够在问题出现的时间点上断入进程,在错误发生后,能够从 Visual Studio 获得一个发生在应用程序中的事件的历史,这样是不是很不错?
当你在调试的时候,IntelliTrace 会在后台搜集一个托管应用程序的数据,包括来自许多像 ADO.NET、ASP.NET、以及System.XML之类的 framework 部件的信息。这些 IntelliTrace 事件让程序员可以查看在运行时曾经发生过什么事情,以及更重要的:“穿越时空”去查看应用程序之前的状态而不需要重启调试器。当我打开调试器的时候,马上看到了一个被搜集到的 IntelliTrace 事件的有序列表,如图1:

图1 IntelliTrace 搜集的诊断信息
    从图1中你可以看到,这个IntelliTrace事件的列表所包含的内容远远超过你在 Process Monitor 中能看到的文件和注册表访问。我们已经为 Visual Studio 2010 定义了将近150种 IntelliTrace 事件,而且已经有计划日后增加更多的事件。图2强调了一些由IntelliTrace搜集的事件的类别:
图2 在 Microsoft .NET Framework 上可用的 IntelliTrace 事件
类别
说明和被搜集的数据
ADO.NET
与执行SQL查询有关的事件,被执行的命令以及连接字符串
ASP.NET
与ASP.NET管道(pipeline)有关的事件,以及请求的处理和重定向
Console
控制台输出
Data Binding
WinForm数据绑定
环境变量
从进程中取回和运算出环境变量
文件
文件的建立、删除和访问
手势(Gestures)
用户在Web表单、Windows Form和WPF上对常用控件执行的操作。除了搜集关于与控件交互的数据外,在这些事件之一上单击还会为你重定向到相应的事件处理程序上
Lazy Initialization
加载速度缓慢的变量的初始化
注册表
注册表信息的建立、删除和查询
Service Model
来自WCF的Web service调用
线程
用户工作项的队列和并发计算任务
跟踪
调试器跟踪输出和断言(assertions)
用户提示
Windows Form和WPF消息框的显示以及对话框的结果
Workflow
活动实例化和完成
XML
XML文件载入

    IntelliTrace窗口让我可以根据类别——图2中的类别——或者线程来对搜集到的事件列表进行过滤。此外,我还能够通过文本搜索来找到关键的事件,以便快速跳转。由于IntelliTrace也会搜集Exceptions,我可以搜索词语“exception”,然后列表就会被过滤并显示那些导致ASP.NET错误页的exception,以及exception是在哪里被掷出、在哪里被捕捉到的。在这个例子里,错误是在第10行、53列处分析一个实体时由一个XMLException引发的(见图3)。当我在掷出了的 exception 事件上单击的时候,象调用堆栈和监视窗口之类的别的调试器窗口会显示出与该事件本身相关的数据,就好像错误被掷出的时候你正在调试一样。此外,就跟在调用堆栈窗口里前进后退一样,代码编辑器会打开相应的源代码文件、并以橘黄色高亮与事件对应的代码行,这就是智能跟踪提供的功能。

图3 当在第10行、53列上分析一个实体的时候,一个XMLException被掷出
    IntelliTrace 已经给了我一段对诊断这个问题非常有用的信息:一个XML文件被载入,并且在XML中有一个意外的或错误的字符。但我还是不清楚哪个文件被访问了。IntelliTrace 再一次搜集了我所需要的信息--也即是文件访问。
    再看一遍图3中的IntelliTrace窗口,可以看到在这个exception之前的是一个XML文件载入事件“Content\Xml\Ads.xml”。这肯定是导致该错误的文件。我可以轻松得在 Visual Studio 中打开这个文件,找到第10行、53列,可以肯定这是该文件中的一个错误,也即是那个"&b=1"是个无效的 NavigateURL XML元素。删除这些无效的字符,这个Web站点应该就能正确的加载了。
    现在,我希望你再回想一下你用传统的调试方式debug过的最后一个未处理的exception。如果它是象这样的一个exception,你应该看到过错误是发生在哪里的,但绝对没看到准确的原因、或者无效的字符。这是IntelliTrace的关键所在——它给了你最佳的信息去更快、更轻松的诊断问题。你可以把时间花在更重要的事情上,而不是浪费在到处寻找那些信息。
使用IntelliTrace去跟踪调试器事件
    我刚刚演示了IntelliTrace是如何搜集在一个应用程序运行时发生的已处理的和未处理的exception的、以及在framework上的IntelliTrace事件是如何能帮助你洞察应用程序在后台所作的一切的。不止这样,IntelliTrace还能够搜集由调试器——也就是断点、跟踪点、以及步进(Stepping)事件——引发的事件。
    最常用的调试技术之一,是在你认为代码中存在问题的位置附近设置一个断点,然后一次次步进(单步执行)代码来观察变量的变动。当对循环进行调试,观察一个变量变成某个特定的值的时候,这种技术特别有用。不幸的是,大多数程序员常常急躁的不停按F10来快速的步进代码,最后往往发现步进的过头了。然后他们就不得不重启调试环境再来一次。而有了IntelliTrace后,所有的断点和步进事件、以及它们的上下文数据都会被记录下来,因此你可以快速跳转到以前停下过的某个点上。
    如果在这些调试器事件之一上单击,监视窗口将显示所有之前我正在观察的数据,其中包括我在本地窗口、监视窗口、自动窗口以及QuickWatch和DataTips窗口中运算过的值。
    通常,以前开发好的、已经部署了的代码并没有内建能够帮助调试可能出现问题的跟踪。断点提供了查看更多在应用程序后台发生的事情的能力。但大多数时候,程序员们并不真正需要在断点处把程序的执行中断下来,可能他们只是想要搜集一些数据、然后继续执行而已。尤其是在循环中的那种情况,你可能只是想要记录下循环变量的值而已,并不想在每次迭代中都停下来。这样的情况中,跟踪点(TracePoint)是一个极好的替代品。跟踪点让程序员可以使得调试器执行一个自定义操作,比如说执行一个宏,或者打印出一个跟踪消息,而不需要中断运行。而有了IntelliTrace后,跟踪点的输出会被搜集起来,并可以象其他IntelliTrace事件那样在同一个界面上查看(见图4)。

图4 跟踪点可以给代码添加动态的跟踪输出
调高刻度
    默认情况下,IntelliTrace被设置为仅搜集IntelliTrace事件。这是一个低开销的解决方案,但不会提供一个你的应用程序的完整历史。当你需要更深入的信息时,IntelliTrace可以被设置为搜集更多的数据。象其他的调试器设置那样,可以从工具菜单下的选项对话框去设置IntelliTrace(见图5)。

图5 可以从选项对话框去设置IntelliTrace
    选中“IntelliTrace事件和调用信息”(IntelliTrace events and call information)会让IntelliTrace不仅搜集IntelliTrace事件,还包括在每个方法的入口、退出以及调用点(call site)上的调用信息,比如在这些位置上的参数和返回值。不幸的是,启用这个模式将导致运行时编辑功能在调试期间被禁用。很明显,选择了搜集更多的信息意味着应用程序需要更多的开销。我们已经花了很大功夫去找出在搜集有用信息和保持性能之间的平衡了,但还是让你自己去完全的控制它。
    每个调试工作期都会建立一个保存在磁盘上的IntelliTrace文件,Visual Studio 关闭的时候这个文件会被清理掉。在选项对话框里可以找到的IntelliTrace高级设置面板里,你可以指定让调试时生成的文件被储存在哪里、以及文件能有多大。如果达到了最大文件的大小,一个循环的缓冲将被用于帮助压缩和截短存储在IntelliTrace log里的信息,从而缩小磁盘上的log文件。高级面板里的另两个设置让你可以隐藏导航条(navigation gutter)和禁用 Team Foundation Server(TFS)查找可用的symbols。
    基于性能的考虑,默认情况下仅启用了搜集一个预定义了的IntelliTrace事件的子集。某些你可能会想要启用的事件包括控制台输出、文件访问、lazy initialization、注册表访问以及线程事件。在选项对话框中的IntelliTrace事件面板里,你能够启用或禁用一整个事件的类别、或者单个独立的事件。
    最后一个IntelliTrace选项面板让你可以控制IntelliTrace从哪个模块搜集数据。除了由 Microsoft 发布的作为 .NET Framwork 和 Visual Studio 的一部分的模块以外,所有的模块数据默认都会被搜集。这样一来就会搜集到大量的数据,所以,你可以考虑把这个列表改成一个包含列表,指定仅包括你关心的模块。反之,如果你用到了某些常用的第三方库,也可以排除它们,因为它们明显超出了你的控制范围。
调查一个用户代码错误
    现在我已经验证过演示程序中的“About us”页面是有效的了,让我们来确认一下我们的购物车也能正常工作。当我向购物车里添加一架要买的纸飞机时,我看到纸飞机的确被正确添加到购物车里了。不过,当我重复这个操作去添加第二架的时候出问题了:要购买的数量还是1,而不是期望中的2。我希望使用调试器来解决这个问题而不需要重启,但在这种情况下IntelliTrace的事件列表可能帮不了我(所有的工作都是在我的代码中完成的,跟 .NET Framework 无关)。这是IntelliTrace的“IntelliTrace事件和调用信息”模式能帮上忙的地方了:它展示了我的应用程序的一个运行历史。让我们断入调试器去看看有什么IntelliTrace的额外功能是我可以用于解决这个问题的。
     先从假设我的“向购物车添加东西”的逻辑中有什么东西出错了开始。由于这是一个模式-查看-控制(MVC)应用程序,所以其中是没有什么单击按钮的事件处理器的,有的只是被发送给MVC、并由MVC处理的一个 POST 消息。这个 POST 消息已经被记录为一个 IntelliTrace 事件了,并且,尽管它本身的用处不大,我至少可以使用这个事件来作为我的调查的起点。通过单击这个IntelliTrace事件,我可以跳转到调试工作期中“添加数据项”逻辑开始的那个点上。通过在 IntelliTrace 窗口中搜索关键词&quotOST",我可以快速的找到这些 POST 消息。由于用户执行了这个操作两次,因此,如我所希望的那样,搜索返回了两条结果。选中第二个事件,把搜索区中的内容清除掉,它就跟所有搜集到的前后有关事件一起显示出来了(见图6)。

图6 IntelliTrace事件视图可以帮助你开始调查
    现在我有了在应用程序时间线中的上下文环境,我想要更深入的理解其内部的方法调用。从事件视图中,我可以切换视图去查看运行时的历史。在窗口顶部的“切换到IntelliTrace调用视图”链接上单击可以转换到调用视图去查看应用程序的完整运行历史(见图7)。在任何时候单击“切换到IntelliTrace事件视图”链接都可以回到事件视图上。

图7 IntelliTrace调用视图展示了应用程序的运行历史
     在运行历史中导航的一个机制是使用调用视图来深入到你有兴趣调查的调用中去。每次你在调用视图的下半部分中双击一个调用的时候,这个调用就会跳入到视图上半部分的底部去,同时在代码编辑器中的指令指针(instruction pointer )就会同步到该调用的方法入口点上,就像在实时调试的时候你进入一个调用堆栈一样。你可以以这种方式在IntelliTrace搜集到的历史数据中前进或后退。在调用视图中导航是一种快速获得对运行时历史的概观、以及在代码中大幅度跳转的机制。
    使用调用视图只是导航方法中的一种而已。我也可以通过步进代码来导航。在源代码窗口中的导航条上有一套新的DVR样式的控件,让你可以在代码中步进,就像在一个传统的调试工作期中一样(见图8)。因为我正在IntelliTrace调试模式中,所以步进是按发生在每个调用点、函数入口和出口处记录的事件来跳转的。当然,如果你更偏爱键盘控制的话,F10/F11也是有效地。

图8 导航条提供了DVR样式的一套控件,让你可以在你的应用程序中步进
    这两种导航方法用来调查是很棒,但有些时候你本来就非常清楚地知道自己已经在哪里设好了断点。在这个应用程序的情况里,我知道那个把数据项添加到购物车中的函数的名称:Kona.Model.ShoppingCart::AddItem。我真正想做的是跳转到这个函数第二次被调用的地方,并观察被传入这个函数的值、以及该函数返回的值。更重要的是,我想要找到那行调用"AdjustQuantity"函数的代码。当然,IntelliTrace也支持这种导航技术。
    在代码编辑器中,我可以在想要搜索的这一行代码上Djibouti右键,然后从右键菜单中选择“在IntelliTrace中搜索这一行”(见图9)。搜索开始后,代码编辑器的顶部将会出现一个结果栏,让我可以在这个栏里面的搜索的结果中导航。在同步到第二条搜索结果后,我的指令指针就正指向我想要的位置上,然后我就可以用别的调试窗口来观察这个调用了(见图10)。

图9 搜索功能让你可以准确跳转到一个指定的函数调用

图10 搜索的结果被显示在代码编辑器窗口顶部的搜索结果栏中
    现在查看本地窗口,你可以看到购物车被改动了导致在购物车中产品的新数量是1。这就是Bug了,我期望的改动后的数量应该是2。查看这行代码,新的数量参数应该不仅包括“item.Quantity”,还要包括被传递给这个函数的 “quantity” 变量。修正的办法是应该把这个函数调用改成:
AdjustQuantity(product, item.Quantity + quantity);
排除令人恐惧的“无法重现”
    测试人员和程序员们都经常会碰到这类麻烦:测试人员报了一个Bug说某些东西出错了,最后Bug却被解成“它在我的机器上不重现”。无论是程序员还是测试员都不希望碰到这种事情,可是他们却没有一套正确的工具能够帮助他们对出现Bug的时候究竟发生了什么来进行很好的沟通。这就是我们为什么设计这个系统了,有了它,IntelliTrace就可以在调试工作期内向程序员提供跟在 Microsoft Test Manager(MTM)中手动测试的执行过程中搜集到的同样一套诊断信息了。
    让我们再回头看看我调试过的第一个场景,"About us"页不能正确的打开。如果一个测试员报了这么一个Bug,Bug的标题可能会是“当查看About页时出现一个应用程序错误”,幸运的话,还会带有一张错误发生时的屏幕快照。如果你今天接到了象这么一个Bug,你会怎么去调试它?很可能你会从源代码控制器中加载应用程序的解决方案,然后试图在你的开发机器上重现这个错误。在我们这个案例里,你是能够调试并解决这个问题的,可想想看这样的情况会发生多少次,或者如果问题是由你和测试员机器上设置的不同所引起的呢?程序员在调试问题时要比设置重现环境时有工作效率多了。
    有了Visual Studio 2010,MTM让测试人员可以方便的编写、管理和执行手动的和自动化的测试。但这还不是该工具全部的功能。MTM为测试员提供了这样的能力:当测试运行中的时候,让诊断数据适配器在后台搜集信息。例如,你可以自动地搜集一段测试运行情况的视频记录,这样开发人员就能看到跟测试人员所看到的一样的东西。别的搜集器还包括搜集测试所运行的机器上的系统信息和事件日志的能力。
    我们已经添加了一个IntelliTrace诊断数据适配器去自动地在后台搜集IntelliTrace文件。当测试人员碰到一个测试步骤失败,然后选择了报一个Bug的时候,搜集到的IntelliTrace文件会自动被上传给TFS并链接到这个Bug。这就让程序员有了一系列丰富的信息可以去调试,而不是一些简单的重现步骤。当程序员打开链接到这个Bug的IntelliTrace文件的时候,他会觉得就好像是在调试一个 mini-dump(windbg或者别的调试工具生成的跟踪日志文件),但不同的是,搜集了应用程序整个时间线上的数据,而不仅仅是Crash发生的那个点上的东西。可以为任何在本地或者远程测试代理上管的应用程序搜集IntelliTrace数据,包括运行在IIS控制下的ASP.NET应用程序。
    当我打开一个 TFS Bug的时候,MTM已经自动地基于由测试员所描述的手动测试步骤添加了重现步骤。更酷的是,如果看一下bug上的"全部链接"(All Links,见图11)页,可以看到还有一个搜集到的IntelliTrace文件。在这个文件上双击,会打开一个该IntelliTrace文件的总结页视图(见图12)。

图11 搜集到的IntelliTrace文件可以通过“全部链接”页来访问

图12 IntelliTrace总结提供了一个搜集到了什么数据的概要
    这个总结页给了我们大量关于IntelliTrace文件中所包含内容的信息。在这个页的顶部,是一个在该应用程序中的线程的时间线视图。如果展开总结页中的“线程列表”(Threads List)段,可以看到格式化成表格的同样数据。在一个线程上双击会从这个IntelliTrace文件上启动一个调试工作期,并将指令指针设置到该线程的起点处。
    在这个IntelliTrace文件中也有一个所有搜集到的Exception的列表。如果选中一个Exception,线程的时间线上将会显示出一条垂直的橙色线条,指示出Exception发生的时间。双击一个Exception将会启动一个IntelliTrace调试工作期,这个Exception时间将会在IntelliTrace事件视图中被选中。总结页上更下面的地方,可以看到一个跟手动重现步骤相匹配的所有测试事件的列表。每一个步骤的结果也被显示出来了。在这些事件其中之一上单击,也会在线程时间线上放置一条垂直线,而双击一个事件会启动一个IntelliTrace调试工作期,并选中了可能是最接近的事件。总结页的底部显示了一个从测试应用被运行的机器上搜集到的系统信息的列表,而下面则是一个所有被加载进进程的模块及其文件路径的列表。
    你也许会问自己,这些在正式发布的版本中会有吗?当然了!此外,当搜集或者查看IntelliTrace数据的时候,是不需要访问 symbols的。Symbols 仅在要将搜集到的数据链接到源代码文件的时候才是必要的,因此,当你打开调试工作期的时候它们还是有帮助的。
    Visual Studio 2010和TFS已经为 Symbols 和源代码服务器提供了支持,因此,如果你的应用程序是作为 TFS 编译系统的一部分被编译的,那么在IntelliTrace需要的时候, Symbols 和 源代码的正确版本将会自动被从TFS上下载下来。你甚至不需要知道 Symbol 服务器的路径。
    一旦你从总结页上启动了一个调试工作期,调试器将工作得像在一个活动调试工作期中那样(见图13)。大多数调试窗口都是可用的,并且你可以导航到任何IntelliTrace搜集到的数据上。

图13 当从总结页开始调试的时候,Visual Studio 运转得就像一个活动调试工作期
结束
    在这篇文章中,你已经见到了IntelliTrace可以如何极大地同时增强你的日常开发活动和更轻松的诊断问题而不需要重启应用程序、并以传统的中断-步进-观察技术调试的能力。我也向你演示了一个组织可以如何通过在测试时搜集IntelliTrace数据去减少“无法重现”的bug的数量,并让程序员们可以离线调试问题而不需要访问什么活动的重现。这里只是对该功能的一个简要的介绍,随着你越来越熟悉IntelliTrace的能力,它将开始改变你的调试方式。

0 个回复

您需要登录后才可以回帖 登录 | 立即注册
返回顶部