软件分析技术进展Word格式.docx

上传人:b****8 文档编号:22419886 上传时间:2023-02-04 格式:DOCX 页数:28 大小:94.89KB
下载 相关 举报
软件分析技术进展Word格式.docx_第1页
第1页 / 共28页
软件分析技术进展Word格式.docx_第2页
第2页 / 共28页
软件分析技术进展Word格式.docx_第3页
第3页 / 共28页
软件分析技术进展Word格式.docx_第4页
第4页 / 共28页
软件分析技术进展Word格式.docx_第5页
第5页 / 共28页
点击查看更多>>
下载资源
资源描述

软件分析技术进展Word格式.docx

《软件分析技术进展Word格式.docx》由会员分享,可在线阅读,更多相关《软件分析技术进展Word格式.docx(28页珍藏版)》请在冰豆网上搜索。

软件分析技术进展Word格式.docx

其中最典型的例子是停机不可判定问题:

不存在一个这样的算法,对于任意的图灵机以及任意的输入,可以判断该图灵机是否停机[64]。

但从软件分析这么多年所取得的进展可以看出,尽管软件分析的能力有限,它仍然是软件领域十分有用的技术:

将程序从高级语言向机器语言的翻译过程需要分析,判断一个程序是否符合需求规约需要分析技术,想了解程序是否存在安全漏洞需要分析技术,维护过程更是需要大量的分析技术,等等。

本文将软件分析定义为“对软件进行人工或者自动分析,以验证、确认、或发现软件性质(或者规约、约束)的过程或活动”。

下面对上述定义中几个术语进行解释。

首先是“软件”:

软件最初主要是指程序,后来逐步扩大到文档等其它形态软件制品。

软件分析也从程序分析发展到了更大的范围,例如:

对文档(含需求规约、设计文档、代码注释等)的分析、对运行程序的分析,等等。

“自动”也是很重要的概念:

软件分析的历史几乎与软件的历史一样长:

自从有了软件就有了软件分析。

最初的分析主要是人工进行的,但人工分析往往需要花费大量的时间与精力,因此,后来人们越来越多地关注自动分析。

其中,编译技术的发展大大带动了软件的自动分析技术,目前的许多分析技术都可以在编译技术中找到基本雏形。

所谓“验证”(Verification),是要回答“软件制品是否与软件需求规约一致”的问题,而“确认”(Validation)则要回答“软件的特性是否符合用户需求”的问题。

在英文中,人们经常用“Dothethingright”来解释“验证”,而用“Dotherightthing”来解释“确认”。

“发现”(Discover)是指在没有事先设定软件某个性质的前提下,通过分析发现软件的某种性质。

之所以强调“性质”,是因为分析的结果通常表示为软件是否符合或者具有某种性质(或者规约、约束),而这种性质不是软件本身自明的。

在本文讨论的软件分析中,分析对象仅限于软件制品,不涉及对软件过程、软件人员与软件组织等的分析。

目前与软件分析相关的综述性文献中,多数只对软件分析的一个子集进行比较深入的介绍。

例如[1]集中在对源代码分析的介绍,[2]主要介绍形式化的分析方法,[53]着重从语义的角度介绍程序分析,[54]集中在模型为中心的程序分析上。

本文在这些工作的基础之上,尝试对软件分析涉及的主要方法进行尽可能全面的总结、分类。

另外,考虑到近年来软件质量为人们所热切关注,本文在介绍分析技术之外,特别关注那些与质量相关的分析技术,并结合不同的软件质量属性,探讨不同的质量属性适合运用什么类型的分析技术。

最后,文章结合软件形态、软件运行环境等几个驱动力对软件分析技术的发展趋势进行展望。

2.软件分析技术

软件分析通常是另外一个更大的软件生命周期活动(例如:

开发、维护、复用等)的一部分,是实施这些过程的一个重要环节:

a)在开发阶段,对正在开发的软件进行分析,以快速地开发出高质量的软件,例如:

