静态分析学习笔记:数据流分析1
1.数据流分析?
数据流分析(Data Flow Analysis),对于这个名词我们需要记住这三点
-
数据流如何在控制流图(CFG)中流动,其中数据流分析主要是在CFG上进行
-
针对特定应用场景的数据是什么样的(对应了数据流中的Data一词)
-
数据是如何流动的 :Nodes(基本块)经过数据流)构成了CFG(整个程序),其中以下两个步骤都属于是数据流分析中近似的一部分,
- Nodes->转换函数(Transfer Function)
- Edges->控制流处理(Control-flow handing)
这里需要强调数据流的两种分析类别,may analysis与must analysis
- may analysis:输出的信息可能为真(over-approximation)
- must analysis:输出的信息一定为真(under-approximation)
这里再补充两个概念over-approximation与under-approximation
- over-approximation:分析的数据结果可能为真(可以理解为包含了所有正确的分析,但也可能有不正确的分析),又被称为safe-apporximation
这里为了更加精确:Over-opprximation可以被视为Safe-approximation
不同的数据流往往会有不同的应用,这也会为数据带来不同的抽象方式,此时的Safe-apprximation也会虽则改变,此时也会造成转换函数和控制流处理的不同
2:数据流分析的几个方法:
以下会简单介绍一下数据流分析的几种方法
2.1 Input and output States
这种方法是将原本的程序的IR转换为节点,并且对应每一个节点都有输入输出,这里的输入输出都对应一个program point。注意第一个例子,S2的输入就是S1的输出,例子2同理,这里S2和S3的输入均为S1的输出,例子3处使用了^(meet)这个预算符,表明了S2的输入符号为S1和S2的输出共同组成(可能是集合的∪、∩具体取决于应用场景,这个符号的本质是描述两条数据流之间数据的状态)。
数据流分析的最终目标是:每一个program point关联一个数据流的值,这个值代表着
这一点所有可能值的抽象,这里我们还是看第一节课的例子:
在这个程序当中,我们在语句和语句之间切分了程序点,并在程序点处分析出了x与y的符号状态。
当然也可以从另外一个角度来理解:数据流分析的本质是通过转换函数和控制流的约束规则,通过解析safe-approximation集合中的约束规则,得到一个解,这解的可以使得每一个program point的IN和OUT都得出一个值。
这里一定要留意两个重点,转换函数与控制流处理,这两个是数据流分析中最为重要的节点。
2.2 预备知识:转换函数符号约束 Notations for transfer Functions's constraints:
- Forward analysis:这里的fs是转换函数,简单来说就是输入通过某种转换函数的运算转换成输出
- Backward analysis:和上面的Forward反转,其他没有区别
2.3预备知识 数据流图(CFG)符号约束:
这里对于CFG的约束研究最小单元是BB(基本块),这里分析需要分为两个情况,在BB中的数据流,以及BB之间的数据流
- 在bb内:每条语句的输入就是上一条语句的输出
2. bb之间:每个基本格的输入就是自己第一条语句的输入,输出则是自己最后一个语句的
这里我们来思考一下以下的这种情况,其中B是一个基本块:
这里我们先思考一下如何构造基本的转换函数。这里的fB表示fB的转换函数, 其是B中每一个语句的转换函数的合集
这里B的IN是所有前驱的meet
3 Reaching Definitions Analysis
主要分为Method Calls与Aliases
-
Method Call
- intra-procedural CFG
-
Aliases:简单来说就是两个变量指向同一块地址
- Pointer Analysis
什么是Reaching Denfinitions
用人话说就是:如果要考变量b可以从点p reach 点q,必须满足以下两个条件
- p到q之间的路径间,v没有再次被定义
- 他们在CFG上是可达的
这里需要留意的是,Reaching denfinitions本质上是一种may analysis。其需要对程序所有可能reach的路径进行测算,这就导致了在有些路径满足v没有再次被定义,而另外一些路径上,v被再次定义了。
这里我们对数据进行抽象
- 这里的定义是程序中所有的变量
- 可以用位向量来表示
- 假设此处我们有100个bit位,这里是抽象出的位向量
此处我们查看以下这个定义
这个定义生成了生成了v的一个定义D,并且删除(kill)了其他变量v在程序p中的定义。这里我们看看其转换函数以及控制流
- 转换函数
这里是一个例子,gen这块好理解,就是变量被定义的地方,kill集这里要注意是BB中所有被定义变量在其他地方被重新定义的地方
此处我们可以正式开始算法的设计分析,此处的method就是算法本身
下面有几点需要关注的
- OUT[entry]={empty}:初始化为空
- for(each basic block B\entry):这里要注意需~~~~要排除掉entry block
- 这里注意,在任何的输出改变的情况下,都需要对基本块进行检测,这里的检测中是实际上就是转换函数的约束加上控制流的约束
- 这个算法的本质是一种迭代算法
此处我们给出一个例子
在这个例子当中我们来尝试分析一下代码的源代码
x=p+1
y=q+2
do {
m=k
y=q-1
if(exp){
x=4
z=5
}
else{
x=m-3
break;
}
while(exp)
z=2p;
}
这里我们利用位向量化算法来对程序进行表示
这个地方的大约做法就是以下两步
- 先对每个位向量赋值为0
- 再对每一个向量进行分析,分析的方式是根据前面的控制流约束规则和转换函数的越苏规则
- 如果该变量出现定义,那么就将这一位置1
- 如果出现需要kill的部分,则对这个需要kill的部分置0。
以下我们展示了这个算法的第一轮迭代,算法会一直迭代到所有基本块的输出都相同后才会停止
接下来是第二轮迭代(蓝色数字),步骤上和第一轮迭代相同
此时尚有out还是有变化,此时我们需要再次进行迭代
第三次遍历,遍历的结果用绿色数字表示,此时out[]不变化了,
此时我们需要对最终输出做出解释,其中任意一个输出中的0代表其所对应的基本块无法reach当前的数据流。反之,输出中的1代表了该位所对应的基本快可以reach到当前的数据流
这里还剩下最后一个问题,这个算法中是否一定能够保证在若干次迭代后一定找到一个状态使得所有的output都不再变化?(也就是说算法是否能够保证停下来)
这里我们再看看转换函数的约束。
这里我们就可以得出以下两个结论
-
gen和kill是不变的(程序中的statement不会改变)
-
当more facts经过IN[S]时,则有两种情况
- more facts被kill掉或者
- 流入OUT[S](这个情况下被称为survivor)
-
当这些fact通过gen或者survior被加入OUT[S]他们就会一直呆在那里了,而OUT[S]也不会出现缩小的情况。
由此我们可以知道在最坏的情况下,一定可以找得到某个状态使得整个OUT满足不变的要求