程序靜態分析
程序靜態分析(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);
}
}