了解开发进展、预测开发行为、消除软件缺陷、程序变换等等;

b)在维护阶段,对已经开发、部署、运行的某个软件进行分析,以准确地理解软件、有效地维护该软件,从而使软件提供更好的服务;

c)在复用阶段,对以前开发的软件进行分析,以复用其中有价值的成分。

上述各过程差异较大,需要的分析技术也多种多样。

这导致目前的软件分析内容十分丰富,且相互之间的界限也不甚清晰:

有些分析过程需要另外某个或某些分析的支持;

有些分析针对具体的性质开展,而有些分析方法则可以支持多种性质的分析。

2.1.软件分析分类

为了对软件分析有个比较全面的了解,对其进行合理的分类是十分必要的。

对软件分析进行分类的维度有很多。

其中,分析对象是最重要的准则之一,即软件分析是对什么制品进行分析?

从大的方面看,软件分析可以对代码进行,也可以对模型(需求规约、设计模型、体系结构等)、文档甚至注释进行。

代码可以进一步分为源码与目标码,目标代码又具有静态与运行态两种存在方式,而运行态的代码又可以进一步区分为离线的运行与在线的运行。

除分析对象维度之外,还可以从方法学(结构化软件、面向对象软件、面向构件软件等)、并行程度(串行软件、并行软件)等其它维度划分软件分析的内容。

本文首先以分析过程“是否需要运行软件”为准则,将软件分析技术划分为静态分析技术与动态分析技术两大类,然后又在每一大类技术下面做进一步的划分。

图1是综合考虑静态、动态分析技术给出的一个分析过程示意图。

图1静态分析与动态分析的基本过程

2.2.静态分析

静态分析是指在不运行软件前提下进行的分析过程。

静态分析的对象一般是程序源代码,也可以是目标码(例如JAVA的bytecode),甚至可以是设计模型等形态的制品。

静态代码分析主要可以应用于如下几个过程:

1)查找缺陷,以消除软件中存在的缺陷;

2)程序转换,以实施编译、优化等过程;

3)后期的演化与维护;

4)动态分析,等等。

根据各种分析方法使用的广泛程度以及分析方法的相近性,本文将主要的代码静态分析划分为四类:

基本分析、基于形式化方法的分析、指向分析与其它辅助分析(见图2)。

其中,基本分析是一些常见的分析,例如语法分析、类型分析、控制流分析、数据流分析等,是多数编译器都包含的分析过程(词法分析由于相对简单而没有引入);

而基于形式化方法的分析则在分析过程中大量采用一些数学上比较成熟的形式化方法,以获得关于代码的一些更精确或者更广泛的性质。

指向分析多数与指针密切相关,由于在静态分析中长期受到较多的关注,因此单独作为一类。

其它辅助分析则包含了一些单独分析的目的性不是很强,但可以为前面几类分析提供支持的一些分析方法。

需要指出的是,这不是一个严格的分类,而仅仅是为了便于人们比较全面地了解静态分析,对一些主要的、具有共性的静态分析进行归类而得到的一个结果。

图2主要的静态分析技术

2.2.1.基本分析

1)语法分析(SyntaxAnalysis)。

语法分析是按具体编程语言的语法规则分析和处理词法分析程序产生的结果并生成语法分析树的过程。

这个过程可以判断程序在结构上是否与预先定义的BNF范式相一致,即程序中是否存在语法错误。

程序的BNF范式一般由上下文无关文法描述。

支持语法分析的主要技术包括算符优先分析法(自底向上)、递归下降分析法(自顶向下)和LR分析法(自左至右、自底向上)等。

语法分析是编译过程中的重要步骤,也是多数其它分析的基础:

如果一个程序连语法分析都没有通过,则对其进行其它的分析往往没有意义。

2)类型分析(TypeAnalysis)。

类型分析主要是指类型检查(TypeChecking)。

