Newtank

个人站

欢迎来到我的个人站~


数据流分析(一)

目录

概述

数据流分析解答的是“数据如何在控制流图(CFG)上流动”的问题。而这里的“数据”具体而言就是指程序特有的数据,在CFG上流动便是指流过其节点与边。“数据”代表的是抽象手段,而“流过”代表的是近似手段。

may analysis

输出可能为真的结果(过近似 over-approximation)

只要有一种为真的可能就应输出

must analysis

输出一定为真的结果(欠近似 under-approximation)

只有全部情况下为真才能输出

无论哪种近似手段,都是追求safe的近似。

在节点上,我们使用转移函数(Transfer function)的方式,而在边上,我们使用控制流处理(Control-flow handling)的方式。

对于不同的数据流分析应用,都有不同的数据抽象、流近似、转移函数和控制流处理。

在基本的数据流分析中,我们不考虑方法调用和别名这两种情况。

基础概念

输入状态和输出状态

每一条IR语句都将一个输入状态转变为一个新的输出状态。这两种状态与语句执行前后的程序状态关联。

显然的,对于相邻的两条语句,第一条语句的输出状态和第二条语句的输入状态是相等的。

值得注意的是,当两条控制流汇聚到一条语句时,我们使用$\wedge$来表示结果。这个运算根据应用来定义。

在所有数据流分析的应用中,我们把每一个程序点(program point)关联于一个数据流对象来表示这个点时所有可能程序状态的抽象。

而数据流分析的目的是通过预先设定的规则来对所有语句的输入、输出状态进行求解。这种规则基于安全近似导向的约束,其基于语句的语义(转移函数)和控制流。

转移函数约束

正向分析:

\[OUT[s]=f_s(IN[s])\]

反向分析:

\[IN[s]=f_s(OUT[s])\]

控制流约束

在有n条语句的基本块内:

\[IN[s_{i+1}]=OUT[s_i],i=1,2,......,n-1\]

基本块之间:

\[IN[B]=In[s_1]\\ OUT[B]=OUT[s_n]\\\]

这两句揭示了基本块和具体语句的状态关系

\[OUT[B]=f_B(IN[B]),f_B=f_{s_n}\circ...\circ f_{s_1}\\ IN[B]=\bigwedge_{P\ a\ predecessor\ of\ B}OUT[P]\]

这两句揭示了正向分析中基本块间的状态关系

\[IN[B]=f_B(OUT[B]),f_B=f_{s_1}\circ...\circ f_{s_n}\\ OUT[B]=\bigwedge_{S\ a\ successor\ of\ B}IN[S]\]

这两句揭示了反向分析中基本块间的状态关系

Reaching Definitions

定义

在程序的p点上的定义d到达了程序的q点。所谓到达,就是指存在一条路径,其没有出现重复定义(没有被新的定义kill掉)

该分析的经典应用是检测未初始化变量。如果未初始化定义能够到达目标点,那么就有使用未定义变量的风险。

抽象

程序中所有的定义可以用一个位向量来表示。

如D1,D2,…,D100(100个定义),可以用00000……00(100位)表示。第i位表示Di定义

转移函数

对于以下语句:

D: v = x op y

这句产生了对变量v的定义D,同时消灭了其他对v的定义,而x和y的定义状态不受影响。

于是我们得到转移函数

\[OUT[B]=gen_B \cup (IN[B]-kill_B)\]

控制流

某个定义只要有一条道路能到达,那么它就是可达的,即有:

\[IN[B]=\bigcup _{P\ a predecessor\ of\ B}OUT[P]\]

算法

输入:CFG(每个基本块的$kill_B$和$gen_B$都已算出)

输出:每个基本块的IN[B]和OUT[B]

方法:

OUT[entry] = EMPTY
for(each entry block B\entry)
	OUT[B] = EMPTY;  //may analysis一般为EMPTY, must analysis一般为FULL
while(changes to any OUT occur)
	for(each basic block B\entry){
		IN[B] = union(OUT[B.predecessors]);
		OUT[B] = union(gen(B), IN[B]-kill(B));
	}

该迭代算法的结构也是很多分析算法的模板。

例如对以下程序:

我们用八位向量表达一个状态,每位表示对应的定义的存活情况。

初始时,所有OUT均被初始化为00000000

第一轮迭代中,B1首先产生1100 0000的OUT,随后B2的IN为B1和B4的OUT的并集,即为1100 0000。B2生成D3和D4,消灭D2,OUT为1011 0000。随后B3和B4分别输出0011 0010和0011 1100(B3和B4的计算顺序不影响最终结果)。B5输入0011 1110,输出0011 1011,第一轮迭代结束。

在第一轮迭代中,存在基本块的输出发生了变化,我们开始新一轮迭代。

在第二轮迭代中,B2和B3的输出发生了变化,进行第三轮迭代。

在第三轮迭代中,所有基本块的输出未发生变化,迭代结束。

在第三轮迭代中,我们可以发现,当IN[B]不变时,OUT[B]是不变的。

解析

为什么该算法能够停止

对于转移函数

\[OUT[B]=gen_B \cup (IN[B]-kill_B)\]

gen和kill是和in与out无关的,在分析过程中保持不变。于是当in不变时,out便不变。

当某个点的IN中的某个值变成1后,它在之后的相同位置的IN便永远都会变成1。于是输出便会最终固定。而位数是有限的,便会最终到达一个不动状态。

终止条件是否正确

当达到终止条件后,再次迭代已经不会改变任何状态。因为

\[OUT[B]=gen_B \cup (IN[B]-kill_B)\]

如果IN不变,OUT就不变。

\[IN[B]=\bigcup _{P\ a predecessor\ of\ B}OUT[P]\]

如果OUT不变,IN就不变。