1、计算机学报2009 年 9 期 Vol.32,No.91软件分析技术进展 *梅宏 1 王千祥 1 张路 1 王戟 21.北京大学信息科学技术学院,高可信软件教育部重点实验室,北京 1008712.国防科技大学计算机学院,并行与分布处理国防科技重点实验室, 长沙 410073摘 要 软件分析技术的研究已有较长历史,相关成果也在软件生命周期的不同阶段中得到了广泛应用。软件生命周期中不同活动所需要的软件分析技术既不完全相同,又有许多交叠,且不同的分析技术之间互相影响。文章在讨论了软件分析的基本概念之后,主要从静态分析与动态分析两个方面介绍了一些主要的软件分析技术,以及部分相关分析工具。结合软件的质量
2、问题,文章还探讨了一些分析技术与软件质量属性的相关性,以便于人们在分析特定的软件质量属性时,选取合适的技术与工具。最后,文章展望了软件分析技术的发展趋势。关键词 软件分析,静态分析,动态分析,软件质量中图法分类号 TP3011. 引言软件是一种十分特殊的人工制品:它是人类“智力活动”的产物,是对客观事物的虚拟反映,是知识的固化与凝练。尽管软件迄今已有 50 多年的发展历史,但目前人们对于软件的许多认识还十分有限。例如:对于任何一个给定的软件,我们能否完全了解它的特性?软件分析就是一个以软件特性为关注点的研究领域。“分析” ,通俗来说,是以某种方式将复杂对象分解为更小的部分,以更好地理解该对象的
3、过程。分析技术很早就被应用于数学、逻辑等方面的研究,近代以来逐步被更多的学科(例如:化学、物理等)所大量采用。软件作为一个新发展起来的学科,在研究过程中引入分析技术是十分自然的。目前软件生命周期中的许多活动(分析、设计、实现、测试、部署、维护等)都离不开分析技术。然而,软件分析的能力是有限的:对于任何一个有一定规模的软件,希望获得关于它的完备描述通常是不现实的18。特别是,对于自动分析而言,许多问题是不可判定的。其中最典型的例子是停机不可判定问题:不存在一个这样的算法,对于任意的图灵机以及任意的输入,可以判断该图灵机是否停机64。但从软件分析这么多年所取得的进展可以看出,尽管软件分析的能力有限
4、,它仍然是软件领域十分有用的技术:将程序从高级语言向机器语言的翻译过程需要分析,判断一个程序是否符合需求规约需要分析技术,想了解程序是否存在安全漏洞需要分析技术,维护过程更是需要大量的分析技术,等等。本文将软件分析定义为“对软件进行人工或者自动分析,以验证、确认、或发现软件性质(或者规约、约束)的过程或活动”。下面对上述定义中几个术语进行解释。首先是“软件”:软件最初主要是指程序,后来逐步扩大到文档等其它形态软件制品。软件分析也从程序分析发展到了更大的范围,例如:对文档(含需求规约、设计文档、代码注释等)的分析、对运行程序的分析,等等。 “自动”也是很重要的概念:软件分析的历史几乎与软件的历史
5、一样长:自从有了软件就有了软件分析。最初的分析主要是人工进行的,但人工分析往往需要花费大量的时间与精力,因此,后来人们越来越多地关注自动分析。其中,编译技术的发展大大带动了软件的自动分析技术,目前的许多分析技术都可以在编译技术中找到基本雏形。所谓“验证”(Verification) ,是要回答 “软件制品是否与软件需求规约一致”的问题,而“确认” (Validation)则要回答“软件的特性是否符合用户需求”的问题。在英文中,人们经常用“Do the thing right”来解释“验证” ,而*资助项目:国家重点基础研究发展规划 973 项目(No. 2009CB320703);国家自然科学
6、基金委创新研究群体研究科学基金项目(No:60821003) ;国家 863 高技术项目(No. 2006AA01Z175)计算机学报2009 年 9 期 Vol.32,No.92用“Do the right thing”来解释“确认” 。 “发现” (Discover)是指在没有事先设定软件某个性质的前提下,通过分析发现软件的某种性质。之所以强调“性质” ,是因为分析的结果通常表示为软件是否符合或者具有某种性质(或者规约、约束) ,而这种性质不是软件本身自明的。在本文讨论的软件分析中,分析对象仅限于软件制品,不涉及对软件过程、软件人员与软件组织等的分析。目前与软件分析相关的综述性文献中,多数
7、只对软件分析的一个子集进行比较深入的介绍。例如1集中在对源代码分析的介绍,2主要介绍形式化的分析方法,53着重从语义的角度介绍程序分析,54集中在模型为中心的程序分析上。本文在这些工作的基础之上,尝试对软件分析涉及的主要方法进行尽可能全面的总结、分类。另外,考虑到近年来软件质量为人们所热切关注,本文在介绍分析技术之外,特别关注那些与质量相关的分析技术,并结合不同的软件质量属性,探讨不同的质量属性适合运用什么类型的分析技术。最后,文章结合软件形态、软件运行环境等几个驱动力对软件分析技术的发展趋势进行展望。2. 软件分析技术软件分析通常是另外一个更大的软件生命周期活动(例如:开发、维护、复用等)的
8、一部分,是实施这些过程的一个重要环节: a)在开发阶段,对正在开发的软件进行分析,以快速地开发出高质量的软件,例如:了解开发进展、预测开发行为、消除软件缺陷、程序变换等等; b)在维护阶段,对已经开发、部署、运行的某个软件进行分析,以准确地理解软件、有效地维护该软件,从而使软件提供更好的服务; c)在复用阶段,对以前开发的软件进行分析,以复用其中有价值的成分。上述各过程差异较大,需要的分析技术也多种多样。这导致目前的软件分析内容十分丰富,且相互之间的界限也不甚清晰:有些分析过程需要另外某个或某些分析的支持;有些分析针对具体的性质开展,而有些分析方法则可以支持多种性质的分析。2.1. 软件分析分
9、类为了对软件分析有个比较全面的了解,对其进行合理的分类是十分必要的。对软件分析进行分类的维度有很多。其中,分析对象是最重要的准则之一,即软件分析是对什么制品进行分析?从大的方面看,软件分析可以对代码进行,也可以对模型(需求规约、设计模型、体系结构等) 、文档甚至注释进行。代码可以进一步分为源码与目标码,目标代码又具有静态与运行态两种存在方式,而运行态的代码又可以进一步区分为离线的运行与在线的运行。除分析对象维度之外,还可以从方法学(结构化软件、面向对象软件、面向构件软件等) 、并行程度(串行软件、并行软件)等其它维度划分软件分析的内容。本文首先以分析过程“是否需要运行软件”为准则,将软件分析技
10、术划分为静态分析技术与动态分析技术两大类,然后又在每一大类技术下面做进一步的划分。图 1 是综合考虑静态、动态分析技术给出的一个分析过程示意图。 静 态 分 析 动 态 分 析 系 统 性 质 (规 约 、 约 束 ) 运 行 软 件 软 件 制 品 软 件 制 品 软 件 制 品 分 析 分 析 动 态 分 析 结 果 静 态 分 析 结 果 输 入 数 据 获 取 信 息 图 1 静态分析与动态分析的基本过程计算机学报2009 年 9 期 Vol.32,No.932.2. 静态分析静态分析是指在不运行软件前提下进行的分析过程。静态分析的对象一般是程序源代码,也可以是目标码(例如 JAVA
11、的 byte code) ,甚至可以是设计模型等形态的制品。静态代码分析主要可以应用于如下几个过程:1)查找缺陷,以消除软件中存在的缺陷;2)程序转换,以实施编译、优化等过程;3)后期的演化与维护;4)动态分析,等等。根据各种分析方法使用的广泛程度以及分析方法的相近性,本文将主要的代码静态分析划分为四类:基本分析、基于形式化方法的分析、指向分析与其它辅助分析(见图 2) 。其中,基本分析是一些常见的分析,例如语法分析、类型分析、控制流分析、数据流分析等,是多数编译器都包含的分析过程(词法分析由于相对简单而没有引入) ;而基于形式化方法的分析则在分析过程中大量采用一些数学上比较成熟的形式化方法,
12、以获得关于代码的一些更精确或者更广泛的性质。指向分析多数与指针密切相关,由于在静态分析中长期受到较多的关注,因此单独作为一类。其它辅助分析则包含了一些单独分析的目的性不是很强,但可以为前面几类分析提供支持的一些分析方法。需要指出的是,这不是一个严格的分类,而仅仅是为了便于人们比较全面地了解静态分析,对一些主要的、具有共性的静态分析进行归类而得到的一个结果。别 名 分 析 指 针 分 析 形 态 分 析 逃 逸 分 析 语 法 分 析 类 型 分 析 控 制 流 分 析 数 据 流 分 析 定 理 证 明 模 型 检 测 抽 象 解 释 约 束 求 解 基 本 分 析 指 向 分 析 基 于 形
13、 式 化 方 法 的 分 析 符 号 执 行 切 片 分 析 结 构 分 析 克 隆 分 析 其 它 辅 助 分 析 图 2 主要的静态分析技术2.2.1. 基本分析1)语法分析(Syntax Analysis) 。语法分析是按具体编程语言的语法规则分析和处理词法分析程序产生的结果并生成语法分析树的过程。这个过程可以判断程序在结构上是否与预先定义的 BNF 范式相一致,即程序中是否存在语法错误。程序的 BNF 范式一般由上下文无关文法描述。支持语法分析的主要技术包括算符优先分析法(自底向上) 、递归下降分析法(自顶向下)和 LR 分析法(自左至右、自底向上)等。语法分析是编译过程中的重要步骤,
14、也是多数其它分析的基础:如果一个程序连语法分析都没有通过,则对其进行其它的分析往往没有意义。2)类型分析(Type Analysis) 。类型分析主要是指类型检查( Type Checking) 。类型检查的目的是分析程序中是否存在类型错误。类型错误通常是指违反类型约束的操作,例如让两个字符串相乘,数组的越界访问,等等。类型检查通常是静态进行的,但也可以动态进行。编译时刻进行的类型检查是静态检查。对类型分析的支持程度是划分编程语言种类的准则之一:对于一种编程语言,如果它的所有表达式类型可以通过静态分析确定下来,进而消除类型错误,则这个语言是静态类型语言(也是强类型语言) 。利用静态类型语言开发
15、出的程序可以在运行程序之前消除许多错误,因此程序质量的保障相对容易一些(但表达的灵活性弱一些) 。3) 控制流分析(Control Flow Analysis) 。控制流分析的目标是得到程序的一个控制流图(Control Flow Graph) 。控制流图是对程序执行时可能经过的所有路径的图形化表示。通过根据不同语句之间的关系,特别是考虑由“条件转移” 、 “循环”等引入的分支关系,对过程内的一些语句进行合并,可以得到关于程序结构的一些结果。一个控制流图是一个有向图:图中的结点对应于程序中经过合并的基本语句块,图中的边对应于可能的分支方向,例如:条件转移、循环等等,这些都是分析程序行为的重要信
16、息。计算机学报2009 年 9 期 Vol.32,No.944) 数据流分析(Data Flow Analysis) 。数据流分析试图确定在程序的某一点(语句) ,关于各个变量的使用或者可能取值情况。数据流分析一般从程序的一个控制流图开始。数据流分析主要有前向分析(Forward Analysis) 、后向分析(Backward Analysis)两种方法。前向分析的一个例子是可达定义( reaching definitions) 。它计算对于程序的每一点,可能到达该点的定义的集合。后向分析的一个例子是活跃变量(live variables) 。它计算对于程序的每一点,程序后面的语句可能读取且
17、没有再次修改的变量。这个结果对于消除死代码(dead code)很有用:如果一个变量在某个阶段被定义后,后面的语句一直不会用到这个定义,那么这个定义就是死代码,应该从程序中删除。基于格(lattice)与不动点(fixpoint)理论的数据流分析是目前被广泛使用的技术:首先对控制流图中的每个节点建立一个数据流等式(equations) ,并根据分析目标构造一个具有有限高度的格 L ,然后不断重复计算每个节点的输出,直到达到格的一个不动点。许多编译器为了进行编译优化而引入了数据流分析技术。由于上述四种基本分析是多数编译器包含的内容,因此很早就得到了较深入的研究62。这些分析过程还有一个共同特点是
18、分析的输入仅仅是软件代码,不需要提供图 1 中的“系统性质” 。或者说,基本分析技术所需要的“系统性质”都是最基本的性质。例如语法分析对应的“系统性质”就是编程语言的 BNF 范式,类型分析对应的“系统性质”是预先定义的类型约束,数据流分析对应的“系统性质”是编程语言的基本约定,等等。而下面要讲到的形式化分析、指向分析则通常要事先提供待验证的性质,例如:某个变量的取值是否在某个范围内、某两个变量名是否指向相同的内存实例、等等。2.2.2. 基于形式化方法的分析为了提高分析的准确度,获取关于程序的更多性质,许多研究人员借用形式化方法来扩展基本分析技术。代表性技术有模型检验、定理证明、约束求解、抽
19、象解释等。1) 模型检验(Model Checking) 。模型检验用状态迁移系统表示系统的行为,用模态/时序逻辑公式描述系统的性质,然后用数学问题“状态迁移系统是否是该逻辑公式的一个模型”来判定“系统是否具有所期望的性质”32。模型检验虽然在检查硬件设计错误方面简单明了且自动化程度高,然而被应用在软件程序分析与验证时却存在着难以解决的状态空间爆炸问题。另外,由于模型检验所针对的检查对象是模型而非程序本身,任何在将程序向模型转化的过程中所使用的抽象技术以及转化工作都有可能使模型与程序不一致或者存在偏差,从而导致最终的检查结果无法准确反映实际程序中存在的错误情况。更多关于模型检验的深入讨论可以参
20、见32 。支持模型检验的代表性软件分析工具为 SLAM23、MOPS24、Bandera25和 Java PathFinder2 26。2) 定理证明(Theorem Proving) 。自动定理证明通过将验证问题转换为数学上的定理证明问题来判断待分析程序是否满足指定属性 1,是众多分析方法中最复杂最准确的方法。然而,一阶逻辑是半可判定的,理论分析结果表明,机械化的定理证明过程并不保证停机。另外,为了获取指定的属性以实现有效的证明,这些工具都要求程序员通过向源程序中添加特殊形式的注释来描述程序的前置条件、后置条件以及循环不变量。这无疑增加了程序员的工作量,也导致该方法难以广泛应用于大型应用程序
21、。使用定理证明的代表性软件分析工具为 ESC27和 ESC/Java28。3) 约束求解(Constraint Solving) 。基于约束求解的程序分析技术将程序代码转化为一组约束,并通过约束求解器获得满足约束的解48。早期的研究表明,面向路径的测试数据生成可以很好地归结为约束求解问题。后来,学者们又发现程序中不变式的分析也可以归结为约束求解问题。最新的研究表明,许多其的程序分析问题也可以归结为约束求解问题:由于从程序获得的约束通常采用一阶或二阶的形式表示,可以进一步将其转换成约束求解器可处理的形式。支持约束求解的代表性软件分析工具为 SAT/SMT Solver49。4) 抽象解释(Abs
22、tract Interpretation) 。程序的抽象解释就是使用抽象对象域上的计算逼近程序指称的对象域上的计算,使得程序抽象执行的结果能够反映出程序真实运行的部分信息。抽象解释本质上是在计算效率和计算精度之间取得均衡,以损失计算精度求得计算可行性,再通过迭代计算增强计算精度的一种抽象计算机学报2009 年 9 期 Vol.32,No.95逼近方法。通过不断迭代,抽象解释最终为程序建立一个抽象模型。如果抽象模型中不存在错误,就证明其对应的源程序中也不存在错误。具有抽象解释分析功能的代表性分析工具为 Proverif29和 ASTREE30。形式化支持的分析技术在分析软件的某个性质时,需要首先
23、对该性质进行形式化的描述,然后将这个描述与软件制品一起作为输入提供给分析工具。其中,模型检验首先被用于对软件的模型进行分析,后来有研究人员通过从代码中提取模型,然后将模型检验技术应用于代码分析。定理证明主要对静态代码比较适用。约束求解多用于输入数据的生成。基于抽象解释理论的形式化方法是对大规模软件、硬件系统进行自动化分析与验证的有效途径之一,已经被广泛地应用于大型软件与硬件系统的验证研究中。2.2.3. 指向分析1) 别名分析( Alias Analysis) 。别名分析主要用于确定程序中不同的内存引用(reference)是否指向内存的相同区域。在编译过程中,这可以帮助判断一个语句将影响什么
24、变量。例如,考虑如下的代码: .; p.foo = 1; q.foo = 2; i = p.foo + 3; .。如果 p 和 q 不是别名,那么 i = p.foo + 3; 等价于 i = 4; 如果 p和 q 是别名,那么 i = p.foo + 3; 等价于 i = 5; 这样就可以对代码进行等价优化。别名分析又可以分为基于类型的分析与基于流的分析。前者主要用于类型安全(type safe)的语言,后者则主要用于含有大量引用与类型转换的语言3。2) 指针分析(Pointer Analysis) 。指针分析试图确定一个指针到底指向哪些对象或者存储位置,尤其是,在某个语句处是否可能为空。由
25、于受到可判定性问题的限制,加上分析过程中时间、存储等的限制,多数的指针分析方法都在分析过程中进行“近似”或者“简化” ,并导致分析结果精确性不够。实际上,上面的别名分析与下面的形态分析、逃逸分析都与指针分析密切相关。3) 形态分析(Shape Analysis) 。形态分析主要用于发现或者验证程序中动态分配结构的性质。对于一个具体的程序,形态分析将为其构造一个形态图(shape graph) ,用于列出每个指针可能指向的目标,以及目标之间的关系。形态分析可以认为是指针分析的一种, 但比一般的指针分析精确:形态分析可以确定一个小一些但是更精确的指向集合。例如在 Java 程序中,可用来保证一个排
26、序算法正确地对列表进行了排序;在 C 程序中,可以用来分析一个内存是否被正确地释放。尽管形态分析很强大,但往往需要花费较多的时间4。4) 逃逸分析(Escape Analysis) 。逃逸分析计算变量的可达边界。对于一个方法 m 中的一个变量,如果变量是在调用方法 m 时创建的,但在 m 的生命周期之外可以获得该变量,我们就说这个变量逃逸了方法 m。类似地,一个变量逃逸了一个线程 t, 如果在 t 之外的一个点能通过一个引用访问到该变量。逃逸分析传统上被用于查找一些变量,它们只存在于为它们分配内存的方法或线程的生命周期内:前者允许变量在运行时的栈(stack )上,而不是堆(heap)上分配内
27、存,这样就可以降低堆的碎片与垃圾回收负载。后者被用于进行优化,以避免高成本的异步操作。逃逸分析检查引用的赋值与使用(assignments and uses )以计算每个变量的逃逸状态。每个变量可以被赋予3个可能逃逸状态中的一个:全局逃逸、参数逃逸或者捕获。当一个变量是全局可达的(例如被赋值给了一个静态域)时,这个变量被标记为全局逃逸;如果变量是通过参数或者被返回给调用者方法,它被标记为参数逃逸;一个不逃逸的变量被标记为捕获。逃逸分析主要用于效率分析22。与基本分析技术相比,指向分析通常与应用程序的某个特定性质密切相关。这类分析一般是以基本分析(尤其是数据流分析)为主要分析框架,为了提高分析精
28、度而提出的技术,且分别结合了编程语言的不同特点。例如:别名分析利用的变量引用、形态分析利用的指针等等。这也导致了这些分析技术分别适合由不同编程语言实现的程序。另外,这些分析技术尽管可以自动进行,但在分析之前通常需要较多的人工介入,例如,指定对哪些变量进行分析、提供对什么性质进行分析等等。2.2.4. 其它辅助分析1) 符号执行(Symbolic Execution) 。符号执行通过使用抽象的符号表示程序中变量的值来模拟程序的计算机学报2009 年 9 期 Vol.32,No.96执行14, 31。其特点在于通过跟踪被模拟的各条执行路径上变量的实际取值,把分析工作局限在实际可达的路径上,从而使得
29、到的结果更贴近程序实际执行情况,并为程序员提供更为准确的与检出的缺陷相关的上下文信息。但是由于需要穷举各条可能执行的路径,该技术需要处理的工作量随着程序规模的增大而呈指数级别增长。虽然符号执行方法可以被应用于大型程序的分析,其分析结果的可靠性仍依赖于所允许的分析时间和对路径及其数目的选择等方面。2) 切片分析(Slicing Analysis) 。切片分析用于从源程序中抽取对程序中兴趣点上的特定变量有影响的语句和谓词,组成新的程序(称作切片) ,然后通过分析切片来分析源程序的行为42。计算程序切片的方法主要有两种:根据数据流方程计算和根据依赖图关系计算。切片分析技术已被广泛应用于程序分析、理解
30、、调试、测试、软件维护等过程38,39。3) 结构分析(Structure Analysis) 。结构分析的目标是获得程序的调用关系图(Call Graph) ,以展示程序中各个函数之间的调用关系。把程序中每个函数当作一个节点,再分析每个函数调用了哪些其他函数,并在存在调用关系的函数间建立一条边,就可以得到调用关系图。调用关系图通常用于辅助开发人员理解程序。对于面向对象程序,程序的结构分析还包括从程序中获取类图(class diagram)等。除了一些编译器支持结构分析外,目前软件开发过程中的一些工具,例如 IBM Rational Rose 等也能够对以开发出的代码进行结构分析。4) 克隆分
31、析( Clone Analysis) 。代码克隆(Code clone)是指软件开发中由于复制、粘贴引起的重复代码现象。研究指出,一般商业软件中存在 5%至 20%的重复代码1。由于克隆代码的普遍性以及克隆代码对代码质量的重要影响,代码克隆相关研究是静态代码分析领域近年来一个十分活跃的研究分支。主要研究内容包括:克隆代码检测、由代码克隆引起的代码缺陷诊断、通过代码重构来减少代码克隆、克隆代码跟踪、基于代码克隆的源代码演化分析等等。代码克隆分析有十分丰富的实际应用价值,比如缺陷诊断、重构、代码理解、源代码演化分析和代码剽窃检查等等。克隆代码可以分为如下四类:1) 除空格、回车以及注解之外完全相同
32、的代码片段;2) 除空格、回车、注解以及变量名及常量值替换外,语法结构完全相同的代码片段;3) 除空格、回车、注解以及变量名及常量值替换外,语法结构基本相同,但含有少量语句的增加、删除或修改的代码片段;4) 两段或多段代码具有相同或相似的功能,或者说相似的输入、输出条件。其中,最后一类比较特殊,是语义(功能)相似性,其它三类都是文本相似性5-17 。2.3. 动态分析动态分析是通过运行具体程序并获取程序的输出或者内部状态等信息来验证或者发现软件性质的过程。与静态分析相比,动态分析具有如下几方面特点:1)需要运行系统,因此通常要向系统输入具体的数据;2)由于有具体的数据,因此分析结果更精确,但同
33、时只是对于特定输入情况精确,对于其它输入的情况则不能保证。本文从运行信息的获得途径与获得时机两个方面对动态分析进行介绍。在信息获得的时机上,又根据软件是否已经上线投入使用将软件的动态分析划分为两大类:离线动态测试/验证(Offline Dynamic Testing/Verification)与在线监测(Online Monitoring) 。所谓离线动态测试/验证,是指在系统还没有正式上线时对软件进行运行、分析,分析过程中可以随意输入数据,并尽量模拟实际用户的操作。所谓在线监测,是指在系统已经上线后对软件系统进行分析,监测过程中一般不能随意输入数据,所有数据都是真实的。离线动态测试/验证、在
34、线监测与运行信息获取之间的基本关系见图 3。计算机学报2009 年 9 期 Vol.32,No.97正 常 输 出 插 装 代 码 平 台 接 口 运 行 信 息 获 取 途 径 离 线 动 态 测 试 /验 证 在 线 监 测 输 入 数 据 生 成 约 束 描 述 执 行 轨 迹 分 析 交 互 消 息 监 测 内 部 状 态 监 测 运 行 环 境 监 测 图 3 动态分析涉及的主要技术2.3.1. 运行信息的获取途径1) 从程序的正常输出中获取信息每个程序在运行过程中都会产生许多输出信息。有些输出是程序运行中间或者结束时输出的正常结果,有些是一些提示信息,还有一些是日志信息。通过将最终
35、得到的实际输出结果与事先设定的期望输出结果进行对比、分析,就可以得到关于软件的有价值信息。2) 通过插装代码获取信息仅仅通过观察程序的正常输出对于了解软件的运行信息往往是不够的。例如,软件运行过程中内部变量的状态信息、某个特定类型的实例信息、模块之间的交互信息等等。这些信息对于发现缺陷,以及定位缺陷特别重要。获得这些内部信息的自然方式是在软件中插装监测代码(Monitoring Code) ,通过这些监测代码就可以获得相应的信息。主要的监测代码插装方法可以分为如下三类: 源码插装。这是最自然的插装方式,即在编写应用系统时,在需要监测的地方直接加上监测代码,例如,增加输出信息语句、增加日志语句等
36、等。AOP(Aspect Oriented Programming)技术出现之后,人们发现 AOP 可以被很好地用于代码插装,以有效地分离系统的业务逻辑与监测逻辑43。 静态目标码插装。近年来字节码插装技术在 Java 社区中十分流行。字节码插装可以在静态直接更改中间代码文件(例如 Java 的.class 文件)或在装载时刻进行字节码插装。字节码插装所具有的执行效率高、插装点灵活、应用范围广等特点,使其被广泛应用于 AOP 等研究领域,并陆续出现了 BCEL64、Javassist65、ASM66 等多种字节码操纵工具。 基于截取器(Interceptor)的获取方式。截取器处于调用者和被调
37、用者之间,可以截获二者之间传递的消息,从而完成一些特定的处理工作。由于这种获取方式不需要直接修改目标程序,代码侵入性较弱,甚至可以在运行阶段部署,因此得到了越来越广泛的使用。Tomcat 服务器、EJB3 规范、Spring 框架中都有截取器的实现40。3) 通过平台接口获取信息向目标系统插装监测代码可以很方便地获得内部信息。如果底层的运行平台(操作系统、JVM、中间件、或者数据库管理系统等)提供很好的支持,则许多信息获取起来就更加方便。例如,许多研究人员通过开发特殊的 JAVA 虚拟机来获取所需要的监测信息。JPF50、QVM51等就是典型代表。目前标准的 JVM 本身也提供了许多供调试、监
38、测的接口,例如 JVMTI52等等。计算机学报2009 年 9 期 Vol.32,No.982.3.2. 离线动态测试/验证离线的动态测试与动态验证都需要在离线的情况下运行程序,并获取、分析运行信息,因此二者有比较密切的联系。不仅如此,为发现更多的软件缺陷,离线动态验证与动态测试都需要仔细准备输入数据。另外,如果将程序的输出与输入之间的关系看作一种约束需求,或者在分析测试结果时也收集日志等内部信息的话,二者就更接近了。从不同之处看,离线动态验证一般比测试收集更多的内部信息、关注更多的约束需求,而且往往利用一些轻权(lightweight )的形式化方法来描述这些需求。从研究内容看,离线动态测试
39、/验证主要关注三个方面的内容:输入数据生成、约束描述、运行轨迹分析。 输入数据生成。为了尽可能多地发现潜在的缺陷,在运行程序之前通常需要首先静态地分析目标程序,根据验证目标(什么功能、什么约束、等等)辅助用户生成和选择输入数据。 约束描述。利用形式化方法描述软件约束,以便于分析能够自动进行。目前多数动态验证研究人员利用线性时序逻辑(LTL: Linear Temporal Logic)来描述。 运行轨迹分析。程序运行过程中产生的内部、外部数据可能是大量的,需要测试/验证目标对数据进行必要的过滤,然后分析这些数据以推断程序的执行轨迹,并进一步判断程序的执行是否遵循程序的约束。2.3.3. 在线监
40、测与离线动态验证相比,在线监测有几个特点:1)系统输入由实际的真实用户与系统拥有者共同决定;2)系统拥有者的输入是受限制的,一些在上线之前可以做的实验(例如:压力测试、安全性测试等)此时不能随意实施,否则就会威胁到系统正常的服务质量;3)监测代码往往就是应用系统的组成部分。这使得在线监测与前面介绍的各种分析都非常的不同:前面介绍的分析技术一般都由特定的分析工具支持,分析工具是一种外部辅助工具,在系统上线后,分析工具就与系统分离了。而在线监测则可能一直伴随系统的服务过程,并且可能是在系统上线之后,在不同的维护阶段增加上去的。有研究人员因此提出了面向监测的编程(MOP)37。在分析机制上,在线监测
41、的分析可以采用内联模式(inline)或者外联模式(outline) 。在内联监测模式中,监测代码(monitor)与被监测程序运行在相同的空间中,因此执行效率相对要高,且发现异常后响应要快。在外联监测模式中,监测代码运行在独立的运行空间中,比如另外一台机器或者 CPU。外联模式效率要低一些,但可以对多项监测内容进行综合处理,因此可以做更深入的分析。对于在线系统,如果要监测它,就一定会影响它。如果影响过大,就可能给正常的服务过程带来负面作用。因此,衡量在线监测技术的一个重要指标是监测开销,尤其是运行开销。许多监测的时间开销甚至高于程序自身的运行开销。由于在线监测一定会对监测对象的运行产生影响,
42、因此监测的结果也一定是被影响后系统的表现,而不是被监测对象自身的表现。针对这个现象,有些研究人员参照物理学中的测不准原理提出了软件的测不准原理34。需要特别注意的是,在系统上线之后,有价值的监测通常不仅仅针对软件自身,而是针对由软件、硬件组成的服务系统,甚至包括与服务系统交互的环境(用例图中的 Actor)35。例如:客户程序的调用序列、系统的响应时间等等。在这个意义上,在线监测不仅仅是努力发现软件的缺陷,而且关注服务过程的潜在问题(例如:响应时间是否足够小?是否发现可疑的攻击行为?) 。因此,随着 Web 服务技术、软件作为服务(SaaS:Software as a Service)的推广,
43、在线监测正受到越来越多研究人员的关注 33。另外,多核处理器的发展,也为监测提供了更多的途径:由于业务逻辑与监测逻辑相对分离,完全可以由不同的核分别进行业务处理与监测处理。 对软件内部的监测。对软件内部的监测方法与运行时验证的方法比较接近,上面提到的各种插装技术对于在线监测都适用。不仅如此,对于在线系统,还可以利用在线升级(upgrading)的技术36,或者动态编织(weaving)技术41等将监测代码“在线”地部署到目标系统中,以加强监测的覆盖面;或者从系统中计算机学报2009 年 9 期 Vol.32,No.99移除监测代码,以降低监测开销。除此之外,还有许多对实例的监测,例如:负责客户
44、连接对象的实例数目、负责数据库连接的实例数目等等。 对外部交互的监测。主要监测对象包括:来自客户程序的请求消息顺序是否正确?参数值是否在允许的范围内?请求者的权限是否够(与安全相关)?应答消息的参数值是否在允许的范围内?从收到请求消息到发出应答消息的响应时间是否符合要求?管理人员的操作是否合法?等等。 对运行环境的监测。对运行环境的监测主要体现为对底层资源的监测,通常是独立于具体应用的监测内容,例如:CPU 的使用情况、内存使用情况、网络带宽等等。对于越来越多的嵌入式系统,由于各种资源都有较大的限制,监测就显得更加重要。2.4. 分析技术的评价面对种类众多的软件分析技术,人们通常不仅希望能了解
45、它们,还希望对它们进行评价、比较,以在其中选择选择合适自己当前任务的分析技术。实际上,由于这些技术错综复杂,对它们的评价本身也是一个十分值得研究的题目。目前人们比较关注的评价准则有:误报率(False positive rate) 、漏报率(False negative rate) ,精度(Precision ) 、速度(Speed) 、等等。误报率与漏报率。误报是指当软件不存在某个缺陷时,分析工具报告软件可能存在某个缺陷。大量的误报将导致大量的人工分析工作:人们必须手工判断系统是否真的存在某个缺陷。漏报是指软件中存在某个缺陷,但分析工具没有报告这个缺陷。将报告结果与缺陷的实际情况进行对比,就
46、可以得到具体的量化指标:误报率与漏报率。误报率与漏报率是目前评价分析工具的两个最重要的指标:一个工具的误报率与漏报率越小,说明这个分析工具越好。但实际情况往往是:有的方法在降低误报率方面很有效,但往往同时增加了漏报率;而有的方法在降低漏报率方面很有效,但却抬高了误报率1 。误报率与漏报率的反面说法分别是查准率(Precision)与查全率( Recall) 。一般来说,静态分析可以比较全面地考虑执行路径,因此可以比动态分析发现更多的缺陷,漏报率比动态分析低;但动态分析由于获取了具体的运行信息,因此报出的缺陷一般更为准确,误报率比静态分析低。精度与速度。为了提高分析的精度,即降低误报率与漏报率,
47、实际的静态分析工具往往综合运用多种分析技术,并需要做一些较深入的分析,这自然意味着更长的分析时间。因此,分析的精度与分析的速度往往也是一对不可兼得的矛盾体,必须在二者之间进行折中。另外,动态分析由于往往一次只关注少部分的软件性质,因此精度可以更高一些,而且受程序规模的限制较小;而静态分析在程序运行之前就可以实施,因此可以更早地发现问题。3. 软件分析与质量保障软件分析的一个主要应用是保障软件质量:通过分析某个软件,查找出其中包含的软件缺陷,就可以让开发人员修改软件,将缺陷修复,从而提高软件质量。ISO9126 提出了一个两层、六类的质量模型,包括:1) 功能性(含:适合性、准确性、互操作性、保
48、密安全性) ;2) 可靠性(含:成熟性、容错性、易恢复性) ;3) 易用性分析(含:易理解性、易学性、易操作性、吸引性) ;4) 效率(含:时间特性、资源利用性) ;5) 易维护性(含:易分析性、易改变性、稳定性、易测试性) ;6) 可移植性(含适应性、易安装性、共存性、易替换性) 。本文以 ISO9126 的分类为基础,从软件分析的角度出发,结合上面的质量属性,归纳出 5 种相对并列的软件质量属性:正确性(Correctness ) 、健壮性(Robustness) 、安全性(Security) 、效率(Efficiency) 、易维护性(Maintenance ) 。当然,这 5 种属性之
49、间不是完全正交的,它们之间存在着一些不同形式的关联。本节主要介绍如何利用上一节中提到的一些分析技术对这些属性进行分析。尤其是,如何利用这些技术发现相应的质量问题。计算机学报2009 年 9 期 Vol.32,No.9103.1 软件正确性“正确性”主要指程序的运行结果是否与预期值相同。如果不相同,则视为结果不正确,该程序包含一个“正确性”错误。正确性的考虑比较单纯,主要是从输入到输出这个函数映射的角度看待计算过程,特别关心输出结果的正确与否,而不考虑输入数据的具体来源与错误输出结果的后果如何。与正确性相对的是健壮性与安全性:正确性考虑的是软件是否按照预先的设定执行,健壮性与安全性则考虑软件是否在一定条件下执行设定之外的事情。3.2.1 动态测试/验证动态测试/验证是发现正确性缺陷最有效的手段。对于任何一个稍具规模的软件而言,穷举所有可能输入的测试/验证都是不现实的。因此,对于发现与正确性有关的缺陷而言,测试/验证的关键在于生成有较强揭示错误能力的输入数据,并通过程序的执行最终检测与正确性相关的缺陷。基于程序分析的数据输入生成