类型检查的目的是分析程序中是否存在类型错误。

类型错误通常是指违反类型约束的操作,例如让两个字符串相乘,数组的越界访问,等等。

类型检查通常是静态进行的,但也可以动态进行。

编译时刻进行的类型检查是静态检查。

对类型分析的支持程度是划分编程语言种类的准则之一:

对于一种编程语言,如果它的所有表达式类型可以通过静态分析确定下来,进而消除类型错误,则这个语言是静态类型语言(也是强类型语言)。

利用静态类型语言开发出的程序可以在运行程序之前消除许多错误,因此程序质量的保障相对容易一些(但表达的灵活性弱一些)。

3)控制流分析(ControlFlowAnalysis)。

控制流分析的目标是得到程序的一个控制流图(ControlFlowGraph)。

控制流图是对程序执行时可能经过的所有路径的图形化表示。

通过根据不同语句之间的关系,特别是考虑由“条件转移”、“循环”等引入的分支关系,对过程内的一些语句进行合并,可以得到关于程序结构的一些结果。

一个控制流图是一个有向图:

图中的结点对应于程序中经过合并的基本语句块,图中的边对应于可能的分支方向,例如:

条件转移、循环等等,这些都是分析程序行为的重要信息。

4)数据流分析(DataFlowAnalysis)。

数据流分析试图确定在程序的某一点(语句),关于各个变量的使用或者可能取值情况。

数据流分析一般从程序的一个控制流图开始。

数据流分析主要有前向分析(ForwardAnalysis)、后向分析(BackwardAnalysis)两种方法。

前向分析的一个例子是可达定义(reachingdefinitions)。

它计算对于程序的每一点,可能到达该点的定义的集合。

后向分析的一个例子是活跃变量(livevariables)。

它计算对于程序的每一点,程序后面的语句可能读取且没有再次修改的变量。

这个结果对于消除死代码(deadcode)很有用:

如果一个变量在某个阶段被定义后,后面的语句一直不会用到这个定义,那么这个定义就是死代码,应该从程序中删除。

基于格(lattice)与不动点(fixpoint)理论的数据流分析是目前被广泛使用的技术:

首先对控制流图中的每个节点建立一个数据流等式(equations),并根据分析目标构造一个具有有限高度的格L,然后不断重复计算每个节点的输出,直到达到格的一个不动点。

许多编译器为了进行编译优化而引入了数据流分析技术。

由于上述四种基本分析是多数编译器包含的内容,因此很早就得到了较深入的研究[62]。

这些分析过程还有一个共同特点是分析的输入仅仅是软件代码,不需要提供图1中的“系统性质”。

或者说,基本分析技术所需要的“系统性质”都是最基本的性质。

例如语法分析对应的“系统性质”就是编程语言的BNF范式,类型分析对应的“系统性质”是预先定义的类型约束,数据流分析对应的“系统性质”是编程语言的基本约定,等等。

而下面要讲到的形式化分析、指向分析则通常要事先提供待验证的性质,例如:

某个变量的取值是否在某个范围内、某两个变量名是否指向相同的内存实例、等等。

2.2.2.基于形式化方法的分析

为了提高分析的准确度,获取关于程序的更多性质,许多研究人员借用形式化方法来扩展基本分析技术。

代表性技术有模型检验、定理证明、约束求解、抽象解释等。

1)模型检验(ModelChecking)。

模型检验用状态迁移系统表示系统的行为,用模态/时序逻辑公式描述系统的性质,然后用数学问题“状态迁移系统是否是该逻辑公式的一个模型”来判定“系统是否具有所期望的性质”[32]。

模型检验虽然在检查硬件设计错误方面简单明了且自动化程度高,然而被应用在软件程序分析与验证时却存在着难以解决的状态空间爆炸问题。

另外,由于模型检验所针对的检查对象是模型而非程序本身,任何在将程序向模型转化的过程中所使用的抽象技术以及转化工作都有可能使模型与程序不一致或者存在偏差,从而导致最终的检查结果无法准确反映实际程序中存在的错误情况。

