Visual Studio 中的高级调试
原文地址:http://www.codeproject.com/Articles/309781/Advanced-Debugging-in-Visual-Studio
下载高级调试.zip - 28.2 KB (Visual Studio 2010 解决方案)
简介
我们中的许多开发者在使用 Visual Studio 进行调试的时候,只关注基本的 F9,F10,F11,F5 和窗体。因此,如果我们直接利用 Visual Studio 中丰富且有效的调试功,理想情况下本应该在几分钟就完成的调试一个问题或模拟一个条件,却最终导致花费了数个小时。
高级调试的技巧分散在整个网络上,但是我想一个整理过的清单将会对开发者接受和开始使用这些技巧很有用。
环境
这篇文章中的技巧可以在 Vistual Studio 2008/2010 中使用。其中的一些对于下一个版本 Visual Studio 版本可能仍然是有效的。
技巧目录
为了使文章更容易浏览,我把它分成6个不同的技巧,分别带有示例代码和截图来帮助介绍。
1、"创建对象 Id" 的魔法
2、附加到进程 - 使用宏
3、即时窗口
- 直接调用函数
- 设置和显示变量
4、调试一个 Winodw 服务
5、玩转断点
- 跟踪断点
- 条件
- 命中数
- 筛选
- 改变断点的位置
6、局部变量/自动窗口/调用堆栈
额外的技巧!
当断点被命中的时候使其能够发出声音
1、“创建对象 Id” 的魔法
有时我们想跟踪一个对象,即便已经超出了作用域。我们可能需要这种跟踪某个对象直到它被垃圾回收器回收的能力来调试一个问题。Visual Studio 调试器中的创建对象 Id功能提供了这种能力。按照下面的步骤自己动手试试。
1. 向下面这样,在你想要跟踪的变量所在的那行代码设置一个断点。
2. 在调试模式下运行程序,让它在断点处停留。
3. 在 str 上右击选择添加监视。
4. 在监视 1窗口中,从上下文菜单中右击对象变量 str 并选择“创建对象 Id”。
5. 你会看到在值列中添加了 1#。这是调试器为当前调试会话标识变量的唯一的 ID.
6. 像下面这样,即便 str 超出了它的作用域,我们用这个 ID 依然可以跟踪对象的值。只是简单的把对象 id 1# 放到监视窗口来监视它的值。
7. 即使我们继续迭代 str 的循环,改变它的值,但是 1# 仍然保持不变。这让我们知道了即使前一个 str 对象已经超出它的作用域,通过使用我们给它指定的对象 ID 依然可以继续跟踪它的值。
8. 最后如果你移除了方法,那么所有的 str 实例将超出作用域,你也就不能继续使用监视窗口跟踪 str 了。它会变成灰色。然而,对象 Id 1# 仍然是有效的,当你经过其它方法时你可以继续跟踪它的值。
注意:正如名字所暗示的那样,它只对引用类型有效,而对值类型无效。理所当然,因为值类型存储在栈上,并且当结束作用域时立即从栈中弹出。所以理应不依赖垃圾回收器回收。
附:第6步中的1#是自己手动编辑添加进去的,当然你也可以直接将值列(value)拖动到下一行来记录值的改变,如果只是观察值的改变,我觉这种拖拽的方法更方便。
2、 附加到进程 - 使用宏
使用 Visual Studio 过程中,我们做了很多不必要的重复工作,可以使用宏来自动执行。将调试附加到进程就是一个这样的例子。一个常见的要求是要有能力调试一个正在运行中的进程(如:一个.net 控制台应用程序的进程)。通常的做法是使用附加到进程窗口,来自Visual Studio中的调试->附加到进程。当我们不得不一遍又一遍的测试迭代的改变时,这种做法是麻烦(cumbersome)且让人愤怒的(irritating)。到了让宏来拯救我们的时候了。
1. 如下,创建一个带有一个 Main 方法和一个 TestAttachToProcessMacro 方法的简单的控制台应用程序。从 Main 方法中调用这个方法。
2. 生成控制台应用程序。这会在 Debug 文件夹下生成一个 exe 文件。双击并运行这个程序。
3. 上面代码中出现的第一个断点不会被命中(因为我们没进行调试),而且你还会在控制台窗口看到下面的输出。
4. 我们想要通过附加到进程从第二个断点进行调试。现在我们开始记录我们的宏通过简单的5步 -
i. 如下,从菜单工具->宏,点击记录 TemporaryMacro
ii. 记录已经开始。现在完成附加到进程这重要的一步,如下:
点击 调试 -> 附加到进程
在下面弹出的窗口中找到你的进程并点击附加。
iii. 停止记录宏,使用 工具 -> 宏,如下:
iv. 保存宏,使用 工具 -> 宏,如下:
v. 保存之后,宏会出现在宏资源管理器中。这里我把它命名为 AttachToMyProgram
。
5. 最后,为了方便,我们可以放置一个快捷方式到工具栏上。
i. 找到 工具栏 -> 自定义 -> 命令,并设置工具栏的下拉选项为 调试,如下:
ii. 点击添加命令按钮,在弹出的窗口中选择类别一栏中的Marco(宏),和命令栏中的 AttachToMyProgram :
iii. 现在从修改选项中重命名命令的名字,如下:
iv. 现在 AttachToMyProgram
的快捷方式已经出现在调试栏上,如下:
6. 现在关闭控制台应用程序并重新运行。我们会再一次看到 “I am started” 消息。现在仅仅是点击调试条上的 AttachToMyProcess
快捷键和在控制台应用程序窗口按下任意键。这正是你想要的结果。你处于调试会话中且第二个断点被命中。现在你可以通过单击一个按钮很容易的附加你的进程。
3. 即时窗口
很多时候我们写了一个方法之后想直接调试它,反反复复,直到得到我们想要的结果。很多人为了每次都运行到我们需要调试的方法,会努力地运行整个应用程序。嗯,其实这是不必要的。用即时窗口就很方便。你可以用快捷键 Ctrl + Alt + I 来打开它。
接下来是怎么使用它:
直接调用方法
让我们试着用即时窗口直接调用下面的方法:
我们可以像下面这样直接从即时窗口调用这个方法:
在即时窗口中点击回车之后,TestImmediateWindow1() 方法中的断点就会被命中,而不需要调试整个应用程序。
同在进程上一样,你也可以在即时窗口中得到输出结果,如下:
你可以通过改变它的值和测试颠倒后输出的结果玩转变量 _test:
设置和显示变量
我们可能会想从即时窗口中传递一个变量给方法。让我们用下面的方法来举一个例子:
我们可以在即时窗口中使用像下面这样的命令,声明,设置和传递一个变量方法。
下面是另一个示例,调用方法并传递一个复杂的 Employee 类对象。
用即时窗口命令测试这个方法:
还可以用即时窗口来做更多的事情,但是我把它留给你自己去探索。
4. 调试一个 Window 服务
如果你对这个技巧不熟悉的话,那么调试 Windows 服务将会变成一项令人畏惧的(daunting)任务。你可能会生成并配置这个服务,然后开始调试。你会使用 Visual Studio 中的附加进程方法来调试。尽管这样,如果你想调试 OnStart 方法内部到底发生了什么,不得不用 Thread.Sleep() 或者做其它的工作以便于附加进程时 OnStart 方法会处于等待状态。通过使用这个简单的技巧我们可以避免这些苦恼。
步骤 1:将 Windows 服务的输出类型设为控制台应用程序:
步骤 2:去掉 Program.cs 文件,粘帖下面的代码替换到继承自 ServiceBase
的 Service 文件。就这样简单,现在你可以调试 Windows 服务了,它会像控制台应用程序一样运行。你也可以像往常一样配置它,它会像 Window 服务一样运行。
partial class MyService : ServiceBase { public static void Main(string[] args) { /*EDIT: 18th January 2012 * As per suggestion from Blaise in his commments I have added the Debugger.Launch condition so that you * can attach a debugger to the published service when it is about to start. * Note: Remember to either remove this code before you release to production or * do not release to production only in the 'Release' configuration. * Ref: http://weblogs.asp.net/paulballard/archive/2005/07/12/419175.aspx */ #if DEBUG System.Diagnostics.Debugger.Launch(); #endif /*EDIT: 18 January 2012 Below is Psuedo code for an alternative way suggested by RudolfHenning in his comment. However, I find Debugger.Launch() a better option. #if DEBUG //The following code is simply to ease attaching the debugger to the service to debug the startup routine DateTime startTime = DateTime.Now; // Waiting until debugger is attached while ((!Debugger.IsAttached) && ((TimeSpan)DateTime.Now.Subtract(startTime)).TotalSeconds < 20) { RequestAdditionalTime(1000); // Prevents the service from timeout Thread.Sleep(1000); // Gives you time to attach the debugger } // increase as needed to prevent timeouts RequestAdditionalTime(5000); // for Debugging the OnStart method <- set breakpoint here, #endif */ var service = new MyService(); /* The flag Environment.UserInteractive is the key here. If its true means the app is running * in debug mode. So manually call the functions OnStart() and OnStop() else use the ServiceBase * class to handle it.*/ if (Environment.UserInteractive) { service.OnStart(args); Console.WriteLine("Press any key to stop the service.."); Console.Read(); service.OnStop(); } else { ServiceBase.Run(service); } } public MyService() { InitializeComponent(); } protected override void OnStart(string[] args) { } protected override void OnStop() { } }
5. 玩转断点
有时候我们希望在一行代码每次被执行的时候监视一个或者是多个变量。通过设置一个普通的断点进行监视会使其变得非常耗时。所以我们通常使用 Console.WriteLine 输出值。如果它是一个临时变量使用跟踪点会更好。它和 Console.WriteLine 实现相同的目的。优点是不必添加 Console.WriteLine 而使得代码被破坏和完成后忘记移除的风险。更好的是,通过这种方式,你可以通过在一个跟踪点上叠加不同的条件来使用断点的其它特性。
让我们来看一个跟踪断点的实例:
像下面这样,在调用 ReverseString
方法的地方设置一个断点。
然后右击并选择“当命中时...”,接着检查输出消息。复制 "Value of reverseMe = {reverseMe
}" 到测试框中。保持“继续执行”勾选状态,点击确认。
断点会转化成跟踪点(菱形),如下:
现在不管什么时候断点被命中,它都不会在这行代码处停止,而是一直执行,并且你可以看到每次断点被命中时变量的值。输出窗口中如下:
条件
如果我们希望断点在一个特殊值的条件下被命中时,条件断点可以用来避免在代码中写入额外的 if/else 条件。
在上面的代码中右击跟踪点,点击断点中的条件选项。然在条件框中输入 "i
==45"并点击确认。(注意:不要使用单个的 "=",而是 "=="。)
现在只有当 i=45 时断点才会被激活;所以跟踪点应该只输出 “Live 45”。
命中数
命中数被用来找出一个断点被命中了多少次。你也可以选择什么时候停止在断点处。改变条件断点为 i > 45 。然后右击 -> 断点 -> 命中数 。选择 中断,条件是命中次数的几倍于,在值域框中输入5。点击确认。
现在断点每迭代5次就会被命中。注意下面的输出是条件和命中数结合的结果。
下面说明了断点从 i=46 到 i=99 总共被命中 54 次,但是每5次才会中断执行一次。
筛选器
对多线程应用程序非常有用。如果多线程调用同一个方法,你可以使用筛选器指定哪个线程应该被命中断点。
右击 -> 断点 -> 筛选器
改变断点的位置
如果你想移动断点到不同的行,你可以使用这个选项。
6.局部变量/自动窗口/调用堆栈
接下来的三个窗口当调试的时候用起来很便利。你可以在调试之后访问它们。到 Visual Studio 菜单栏中 调试 -> 窗口。
自动窗口:自动窗口用于显示当前语句和上一条语句中使用的变量。帮助你只专注于当前行范围内使用的变量。
局部变量:局部窗口显示当前上下文中的局部变量。你可以在这里监视一个方法中的局部变量的值。注意,类级别的变量不会在局部变量窗口中显示。
调用堆栈:调用堆栈会显示引导当前调用的方法的整个方法树。能帮助我们追溯到罪魁祸首。
额外的技巧!
1. 到 控制面板 -> 硬件和声音(Window XP).在 Window 7中 控制面板 -> 声音。
2. 找到声音选项卡中的程序事件,选择 “Breakpoint Hit”。(看下面的图片)
3. 选择你要的声音点击确认。
4. 现在,当一个断点被击中时,你会听到选中的声音。