这是我在实习期间对程序静态分析写的报告,以普及大家对程序静态分析
程序静态分析简述
静态程序分析:
程序静态分析(Program Static Analysis)是指在不运行代码的方式下,通过词法分析、语法分析、控制流分析等技术对程序代码进行扫描,验证代码是否满足规范性、安全性、可靠性、可维护性等指标的一种代码分析技术。
它可以帮助软件开发人员、质量保证人员查找代码中存在的、程序的静态分析被用于程序测试和正确性验证。
静态分析的功能:
结合词法分析和语法分析给出的信息,静态分析工具可以检查所测试程序违反编程标准的错误,如模块大小、模块结构、注释的约定和各种类型源语句的出现次数等;
结合语义分析,它还能完成对一些特性的统计,如函数过程引用情况、标识符使用的交叉索引、标识符在每个语句中使用的情况、任何输入输出语句都执行不到的语句代码段、全局变量和局部变量的各种统计等。
一个简单的静态分析示例:
静态分析的特性:
优点:静态分析提供程序所有可能执行情况都有效的信息
缺点:
1、因为静态分析经常是基于近似的分析方法,其提供的信息并不总是精确的。
2、对于程序的某些性质(比如指针运算、动态存储分配等相关的性质),用静态分析难以奏效。
与动态分析测试比较:
(1)不实际执行程序。动态分析是通过在真实或模拟环境中执行程序进行分析的方法,多用于性能测试、功能测试、内存泄漏测试等方面。与之相反,静态分析不运行代码只是通过对代码的静态扫描对程序进行分析。
(2)执行速度快、效率高。目前成熟的代码静态分析工具每秒可扫描上万行代码,相对于动态分析,具有检测速度快、效率高的特点。
(3)误报率较高。代码静态分析是通过对程序扫描找到匹配某种规则模式的代码从而发现代码中存在的问题,例如可以定位strcpy()这样可能存在漏洞的函数,这样有时会造成将一些正确代码定位为缺陷的问题,因此静态分析有时存在误报率较高的缺陷,可结合动态分析方法进行修正。
程序静态分析的目标不是证明程序完全正确,而是作为动态测试的补充,在程序运行前尽可能多地发现其中隐含的错误,提高程序的可靠性和健壮性,但并不能完全代替动态测试。
一个简单的静态分析示例:
346288 * -8782332的结果是正还是负?-439232 * -2323347呢?
符号的规则:+ * + = +; + * - = -; -* - = +
利用符号规则可以在不实际计算的情况下可以静态分析出结果的符号。
静态分析使用场合:
适合的场合:
不适合的场合:
静态分析潜在收益是很高的:
缺陷去除成本:在软件开发生命周期中,越是后来发现的缺陷,其去除成本为指数上涨,所以缺陷越早发现越好。静态分析是所有测试中可以最先发现缺陷的方法,极大的降低了软件的修复成本。
对软件经理,静态分析有助于:
减少部署后存在错误的风险,去除此时发现的错误是非常昂贵的;
缩短产品上市时间;
减少代码审查和测试的成本和时间:
自动化(部分)审查,没有或多个有限的人工检查;
消除明显的错误以便改善测试的速度和重点;
提高代码质量(坚持编码标准);
实现更高的覆盖率(更多的代码检查):与测试覆盖率相关但并不完全一样,因为重点不同。
对软件开发人员,静态分析有助于:
提早找到/阻止错误(在错误变得很难处理之前):
工具可用作开发周期的一部分,像编译器;
更直接,明显的反馈。
2)发现/阻止“难以测试”的错误:例如,静态分析善于检测潜在的内存泄露和缓冲区溢出,从而使开发人员更高效,花费更少的时间调试。
二、常用静态分析技术(技术之间是相互关联,有些是层层递进的):
(1) 词法分析:从左至右一个字符一个字符的读入源程序,对构成源程序的字符流进行扫描,通过使用正则表达式匹配方法将源代码转换为等价的符号(Token) 流,生成相关符号列表,Lex为常用分析工具。
(2) 语法分析:判断源程序结构上是否正确,通过使用上下文无关语法将相关符号整理为语法树,Yacc为常用工具。
(3) 抽象语法树分析:将程序组织成树形结构,树中相关节点代表了程序中的相关代码,目前已有javacc等抽象语法树生成工具。
(4) 语义分析:对结构上正确的源程序进行上下文有关性质的审查。
(5) 控制流分析:生成有向控制流图(控制流图(CFG)是编译器内部用有向图表示一个程序过程的一种抽象数据结构,图中的节点表示一个程序基本快,基本块是没有任何跳转的顺序语句代码,图中的边表示代码中的跳转,它是有向边,起点和终点都是基本块),用节点表示基本代码块,节点间的有向边代表控制流路径,反向边表示可能存在的循环;还可生成函数调用关系图,表示函数间的嵌套关系。
(6) 数据流分析:对控制流图进行遍历,记录变量的初始化点和引用点,保存相关数据信息。
通过静态模拟应用程序的执行路径,帮助用户找到运行时才能暴露的一些严重错误,如资源泄漏、空指针异常、SQL注入以及其它的安全性漏洞等潜在的运行时错误。这些严重的错误往往通过一般的静态规则扫描难以查找,此时需要数据流分析技术,也叫BugDetective。
(7) 污点分析:基于数据流图判断源代码中哪些变量可能受到***,是验证程序输入、识别代码表达缺陷的关键。(污点:所有来自不可靠数据源的数据:用户输入,网络等)
(8)程序分片:
程序切片就是指将一个程序中用户感兴趣的代码都抽取出来组成一个新的程序,这个新的程序就是源程序的切片,根据切片规则的不同,生成的切片也各不相同。
程序分片的应用:
程序分片技术(program slicing)在软件维护、程序调试、测试、代码理解以及逆向工程等方面有许多应用。
程序调试是程序切片的第一个应用:在查找程序中的缺陷时,常常要忽略掉那些对暴露出缺陷的语句没有影响的语句。程序切片就是对程序进行这种提取,是程序员集中检查那些有潜在影响的语句。
程序分片可以将程序分解成多个关于不同测试用例的小程序,从而减少了回归测试的时间,因为只有一部分测试用例需要反复测试。
程序分片还可以区分同一程序的不同版本之间的区别。
软件维护时,如果程序必须作改变,前向分片可以用来识别出各种改变可能会产生的影响。
程序切片的分类:
后向切片:关心的是所有受分片标准变量的值影响的程序部分。
前向切片:分片集合中包含了程序中所有能够影响变量在分片标准的值的部分。
切片不是静态分片就是动态切片产生的:一个静态程序切片是象征性计算,即不需要考虑具体的数据值;动态程序切片是根据特定的数据值计算的。
程序分片示例:
源程序: 有关R的程序切片:
read(X); read(X);
read(Y); read(Y);
Q := 0; R := X;
R := X; while R >= Y do
while R >= Y do begin
begin R := R - Y;
R := R - Y; end;
Q := Q + 1 print(R);
end;
print(Q);
print(R);
(9)程序静态分析的形式化方法---------方法和理论:
程序分析中的形式化方法一般指利用纯粹严格的数学方法对软件、硬件进行分析的理论及技术。这些数学方法包括符号语义、公理语义、操作语义和抽象解释等,对应的方法和理论便是:符号执行、定理证明、类型推导、抽象解释、基于规则的检查和模型检查。
以上所述的方法并不完全相互独立,一个静态分析工具常常需要使用多种方法以取得最佳效果。本节着重描述基于路径分析方法的符号执行技术。
符号执行:
基本思想:
用抽象的符号表示程序中变的值, 来模拟程序的执行。该方法很好地克服了在静态测试时不能确定程序中变量的值的问题。符号执行常常在对路径敏感的程序分析中使用。
具体过程为:使用符号值,而不是实际数据,作为输入;将程序变量的值表示为符号表达式;程序计算的输出表达为输入符号值的函数。
具体实现:
符号执行要记录执行的状态,包括:程序变量的符号值、路径条件(PC:Path Condition)、程序标记(后面执行什么);其中路径条件非常重要,积累了路径的约束条件;符号执行树刻画程序符号执行过程中的执行路径。
符号执行分为过程内分析和过程间分析(又称全局分析)。过程内分析是指只对单个过程的代码进行分析,全局分析指对整个软件代码进行上下文敏感的分析。所谓上下文敏感分析是指在当前函数入口点要考虑当前的函数间调用信息和环境信息等。程序的全局分析是在过程内分析的基础上进行的,但过程内分析中包含了函数调用时就引入了过程间分析,因此两者是相对独立又相互依赖的关系。
过程内分析流程如图2所示。首先,对待分析的单个过程代码对象构建控制流图(Control Flow Graph,CFG)。在CFG上从入口节点开始模拟执行,在遇到分支节点时,使用约束求解器判定哪条分支可行,并根据预先设计的路径调度策略实现对该过程所有路径的遍历分析,最后输出每条可执行路径的分析结果。其中,约束求解是数学上的判定过程,形象地说是对一系列的约束方程进行求解。
如果要进行源代码的安全性检测,则需要在过程内分析时,根据具体的安全知识库来添加安全约束。例如,如果要添加缓冲区溢出的安全约束,则在执行时遇到对内存进行操作的语句时,就要对该语句所操作的内存对象的边界添加安全约束。以上面的方式来进行安全约束的添加,并且每次在添加之后就使用约束求解器对所有的安全约束进行求解,以判定当前是否可能潜在一个安全问题。
图2 过程内分析原理流程图
程序全局分析流程如图3所示。首先,为整个程序代码构建函数调用图(Call Graph,CG),在函数调用图中,节点表示函数,边表示函数间的调用关系。根据预设的全局分析调度策略,对CG中每个节点(对应一个函数)进行过程内分析,最终给出CG每种可行的调用序列的分析结果。
图3 全局分析原理流程图
符号执行示例------通过一个示例代码来具体说明符号执行的分析原理
如图4所示的步骤:
1、对一段简单的代码函数test构建控制流程图CFG,可以看出该CFG只包含了2条路径;
2、以符号值为输入,模拟执行代码,在遇到分支语句时,使用约束求解器判定这两条路径的可行性,本示例使用的是“深度优先”的路径调度策略,详细的分析过程见图4。
3、在对两条路径模拟执行并收集路径约束后,使用约束求解器可以求解出分别触发这两条路径的两个测试例输入及对应的返回值:
测试例输入为“i=11”时,返回值“10”;
测试例输入为“i=10”时,返回值为“9”。
图4 符号执行原理示例
通过这个简单的例子可以看出使用符号执行的方法进行分析可以达到很高的路径覆盖率,并且结合约束求解器还可以实现测试例的自动生成。
基于规则的检查:
基本思想:
在面向不同应用的程序中,常常隐含着各种不同的编程规则。
例如:在多线程程序中要求在使用某一共享变量时遵守“使用前先枷锁,使用后解锁”的规则;操作系统的内核处理程序在屏蔽中断进行原子操作后必须打开中断屏蔽等。
因此从经验的角度出发,人们提出了基于规则对程序进行分析的方法。
采用基于规则进行分析的系统的结构:
1、由一个规则处理器处理规则,将其转换为分析器能够接受的内部表示,然后再将其应用于程序的分析。
2、通过自动化逐行扫描程序代码,从中查找是否存在与事先构建好的规则模式相匹配的代码,如果发现相匹配的代码,则报告相应错误。
基于规则的分析工具是将规则作为判定程序是否正确的标准。更多的分析工具是根据程序不同的上下文嵌入不同的规约。
示例:针对源程序进行程序静态的算法如下:
图5 一个简单的程序静态分析算法
输入: 待分析的源程序
输出: 故障语句表CON,变量信息表(十字链表),运算信息表(十字链表)
Step1 首先针对不同的故障类型,制定不同的故障判定规则,将其作为程序静态分析的插装函数。
Step2 扫描源程序中的每一行语句,并完成:凡是不满足判定规则的语句,则将其加入到故障语句表中,并将违反判定规则的运算信息加入到运算信息表;对于那些变量声明语句和变量赋值语句,将这些变量信息加入到变量信息表;若源程序扫描没有结束,则转Step2;否则转Step3。
Step3 输出故障语句表CON,变量信息表和运算信息表。
实例研究:
下面以非法计算故障为例,以说明上述算法有效,假设待分析的源程序如下:
#include
int main( )
{
1、 int a=20,b=30;
2、 int c=30;
3、 float result;
4、 a=a-5;
5、 c=c*2-60;
6、 result=sqrt(c-4)%(sqrt(a-15)*sqrt(a-6))/(c-10);
7、 cout<
}
程序静态分析过程:
(1)指定判定规则,可以将C/C++语言中的非法计算库函数和操作符取出作为非法计算的探测函数;
(2)从程序开始处进行查找,此时未发现有出现在表1 中的操作符和库函数,发现有3 条变量声明语句,接下来将所声明的变量加入到如图6 所示的变量信息表中,应为a, b, c 和result;
(3)由于1~2 句是赋值语句,则将3 个变量赋值,并加入到相应的横向链表中去;
(4)程序中的4~5句是运算,但是没有出现非法计算故障,所以跳过,不做处理;
(5)第6 句中出现了导致非法计算故障出现的操作符和库函数;
(6)根据非法计算故障的特征,发现在这段程序中,总共存在4 个可能导致非法计算故障的地方,分别是(6,f1),(6,O1),(6,f2),(6,f3),(6,O2)。其中,f1 代表的是sqrt()函数,它的参数约束是c-4≥0;O1 代表的是“%”操作符,它的参数约束是sqrt(a-15)∙sqrt(a-6)!=0;f2 代表的是sqrt()函数,它的参数约束是a-15≥0;f3 代表的是sqrt()函数,它的参数约束是a-
6≥0;O2 代表的是“/”操作符,它的参数约束是c-10!=0。
表1 C/C++非法计算操作符和库函数
编号 操作符或函数名 限制
1 double asin(double x) 1≤x≤1
2 double acos(double x) 1≤x≤1
3 double atan2(double y, double x) x ≠ 0
4 div_t div(int number, int denom) denom≠ 0
5 ldiv_t ldiv(long int number, long int denom) denom ≠ 0
6 double log(double x) x > 0
7 double log10(double x) x > 0
8 logb(double x) x > 0
9 double pow(double x, double y) x=0 时y≥ 0;x<0 时y 为整数
10 double fmod(double x, double y) y ≠ 0
11 double sqrt(double x) x≥0
12 double _in(int n, double x) n > 0
13 double _y0(double x) x > 0
14 double _y1(double x) x > 0
15 Double _yn(int n, double x) n > 0, x > 0
16 /, %, /=, %= 右边操作数不为0
图6 程序静态分析的变量信息表:
变量信息表的竖向链表记录程序中共有几个变量,而横向链表则记录每一个变量在程序中的变化过程,变量最后变化的语句位置放在最前头,因为有一个就近检查的原则,横向链表每个结点的数据结构中第1 个元素代表变量值,第2 个元素代表该变量出现的位置。
四、静态分析工具:
1、一个LINT静态分析工具示例:
> cat lint_ex.c
#include
printarray (Anarray)
int Anarray;
{ printf(“%d”,Anarray); }
main () {
int Anarray[5]; int i; char c;
printarray (Anarray, i, c);
printarray (Anarray) ;
}
> cc lint_ex.c
> lint lint_ex.c
lint_ex.c(10): warning: c may be used before set
lint_ex.c(10): warning: i may be used before set
printarray: variable # of args. lint_ex.c(4) :: lint_ex.c(10)
printarray, arg. 1 used inconsistently lint_ex.c(4) :: lint_ex.c(10)
printarray, arg. 1 used inconsistently lint_ex.c(4) :: lint_ex.c(11)
printf returns value which is always ignored
2、其它静态分析工具:
Java开源工具,如:Findbugs,QJPro,PMD
大部分与IDE集成,如Eclipse
也有其他语言的静态分析工具,如:FxCop分析.NET,Perl::Critic
许多商业工具:经常是检查“工业”语言,如C, C++, Ada;通常是用规则来检测各种编码标准(如MISRA C标准)的一致性;
3、组成:
静态分析工具一般由4部分组成:语言程序的预处理器、数据库、错误分析器和报告生成器。
一般的静态分析工具的系统流程图(图7):
图7 一般的静态分析工具的系统流程图
其中符号表通常用于记录源程序中各种名字的属性和特征,供各模块查询分析。
静态分析模块:静态分析模块根据词法分析和语法分析的结果,进行更深层次的挖掘,以便在执行动态测试以前尽可能多地发现程序中的缺陷,降低测试成本和减少测试工作量。静态分析模块可以根据实际应用的需要包含不同的功能,一般主要进行如下几方面的工作:
(1)程序复杂度度量; (2)不可达路径的识别:识别不可达路径可以减少设计和运行测试用例的工作量,并且有些不可达路径可能是有逻辑错误引起的;(3)模块间的耦合性和模块内聚性度量:设计原理要求尽量设计高内聚低耦合的模块,这样有利于提高软件的可理解性、可测试性和可维护性;(4)自动设计测试用例:通过对可达路径的识别,针对这些路径设计满足一定覆盖标准的测试用例,降低测试成本。
示例:利用静态分析模块使用信息流方法进行简单的语句分析,针对不同的语句内容作不同形式的分析。其数据流图如下(图8):
图8 语句分析数据流图
不正确性的种类:1)错误未被检测出来(假阴性);2)将非错误的情况报告为错误(误
实践中的静态分析工具:
详细的分析需要大量的处理能力,然而计算机的处理能力每18个月翻一番,并且工具快速改善(软件安全分析是一个大的驱动程序)
不是每个人都对产生的大量数据满意:实践中会有很多假阳性(50%);大量对琐碎问题的投诉使引进十分困难;需要对报告的警告区分优先次序。
有时并不容易添加新的规则来进行检查并:错误模式可能是模糊的并依赖于控制流和数据流模式(不只是简单的语法匹配)
做出更好的静态分析工具:
目前已总结了可能有助于改善现有工具的技术研究,即不用建立一个新的静态分析工具,而是建立一个可以使用于所有工具的前置或后置器。
两种方法的调查:1、旨在提高检验结果的优先次序的合理性;2、旨在减少警告名单的“噪声”。
一、分析结果优先次序排列:
得到了来自静态分析工具的一个巨大的错误名单后,从何处开始分析结果?
方法:1、确定执行违规地点的可能性,并用它来优化排列列表中的项:
对于一个给定的程序P和给定的位置V,执行的可能性EV是V在P的任意一次运行中被执行的概率;
静态分析(通过对程序的控制流结构的分析)可以近似估计可能性,如在 in-then-else的每个分支中的语句被执行的可能性是语句本身的一半。
2、开发“更深的”静态分析算法 , 使用详细的数据流分析(值范围传播),以更好地估计是哪一个分支
3、基于执行可能性的优先次序导致开发者那些在一个实际运行过程中被出发的高度变化的违规: 旨在先修复最具影响力的问题;
二、通过学习过去进行结果的过滤:
1、按某些规则检查出的错误真正的显示故障码?我们是否能基于相关性使用这些规则来进行优化或过滤?
2、使用新推出的编码标准检查分析三个相关的软件项目的历史;
3、计算规则的真阳性率:一个“真阳性”是在版本n中的违规,其被正确预测出现故障的线路,即在以后大于n的版本中的错误修复部分
4、使用这些值来确定“显规则集” :那些违规预测结果所对应的规则由于一个随机猜测
五、总结:
静态分析功能强大可以发现程序的大部分问题,以及动态分析难以发现的问题,是程序分析不可缺少的部分。
静态分析可以提早发现程序中的问题,以便及时修复,减少了很大的成本。
静态分析理论知识完备,静态分析自动化工具已很完善,要大力推广其应用。
同时静态分析也存在一些局限性,需要进行一些改进,不断完善其功能,更好的为程序分析服务。
与具体应用“无关”的缺陷:
词法或者语法上的错误;
共性特性(死锁、空指针、内存泄露、数组越界);
公共库用法(顺序、参数、接口实现,容错,安全)。
2、与具体应用“相关”的缺陷:
类型定义(操作格式,不含其它信息(信息隐藏));
类型约束(调用的顺序、参数值,接口,接口实现);
需求相关(正确)。
静态分析程序不需要执行所测试的程序,它扫描所测试程序的正文,对程序的数据流和控制流进行分析。然后送出测试报告。通常,它具有以下几类功能:
(1)对模块中的所有变量,检查其是否都已定义,是否引用了未定义的变量,是否有已赋过值但从未使用的变量。实现方法是建立变量的交叉引用表。
(2)检查模块接口的一致性。主要检查子程序调用时形式参数与实际参数的个数、类型是否一致,输入输出参数的定义/使用是否匹配、数组参数的维数、下标变量的范围是否正确,各子程序中使用的公用区(或外部变量、全局变量)定义是否一致等等。
(3)检查在逻辑上可能有错误的结构以及多余的不可达的程序段。如交叉转入转出的循环语句,为循环控制变量赋值,存取其他模块的局部数据等。
(4)建立“变量/语句交叉引用表”、“子程序调用顺序表”、“公用区/子程序交叉引用表”等。利用它们找出变量错误可能影响到哪些语句,影响到哪些其他变量等。
(5)检查所测程序违反编程标准的错误。例如,模块大小、模块结构、注释的约定、某些语句形式的使用,以及文档编制的约定等。
(6)对一些静态特性的统计功能:各种类型源语句的出现次数,标识符使用的交叉索引,标识符在每个语句中使用的情况,函数与过程引用情况,任何输入数据都执行不到的孤立代码段,未经定义的或未曾使用过的变量,违背编码标准之处,公共变量与局部变量的各种统计。
找到动态内存错误¶
写程序时, 您通常不知道程序运行时需要多少内存。比如说,运行时从一个文件逐行读可能需要任意长度的内存。C/C++ 程序使用 molloc, free 及其变种在程序运行时动态分配内存。动态内存的规则有如下几点:
分配(调用 malloc)的数目必须和释放的数目严格一致(调用free)。
对分配内存的读写必须在获得的区间里,而不是在它的范围之外。
分配的内存在它被分配前或者被释放后不能被使用。
因为动态内存分配和释放在运行时间进行,静态程序分析很少会找到违规代码。而内存检查工具在运行程序时,收集数据来确定这里有无违规现象。一个工具可能找到的违规如下:
在分配内存前读
在分配内存前写
在分配内存开始位置前读
在分配内存开始位置前写
在分配内存结束位置后读
在分配内存结束位置后写
在释放内存后读
在释放内存后写
内存释放失败
两次释放同样的内存
释放的内存没有被分配
要求警告对分配 0 字节内存进行警告也往往很有用,因为这常常意味着程序逻辑错误。表格 A.1 显示了不同工具的诊断能力。不幸的是,没有一个工具能够独立诊断所有的错误。同样,也没有一个工具会察觉声明内存分配前的读写,但是使用它会引起一个段错误。释放相同的内存两次也会引起一个段错误。这些工具只诊断程序运行时一定会发生的错误。如果您使用可能引起没有内存被分配的输入值运行一个程序,工具将显示没有内存错误。为了测试整个程序,您必须使用不同的输入值运行程序来确保每一个可能路径都被程序覆盖到。同样您一次只使用一个工具,所以您不得不使用工具重复测试若干次来获得更好的错误检查。
http://hi.baidu.com/anglecloudy/blog/item/843d16135b3f62045baf5328.html
静态分析检查优点:覆盖率高、能够在早期发现错误和易于使用的优点,因而被大量应用于软件差错中。
软件测试是常用的差错技术,具有极高的精确度,因为它所发现的错误通常就是程序执行时错产生的。它的扩展性也很好,同时也具有比较高的自动化程度。然而,覆盖率是软件测试最大的弱点。很多在边界条件下才会发生的错误不能再软件测试过程中发现。
静态检查所采用的则是一种完全不同的思路,其源头可以上溯到Lint,它把可扩展性放在极为重要的位置,同时牺牲了覆盖率和精确度。程序分析是一种周昂耀的静态检查技术。这类工具一般不保真能够发现所有的错误,也不保证所有爆出的错误都是真正的错误。静态检查工具的设计者把易用性看得十分重要,因而这些工具的自动化程度都非常高,用户通常并不需要写任何的规约。他们所能够检查的错误类型比较有限,通常是一些“浅”层次的错误,而不能检查那些复杂的时序逻辑公式所表达的“深”层次错误。因为其易用性和较好的可扩展性,这些工具在实践中得到了广泛的采用。不幸的是,它们的可扩展性来源于其对程序状态的近似与合并,造成了一个重要的缺点:误报率比较高。
动态工具通过收集并分析程序运行时信息来检测错误。主要优点:精确度高,然而其缺点也很明显。首先,为论文获取程序执行时的信息,动态工具常常给被测程序增加了很多的时空开销,从而影响了程序的性能。
例如,经动态工具Purify处理之后的程序,其运行时间增至原来的2到5倍,所消耗的内存超过原来的1.5倍。覆盖率是动态工具的另一个缺点。动态工具常使用插桩等技术来处理被测试程序,而这会带来不兼容的问题。一些对时间空间要求比较严格的程序也不适合使用动态工具来检测。
静态工具的优点:
能够较早发现软件错误。程序中的错误越早被发现,修复它的代价就越低,静态工具可以在代码完成之后就立即对其进行检查。很多模块化的静态分析技术甚至不要求完整的程序代码,而能够直接工作再代码段上,因为能够在代码完成之前就发现错误。
覆盖率较好。静态工具可以系统地、完整地探测所有可能的执行场景,具有比动态工具高得多的覆盖率。另外,静态检查并不需要测试用例的生成,在寻找那些发生在不易察觉的路径上的错误方面,比动态工具更有效果。
低开销。静态工具不增加程序运行时的额外开销,能够被用来检查一些对时空和其它资源要求比较严格的系统。通常静态检查的自动化程度比较高,而不要额外的人力投入。
这是一个用类似metal语言写的一个简化的NULL Checker(Null指针检查器),也即一个有限的状态机(FSM)
其中的状态:
initial state START //其中的状态 START是分析开始的地方
state ALLOCATED
state CHECKED
error state WRONG //WRONG代表有错误出现
pattern allocation “%OR{ //各个匹配模式,用来匹配变量,表达式或参数列表
%X = malloc(% );
%X = dev alloc skb(% );
%X = kmalloc(% ,% );
%X = scsi register(% ,% ) }”;
pattern dereference “%OR{
*%X = % ;
% = %X->% ;
constant c memset(%X,% ,% );
% = constant c memset(%X,% ,% ) }”;
pattern check “%OR{%X == 0; %X == NULL}”;
transition START ALLOCATED allocation; //程序转换,执行控制
transition ALLOCATED WRONG dereference; //每一个transition包括一个源
transition ALLOCATED CHECKED check; //状态,一个目标状态和一个模式
transition CHECKED START dereference; //只有当一个FSM在一个源状态,并
接下来的语句与模式匹配,状态将变为目标状态
待分析的程序:
1:char *foo(int size)
2:{
3: char *result;
4:
5: if(size > 0)
6: result = malloc(size);
7: if(size == 1)
8: return NULL;
9: *result = 0;
10: return result;
11: }
分析:函数的功能是分配内存单元并进行初始化。有三处错误:
内存泄露:5,6,7,8行;无效的指针引用:5,7,9,10行;空指针引用:5,6,7,9,10.
用NULL Checker静态分析程序,其控制流路径如图1,
对于5,6,7,9,10行,由于模式 allocation“%X =malloc(% );”与第6行的语句匹配,根据“transition START ALLOCATED allocation”状态转换为ALLOCATED。
接下来的语句第9行,指针result的应用与“*%X= % ;”.应用模式匹配,状态转换为WRONG,报告错误(有潜在的空指针引用)。