更多关于模型检验的深入讨论可以参见[32]。

支持模型检验的代表性软件分析工具为SLAM[23]、MOPS[24]、Bandera[25]和JavaPathFinder2[26]。

2)定理证明(TheoremProving)。

自动定理证明通过将验证问题转换为数学上的定理证明问题来判断待分析程序是否满足指定属性[1],是众多分析方法中最复杂最准确的方法。

然而,一阶逻辑是半可判定的,理论分析结果表明,机械化的定理证明过程并不保证停机。

另外,为了获取指定的属性以实现有效的证明,这些工具都要求程序员通过向源程序中添加特殊形式的注释来描述程序的前置条件、后置条件以及循环不变量。

这无疑增加了程序员的工作量,也导致该方法难以广泛应用于大型应用程序。

使用定理证明的代表性软件分析工具为ESC[27]和ESC/Java[28]。

3)约束求解(ConstraintSolving)。

基于约束求解的程序分析技术将程序代码转化为一组约束,并通过约束求解器获得满足约束的解[48]。

早期的研究表明,面向路径的测试数据生成可以很好地归结为约束求解问题。

后来,学者们又发现程序中不变式的分析也可以归结为约束求解问题。

最新的研究表明,许多其的程序分析问题也可以归结为约束求解问题:

由于从程序获得的约束通常采用一阶或二阶的形式表示,可以进一步将其转换成约束求解器可处理的形式。

支持约束求解的代表性软件分析工具为SAT/SMTSolver[49]。

4)抽象解释(AbstractInterpretation)。

程序的抽象解释就是使用抽象对象域上的计算逼近程序指称的对象域上的计算,使得程序抽象执行的结果能够反映出程序真实运行的部分信息。

抽象解释本质上是在计算效率和计算精度之间取得均衡,以损失计算精度求得计算可行性,再通过迭代计算增强计算精度的一种抽象逼近方法。

通过不断迭代,抽象解释最终为程序建立一个抽象模型。

如果抽象模型中不存在错误,就证明其对应的源程序中也不存在错误。

具有抽象解释分析功能的代表性分析工具为Proverif[29]和ASTREE[30]。

形式化支持的分析技术在分析软件的某个性质时,需要首先对该性质进行形式化的描述,然后将这个描述与软件制品一起作为输入提供给分析工具。

其中,模型检验首先被用于对软件的模型进行分析,后来有研究人员通过从代码中提取模型,然后将模型检验技术应用于代码分析。

定理证明主要对静态代码比较适用。

约束求解多用于输入数据的生成。

基于抽象解释理论的形式化方法是对大规模软件、硬件系统进行自动化分析与验证的有效途径之一,已经被广泛地应用于大型软件与硬件系统的验证研究中。

2.2.3.指向分析

1)别名分析(AliasAnalysis)。

别名分析主要用于确定程序中不同的内存引用(reference)是否指向内存的相同区域。

在编译过程中,这可以帮助判断一个语句将影响什么变量。

例如,考虑如下的代码:

...;

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;

这样就可以对代码进行等价优化。

别名分析又可以分为基于类型的分析与基于流的分析。

前者主要用于类型安全(typesafe)的语言,后者则主要用于含有大量引用与类型转换的语言[3]。

2)指针分析(PointerAnalysis)。

指针分析试图确定一个指针到底指向哪些对象或者存储位置,尤其是,在某个语句处是否可能为空。

由于受到可判定性问题的限制,加上分析过程中时间、存储等的限制,多数的指针分析方法都在分析过程中进行“近似”或者“简化”,并导致分析结果精确性不够。

实际上,上面的别名分析与下面的形态分析、逃逸分析都与指针分析密切相关。

3)形态分析(ShapeAnalysis)。

形态分析主要用于发现或者验证程序中动态分配结构的性质。

