调试术语
原文链接地址:http://blog.csdn.net/Donjuan/article/details/3454597
1.1. 调试模式
调试器可以采用三种模式来调试被调试程序(在下文中,如果没有特别说明的话,简称程序):
- 一种是直接调试模式,即直接从调试器里面启动程序,就如同我们在Visual Studio里面按下F5就可以调试程序那样。
- 另外一种是附加(attach)模式,即你可以在程序已经启动的情况下,把你的调试器附加到程序上,进行调试。这种模式通常在调试服务(Service)程序非常有用,例如你的ASP.NET网站可能会存在这样一种bug,在网站运行的过程当中,有一个异常不知道从那个角落里面抛了出来,这时你可以使用附加模式来调试你的网站。
- 最后一种叫做验尸(post-mortem)调试,这种调试模式允许你调试在客户机器上出错的程序。也就是说,当你的程序发布给客户以后,客户在使用程序的过程中,可能会碰到一些很难重现的错误(bug)。这时操作系统可以将出错的程序的内存保存到一个文件里面,然后你可以在自己的开发机上调试这个文件,找到程序错误的原因。
1.2. 内存文件
1.3. 符号文件
CPU一般都提供一个特殊的指令来实现对断点的支持,因此你在调试器里面设置断点的时候,调试器会在程序的指令流里面插入这个特殊指令。而CPU在执行程序的指令时,如果碰到这个指令,就会自动中断程序的执行,并且将程序的控制权交给调试器,这样你才能通过调试器查看程序里边一些变量的值,以及对程序做一些其他操作。
但问题是,在你设置断点的时候,你只是告诉了调试器要中断执行的代码行,即你只是在源代码文件的某一行点了一下。而通常一行C或者C#代码在编译之后,会被解释成多条机器指令,调试器是怎样将代码行号信息和保存在被调试器里面的机器指令对应起来的呢?
图 1-1 C#源代码与汇编代码的对应关系示例
另外程序中断以后,你在调试器的监视窗口输入了一个变量,调试器显示变量的值,这是最正常的调试场景了。可是调试器是如何知道你输入的变量的类型,又是如何在程序的一堆内存当中,定位到你要查看的变量的值的呢?
其实调试器所做的一系列的魔术,都是和符号文件分不开的。微软的Visual Studio的符号文件以.pdb为后缀名,当你在Visual Studio当中编译好解决方案后,可以在与编译出来的程序的相同文件夹里找到其对应的符号文件,默认情况下,文件名和程序名相同。
符号文件其实是编译器生成的,因此你也可以使用C#的编译器csc为你的程序生成符号文件。Csc程序的/debug开关可以用来控制符号文件的生成:
编译选项
说明
1.4. 符号文件服务器
由于符号文件包含了二进制程序和源代码文件之间的对应关系,因此每次程序升级以后, 你可能会同时有多个版本的程序在不同的客户机上运行。而在调试的时候,手工寻找正确版本的符号文件肯定是一个非常费力的事情。因此就有了符号文件服务器软件的出生,符号文件服务器的工作就是,当你在调试程序的时候,调试器会自动和符号文件服务器交互,获取正确版本的符号文件。
在后面的章节里面,会介绍如何创建一个符号文件服务器,以及如何使用符号文件服务器。
1.5. 工作目录
每一个程序都有工作目录,当你的程序中使用相对路径打开文件的时候,这个相对路径就会和程序的工作目录组成文件的绝对路径,通过这种方式,操作系统才能根据相对路径找到文件。在.NET程序中,你可以使用下面这个属性来获取程序当前的工作路径:
Environment.CurrentDirectory
下面的示例代码演示了如何通过编程手段改变程序的工作目录--为了简介起见,我会在本书的示例代码里面去掉错误处理部分的代码:
using System; using System.Diagnostics; using System.IO; public class StartApp { static void Usage() { Console.WriteLine("Usage: StartApp <app> <working dir>"); } static void Main(string[] args) { if (args.Length != 2) { Usage(); return; } ProcessStartInfo si = new ProcessStartInfo( Path.GetFullPath(args[0])); // 避免操作系统为运行的程序打开一个新的窗口 si.UseShellExecute = false; si.WorkingDirectory = Path.GetFullPath(args[1]); Process.Start(si); } }
表 1-1 使用编程方法设置程序的工作目录