写在前面的话
本渣有幸成为南京大学软件学院研究生,在前往仙林校区蹭课的时候偶然发现了这门宝藏课程,听了以后感觉深有收获,但又因为课程难度较大,国庆假期归来发现遗忘较多,因此开了一坑来记录自己对每节课知识点的理解。也由于这是本人第一次开坑写博客,结构内容自有诸多不合理之处,希望有问题的地方大家可以指出。
彩蛋:这门课程在课表上名叫软件分析,但是实际上应该叫做静态程序分析,老师说这样起名主要是怕静态程序分析这个名字太高大上,吓到学生导致没人敢选。
系列文章目录
1.静态程序分析(Static Program Analysis)介绍
2.中间表示(Intermediate Representation)
3.数据流分析(Data Flow Analysis) (上):可达性分析(Reaching Definitions)
4.数据流分析Data Flow Analysis) (下):存活变量分析(Live Variables Analysis)及可用表达式分析(Available Expressions Analysis)
5.数据流分析基础(Data Flow Analysis-Foundations)
6.过程间分析(Interprocedural Analysis)
7.指针分析(Pointer Analysis)入门
8.指针分析基础知识(Pointer Analysis Foundations)
一、PL和Static Analysis
PL即Programming Languages,程序语言的统称。
如图所示,分为三个主题,理论、环境、应用。
理论方面非常基础,基本都是语言规则等基础的东西;
环境部分主要是编译器相关;
而应用部分主要是程序分析、验证、合成,本系列文章主要讲的就是其中的Program analysis部分。
背景与挑战:最近十多年来,语言的内核并没有太多的改变,但是程序的规模变得越来越庞大及复杂,而与此同时带来的如何确保大型复杂项目的可靠性、安全性成为一个挑战,这个时候就要请出我们的静态程序分析啦。
二、我们为何需要静态分析
- 程序可靠性
空指针引用,内存泄露等 - 程序安全性
隐私信息泄露,注入攻击等 - 编译器优化
死亡代码消除,代码移动(code motion)?等 - 程序理解
IDE调用层次结构,类型指示等
以上是从代码角度来说需要学习静态分析的原因及作用,而对于个人来说,学习静态分析也可以在自己写代码的时候给自己一个提醒,写出质量更高的代码。
三、什么是静态分析
Static analysis analyzes a program P to reason about its behaviors and
determines whether it satisfies some properties before running P.
静态分析就是在运行程序P之前对其进行分析,判断这段程序是否满足需要的属性要求,例如是否存在内存泄露等问题。
静态分析与动态分析的最主要的区别就在于分析是在程序运行之前还是之后进行,而这不同之处决定了二者关注点的区别。
通俗来说静态分析更加关注程序运行中的过程,而动态分析更加关注运行的结果,这些区别将在后续的文章中得到比较直观的展示。
- Does P contain any private information leaks?
- Does P dereference any null pointers?
- Are all the cast operations in P safe?
- Can v1 and v2 in P point to the same memory location?
- Will certain assert statements in P fail?
- Is this piece of code in P dead (so that it could be eliminated)?
以上就是静态程序分析所希望做的一些事情,看上去很美好,但是实际上静态分析发展至今,依旧不能完美地解决以上的问题。
“Any non-trivial property of the behavior of programs in a r.e. language is undecidable.”
—— Rice’s Theorem
在一个r.e.的语言中,任何程序行为的non-trivial属性都是不可解释的。
其中r.e.即递归可枚举(recursively enumerable),指的是可以由图灵机原理定义的语言,而non-trivial property指的是那些与程序运行行为有关的属性。
也就是说,大米理论告诉我们,世界上并不存在一种完美的静态分析,可以完美地解决上文提到的那些问题。
四、Sound与Complete
为什么就没有那么一种完美的静态分析呢,因为在分析的过程中会不可避免的出现sound与complete的行为。
由上图可以直观地看出,三者成包含关系,而完美的程序分析就是中间的Truth,一个既Sound又Complete的状况,而我们正常的程序分析只能获得要么sound要么complete的结果,一种useful的结果。
简单来说,sound是一种过多的输出,输出的是全部的真实报错和部分的虚假报错;而complete与之相反,输出的是全是真是报错,但是比truth少了一部分的报错。
由上可知,我们在实际使用场景中,自然是希望输出是sound的,也就是说我情愿有误报而不要有漏报。
再引入两个概念,False Positives假阳性与False Negative假阴性。
红色部分是假阳性,一种comprise soundness妥协的sound,是sound相对于完美来说多余的部分,是误报;
绿色部分是假阴性,一种comprise completed妥协的complete,是complete相对于完美来说缺少的部分,是漏报。
误报了我们可以手动排除,对修复所有错误的目的无影响,而漏报会导致在检测不出部分错误,最终导致无法修复所有错误。
因此我们实际编写过程中要求分析结果可以是sound的,但不能是complete的。
(即那种同时存在误报和漏报的就不行)
五、一个简单的例子
接下来站是一个非常简单的程序分析过程帮助理解。
1. 抽象
先将具体的输入值抽象为符号
如图所示,将左侧的数值对应到右侧的抽象符号上,前三个符号是正负零,比较容易理解。
第四个符号unknown指得是,如果当前数值会因为变量改变而呈现为不同的状态,则全部定义为unknown。
第五个符号undefined指的是经过判断肯定不符合int定义的,例如一个除以0的数或者字符等。
2. 定义转换函数
如图所示这是针对这个例子定义的部分的转换函数,前五个和第八个一目了然,不做解释。
第六个因为除数是0,故结果是undefined。
第七个是由于我们定义的转换函数是抽象的,进行运算的目标也是抽象出来的,因此相加结果是unknown的。举个例子,即使我正数是1000,负数是-1,经过抽象后再经过转换函数处理,得到的结果仍是unknown,这也是sound的一种体现。
如图所示,将全部转换函数定义好以后,覆盖了所有的情况以后就可以进行运算了。
由上而下一步步进行运算,xyz三者没有疑问,而a虽然我一眼就能看出运算结果是正数,但是根据抽象运算规则定义,得到的数值应当是unknown。以下同理,且由于未定义arr,p和q的结果是undefined。
3. 控制流运算
右图是左图的运算流展示,在不同情况下y会有值,而根据sound原则,在最后一步的y值只能是unknown。
六、总结
以上就是第一节课主要讲的内容了,本人水平有限,有的地方可能解释的不大清楚,如果有错漏之处,还望指正,谢谢你的观看!