对于一个具体的程序,形态分析将为其构造一个形态图(shapegraph),用于列出每个指针可能指向的目标,以及目标之间的关系。

形态分析可以认为是指针分析的一种,但比一般的指针分析精确:

形态分析可以确定一个小一些但是更精确的指向集合。

例如在Java程序中,可用来保证一个排序算法正确地对列表进行了排序;

在C程序中,可以用来分析一个内存是否被正确地释放。

尽管形态分析很强大,但往往需要花费较多的时间[4]。

4)逃逸分析(EscapeAnalysis)。

逃逸分析计算变量的可达边界。

对于一个方法m中的一个变量,如果变量是在调用方法m时创建的,但在m的生命周期之外可以获得该变量,我们就说这个变量逃逸了方法m。

类似地,一个变量逃逸了一个线程t,如果在t之外的一个点能通过一个引用访问到该变量。

逃逸分析传统上被用于查找一些变量,它们只存在于为它们分配内存的方法或线程的生命周期内:

前者允许变量在运行时的栈(stack)上,而不是堆(heap)上分配内存,这样就可以降低堆的碎片与垃圾回收负载。

后者被用于进行优化,以避免高成本的异步操作。

逃逸分析检查引用的赋值与使用(assignmentsanduses)以计算每个变量的逃逸状态。

每个变量可以被赋予3个可能逃逸状态中的一个:

全局逃逸、参数逃逸或者捕获。

当一个变量是全局可达的(例如被赋值给了一个静态域)时,这个变量被标记为全局逃逸;

如果变量是通过参数或者被返回给调用者方法,它被标记为参数逃逸;

一个不逃逸的变量被标记为捕获。

逃逸分析主要用于效率分析[22]。

与基本分析技术相比,指向分析通常与应用程序的某个特定性质密切相关。

这类分析一般是以基本分析(尤其是数据流分析)为主要分析框架,为了提高分析精度而提出的技术,且分别结合了编程语言的不同特点。

别名分析利用的变量引用、形态分析利用的指针等等。

这也导致了这些分析技术分别适合由不同编程语言实现的程序。

另外,这些分析技术尽管可以自动进行,但在分析之前通常需要较多的人工介入,例如,指定对哪些变量进行分析、提供对什么性质进行分析等等。

2.2.4.其它辅助分析

1)符号执行(SymbolicExecution)。

符号执行通过使用抽象的符号表示程序中变量的值来模拟程序的执行[14,31]。

其特点在于通过跟踪被模拟的各条执行路径上变量的实际取值,把分析工作局限在实际可达的路径上,从而使得到的结果更贴近程序实际执行情况,并为程序员提供更为准确的与检出的缺陷相关的上下文信息。

但是由于需要穷举各条可能执行的路径,该技术需要处理的工作量随着程序规模的增大而呈指数级别增长。

虽然符号执行方法可以被应用于大型程序的分析,其分析结果的可靠性仍依赖于所允许的分析时间和对路径及其数目的选择等方面。

2)切片分析(SlicingAnalysis)。

切片分析用于从源程序中抽取对程序中兴趣点上的特定变量有影响的语句和谓词,组成新的程序(称作切片),然后通过分析切片来分析源程序的行为[42]。

计算程序切片的方法主要有两种:

根据数据流方程计算和根据依赖图关系计算。

切片分析技术已被广泛应用于程序分析、理解、调试、测试、软件维护等过程[38,39]。

3)结构分析(StructureAnalysis)。

结构分析的目标是获得程序的调用关系图(CallGraph),以展示程序中各个函数之间的调用关系。

把程序中每个函数当作一个节点,再分析每个函数调用了哪些其他函数,并在存在调用关系的函数间建立一条边,就可以得到调用关系图。

调用关系图通常用于辅助开发人员理解程序。

对于面向对象程序,程序的结构分析还包括从程序中获取类图(classdiagram)等。

