基於soot的過程內數據流分析

程序靜態分析

程序靜態分析(program static analysis)是指在不運行代碼的方式下,通過詞法分析、語法分析、控制流、數據流分析等技術對程序代碼進行掃描,驗證代碼是否滿足規範性、安全性、可靠性、可維護性等指標的一種代碼分析技術。(百度百科


靜態分析中的xxx-sensitive的一些理解:

(此內容來自知乎

xxx-sensitive是指靜態分析中用於降低誤報(false positive)的技術。存在flow-、path-、context-。

flow-insensitive:是把statements當作一個集合來看的,各個statement之間沒有順序,所以control flow statement(if、while)可以直接刪除;flow-sensitive是說要關注statements之間的先後順序。

path-insensitive:if、while靜態分析時不知道動態的執行路線(path),所以一般會把它們不同的分支的數據流集merge起來。而path-sensitive則針對不同的路徑的數據集不會merge,而是分別進行分析;

context-insensitve:只關心function之間的數據傳遞(參數、返回值、side-effect)而忽略了同一函數在不同call side下不同的context,即忽略了call stack。將call site和return當作goto,並添加一些賦值語句,這樣造成的情況是,第一個call site處正常,第二個call site時返回值可能出現兩個。

 

靜態分析時,無論是分析源代碼還是目標代碼,分析的對象(方法、語句、變量)都只有一份:同一個方法我們只會寫一個方法體(方法體裏的語句也就只有一份)。同一個變量只會聲明一次。然後動態運行程序的時候:

一個方法可能會被調用N次,每次調用的上下文可以不一樣,不同上下文中這個方法裏的變量的值會不同;一個方法裏,一個變量的不同位置的值也會不一樣;一個方法裏同一個位置的變量的值在程序執行不同路徑時也不一樣。寫的方法、語句、變量在動態運行時彷彿有了“分身”,每個分身都有自己的值。靜態分析的時候對於同一個對象只能看到一個實體,如果直接分析,一個變量所有“分身”的相關屬性會全部合併,並且一個變量的屬性合併了,會影響其他變量的分析結果。

靜態分析爲得到準確的結果,就得爲分析的對象模擬動態運行時的分身。

xxx-sensitive就是在靜態分析時,按照xxx給程序裏的對象(模擬動態運行)創建“分身”(或者說按照xxx區分分析對象):按照上下文區分叫作context-sensitive;按照位置區分叫作flow-sensitive;按照路徑區分叫作path-sensitive。區分之後就可以減少false positive。

數據流分析

(此內容來自龍書)

數據流分析:指的是一組用來獲取有關數據如何沿着程序執行路徑(control-flow graph)流動的相關信息的技術。

 

在所有的數據流分析應用中,我們都會把每個程序點和一個數據流(data-flow value)關聯起來。這個值是在該點可能觀察到的所有程序狀態的集合的抽象表示。所有可能的數據流值的集合稱爲這個數據流應用的域(domain)。

 

我們把每個語句s之前和之後的數據流值分別記爲IN[s]和OUT[s]。數據流問題(data-flow problem)就是要對一組約束求解。這組約束對所有的語句s限定了IN[s]和OUT[s]之間的關係。約束分爲兩種:基於語句語義(傳遞函數)的約束和基於控制流的約束。

 

數據流分析一般分爲:intra-procedural analysis和inter-proceduralanalysis,在上篇Phase中提到,jtp pack和wjtp pack可以分別被用來實現自定義的數據流分析

 

實現數據流分析前需要搞清楚的問題

數據流的方向(前向、後向);交匯運算採用交集還是並集(當path-insenstitive時);傳遞函數;數據流集合的初始化。

採用soot框架實現過程內數據流分析

(此部分內容參考點擊打開鏈接

1.      過程內數據流分析

過程內數據流分析(intra-proceduraldata-flow analysis)指在一個單獨方法的控制流圖上操作。在soot中的控制流圖爲UnitGraph。UnitGraph中節點表示statements,如果在控制流上一個表示source node流向target node,那麼這兩個結點存在邊(edge)。

數據流分析都與unitgraph每個節點上的兩個元素有關,這兩個集合被稱爲:in-set和out-set。這些集合會被初始化,然後沿着語句節點傳播,指導一個定點抵達才停止。

最後,你要做的就是檢查每個句子的前後的flow set。通過設計的數據流分析,你的flow sets應該會直接告訴你所需要的信息。

 

2.      Forward、backward orbranched(解決數據流的方向問題)?

在soot中FlowAnalysis存在三種不同類型的方法:

ForwardFlowAnalysis:這個分析以UnitGraph的entrystatement作爲開始並開始傳播;

BackwardsFlowAnalysis:這個分析以UnitGraph的exit node(s)作爲分析並且向後開始傳播(當然可以將UnitGraph轉換產生inverseGraph,然後再使用ForwardFlowAnalysis進行分析);

ForwardBranchedFlowAnalysis:這個分析本質上也是Forward分析,但是它允許你在不同分支處傳遞不同的flow sets。例如:如果傳播到如if(p!=null)語句處,當“p is not null”時,傳播進入“then”分支,當“p is null”時傳播進入“else”分支(Forward、backward分析都在分支處會將分析結果merge)。

 

3.      實現過程間數據流分析的關鍵方法

Constructor

必須實現一個攜帶DirectedGraph作爲參數的構造函數,並且將該參數傳遞給super constructor。然後,在構造函數結束時調用doAnalysis(),doAnalysis()將真正執行數據流分析。而在調用super constructor和doAnalysis之間,可以自定義數據分析結構。

<span style="font-size:14px;">public MyAnalysis(DirectedGraph graph) { //構造函數
	super(graph);
	// TODO Auto-generated constructor stub
	emptySet = new ArraySparseSet();
	doAnalysis();//執行fixed-point
}</span>


newInitialFlow()和entryInitialFlow()(數據流集合的初始化問題

newInitialFlow()方法返回一個對象,這個對象被賦值給每個語句的in-set和out-set集合,除過UnitGraph的第一個句子的in-set集合(如果你實現的是backwards分析,則是一個exit statement語句)。第一個句子的in-set集合由entryInitialFlow()初始化。

<span style="font-size:14px;">@Override
protected Object newInitialFlow() {
	// TODO Auto-generated method stub
	return emptySet.emptySet();
}

@Override
protected Object entryInitialFlow() {
	// TODO Auto-generated method stub
	return emptySet.emptySet();
}</span>

copy(..)

copy(..)方法攜帶兩個參數,一個source和一個target。它僅僅實現將source中的元素拷貝到target中。

<span style="font-size:14px;">@Override
protected void copy(Object source, Object dest) {
	// TODO Auto-generated method stub
	FlowSet srcSet = (FlowSet)source,
	destSet = (FlowSet)dest;
	srcSet.copy(destSet);
}</span>


merge(..)(數據流的交匯運算問題

merge(..)方法被用來在control-flow的合併點處合併數據流集,例如:在句子(if/then/else)分支的結束點。與copy(..)不同的是,它攜帶了三個參數,一個參數是來自左邊分支的out-set,一個參數是來自右邊分支的out-set,另外一個參數是兩個參數merge後的集合,這個集合將是合併點的下一個句子的in-set集合。

注:merge(..)本質上指的是控制流的交匯運算,一般根據待分析的具體問題來決定採用並集還是交集。

<span style="font-size:14px;">@Override
protected void merge(Object in1, Object in2, Object out) {
	// TODO Auto-generated method stub
	FlowSet inSet1 = (FlowSet)in1,
	inSet2 = (FlowSet)in2,
	outSet = (FlowSet)out;
	//inSet1.union(inSet2, outSet);
	inSet1.intersection(inSet2, outSet);
}</span>

flowThrough(..)(數據流的傳遞函數問題

flowThrough(..)方法是真正執行流函數,它有三個參數:in-set、被處理的節點(一般指的就是句子Unit)、out-set。這個方法的實現內容完全取決於你的分析。

注:flowThrough()本質上就是一個傳遞函數。在一個語句之前和之後的數據流值受該語句的語義的約束。比如,假設我們的數據流分析涉及確定各個程序點上各變量的常量值。如果變量a在執行語句b=a之前的值爲v,那麼在該語句之後a和b的值都是v。一個賦值語句之前和之後的數據流值的關係被稱爲傳遞函數。針對前向分析和後向分析,傳遞函數有兩種風格。

<span style="font-size:14px;">@Override
protected void flowThrough(Object in, Object d, Object out) {
	// TODO Auto-generated method stub
	FlowSet inSet = (FlowSet)in,
	outSet = (FlowSet)out;
	Unit u = (Unit) d;
	kill(inSet,u,outSet);
	gen(outSet,u);
}

private void kill(FlowSet inSet, Unit u, FlowSet outSet) {
	// TODO Auto-generated method stub
	FlowSet kills = (FlowSet)emptySet.clone();//Unit的kills
	Iterator defIt = u.getDefBoxes().iterator();
	while(defIt.hasNext()){
		ValueBox defBox = (ValueBox)defIt.next();
			
		if(defBox.getValue() instanceof Local){
			Iterator inIt = inSet.iterator();
			while(inIt.hasNext()){
				Local inValue = (Local)inIt.next();
				if(inValue.equivTo(defBox.getValue())){
					kills.add(defBox.getValue());
			}
		}
	<span style="white-space:pre">	</span>}
	}
	inSet.difference(kills, outSet);
}

private void gen(FlowSet outSet, Unit u) {
	// TODO Auto-generated method stub
	Iterator useIt = u.getUseBoxes().iterator();
	while(useIt.hasNext()){
		ValueBox e = (ValueBox)useIt.next();
		if(e.getValue() instanceof Local)
			outSet.add(e.getValue());
	}
}</span>


Flow sets

(此部分內容參考點擊打開鏈接

    在soot中,flow sets代表control-flowgraph中與節點相關的數據集合。Flow set存在有界限的(interface BoundedFlowSet)和無界限的(interfaceFlowSet)兩種表達。有界限的集合知道可能值的全體集合,而無界限的集合則不知道。

Interface FlowSet<T>提供的關鍵方法有

<span style="font-size:14px;">FlowSet<T> clone() //克隆當前FlowSet的集合
FlowSet<T> emptySet() //返回一個空集,通常比((FlowSet)clone()).clear()效率更高
void copy(FlowSet<T> dest) //拷貝當前集合到dest集合中
void union(FlowSet<T> other) //FlowSet∪other = FlowSet
void union(FlowSet<T> other,FlowSet<T> dest) // FlowSet∪other = dest,其中other、dest可以與該FlowSet一樣
void intersection(FlowSet<T> other) //FlowSet∩other = FlowSet
void intersection(FlowSet<T> other,FlowSet<T> dest) // FlowSet∩other = FlowSet,其中,dest、other可以和該FlowSet一樣
void difference(FlowSet<T> other) // FlowSet-other = FlowSet
void difference(FlowSet<T> other,FlowSet<T> dest) // FlowSet-other = dest,其中,dest、other和FlowSet可能相同。
</span>

還有isEmpty()、size()、add(T obj)、remove(T obj)、contains(Tobj)、isSubSet(FlowSet<T> other)、iterator()、toList()等。

上述方法足以使flow sets成爲一個有效的lattice元素。


當實現BoundedFlowSet時,它需要提供方法,該方法能夠產生set‘s complement和its topped set(一個lattice element包括所有的可能的值的集合)。

Soot提供了四種flow sets的實現:ArraySparseSet,ArrayPackedSet,ToppedSet和DavaFlowSet。

ArraySparseSet:是一個無界限的flowset。該set代表一個數組引用。注意:當比較元素是否相等時,一般使用繼承自Object對象的equals。但是在soot中的元素都是代表一些代碼結構,不能覆寫equals方法。而是實現了interface soot.EquivTo。因此,如果你需要一個包含類似binary operation expressions的集合,你需要使用equivTo方法實現自定義的比較方法去比較是否相等


針對intra-procedural analysis,本人實現了一個活躍變量的代碼

import java.util.Iterator;

import soot.Local;
import soot.Unit;
import soot.ValueBox;
import soot.toolkits.graph.DirectedGraph;
import soot.toolkits.scalar.ArraySparseSet;
import soot.toolkits.scalar.BackwardFlowAnalysis;
import soot.toolkits.scalar.FlowSet;

class MyAnalysis extends BackwardFlowAnalysis{
	
	private FlowSet emptySet;

	public MyAnalysis(DirectedGraph graph) { //構造函數
		super(graph);
		// TODO Auto-generated constructor stub
		emptySet = new ArraySparseSet();
		doAnalysis();//執行fixed-point
	}

	@Override
	protected void flowThrough(Object in, Object d, Object out) {
		// TODO Auto-generated method stub
		FlowSet inSet = (FlowSet)in,
				outSet = (FlowSet)out;
		Unit u = (Unit) d;
		kill(inSet,u,outSet);
		gen(outSet,u);
	}

	private void kill(FlowSet inSet, Unit u, FlowSet outSet) {
		// TODO Auto-generated method stub
		FlowSet kills = (FlowSet)emptySet.clone();//Unit的kills
		Iterator defIt = u.getDefBoxes().iterator();
		while(defIt.hasNext()){
			ValueBox defBox = (ValueBox)defIt.next();
			
			if(defBox.getValue() instanceof Local){
				Iterator inIt = inSet.iterator();
				while(inIt.hasNext()){
					Local inValue = (Local)inIt.next();
					if(inValue.equivTo(defBox.getValue())){
						kills.add(defBox.getValue());
					}
				}
			}
		}
		inSet.difference(kills, outSet);
	}

	private void gen(FlowSet outSet, Unit u) {
		// TODO Auto-generated method stub
		Iterator useIt = u.getUseBoxes().iterator();
		while(useIt.hasNext()){
			ValueBox e = (ValueBox)useIt.next();
				if(e.getValue() instanceof Local)
					outSet.add(e.getValue());
			}
		}


	@Override
	protected Object newInitialFlow() {
		// TODO Auto-generated method stub
		return emptySet.emptySet();
	}

	@Override
	protected Object entryInitialFlow() {
		// TODO Auto-generated method stub
		return emptySet.emptySet();
	}

	@Override
	protected void merge(Object in1, Object in2, Object out) {
		// TODO Auto-generated method stub
		FlowSet inSet1 = (FlowSet)in1,
				inSet2 = (FlowSet)in2,
				outSet = (FlowSet)out;
		//inSet1.union(inSet2, outSet);
		inSet1.intersection(inSet2, outSet);
	}

	@Override
	protected void copy(Object source, Object dest) {
		// TODO Auto-generated method stub
		FlowSet srcSet = (FlowSet)source,
				destSet = (FlowSet)dest;
		srcSet.copy(destSet);
	}

}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章