除了一些编译器支持结构分析外,目前软件开发过程中的一些工具,例如IBMRationalRose等也能够对以开发出的代码进行结构分析。

4)克隆分析(CloneAnalysis)。

代码克隆(Codeclone)是指软件开发中由于复制、粘贴引起的重复代码现象。

研究指出,一般商业软件中存在5%至20%的重复代码[1]。

由于克隆代码的普遍性以及克隆代码对代码质量的重要影响,代码克隆相关研究是静态代码分析领域近年来一个十分活跃的研究分支。

主要研究内容包括:

克隆代码检测、由代码克隆引起的代码缺陷诊断、通过代码重构来减少代码克隆、克隆代码跟踪、基于代码克隆的源代码演化分析等等。

代码克隆分析有十分丰富的实际应用价值,比如缺陷诊断、重构、代码理解、源代码演化分析和代码剽窃检查等等。

克隆代码可以分为如下四类:

1)除空格、回车以及注解之外完全相同的代码片段;

2)除空格、回车、注解以及变量名及常量值替换外,语法结构完全相同的代码片段;

3)除空格、回车、注解以及变量名及常量值替换外,语法结构基本相同,但含有少量语句的增加、删除或修改的代码片段;

4)两段或多段代码具有相同或相似的功能,或者说相似的输入、输出条件。

其中,最后一类比较特殊,是语义(功能)相似性,其它三类都是文本相似性[5-17]。

2.3.动态分析

动态分析是通过运行具体程序并获取程序的输出或者内部状态等信息来验证或者发现软件性质的过程。

与静态分析相比,动态分析具有如下几方面特点:

1)需要运行系统,因此通常要向系统输入具体的数据;

2)由于有具体的数据,因此分析结果更精确,但同时只是对于特定输入情况精确,对于其它输入的情况则不能保证。

本文从运行信息的获得途径与获得时机两个方面对动态分析进行介绍。

在信息获得的时机上,又根据软件是否已经上线投入使用将软件的动态分析划分为两大类:

离线动态测试/验证(OfflineDynamicTesting/Verification)与在线监测(OnlineMonitoring)。

所谓离线动态测试/验证,是指在系统还没有正式上线时对软件进行运行、分析,分析过程中可以随意输入数据,并尽量模拟实际用户的操作。

所谓在线监测,是指在系统已经上线后对软件系统进行分析,监测过程中一般不能随意输入数据,所有数据都是真实的。

离线动态测试/验证、在线监测与运行信息获取之间的基本关系见图3。

图3动态分析涉及的主要技术

2.3.1.运行信息的获取途径

1)从程序的正常输出中获取信息

每个程序在运行过程中都会产生许多输出信息。

有些输出是程序运行中间或者结束时输出的正常结果,有些是一些提示信息,还有一些是日志信息。

通过将最终得到的实际输出结果与事先设定的期望输出结果进行对比、分析,就可以得到关于软件的有价值信息。

2)通过插装代码获取信息

仅仅通过观察程序的正常输出对于了解软件的运行信息往往是不够的。

例如,软件运行过程中内部变量的状态信息、某个特定类型的实例信息、模块之间的交互信息等等。

这些信息对于发现缺陷,以及定位缺陷特别重要。

获得这些内部信息的自然方式是在软件中插装监测代码(MonitoringCode),通过这些监测代码就可以获得相应的信息。

主要的监测代码插装方法可以分为如下三类:

●源码插装。

这是最自然的插装方式,即在编写应用系统时,在需要监测的地方直接加上监测代码,例如,增加输出信息语句、增加日志语句等等。

AOP(AspectOrientedProgramming)技术出现之后,人们发现AOP可以被很好地用于代码插装,以有效地分离系统的业务逻辑与监测逻辑[43]。

●静态目标码插装。

近年来字节码插装技术在Java社区中十分流行。

字节码插装可以在静态直接更改中间代码文件(例如Java的.cl

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 人文社科 > 军事政治

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1