1. 程式人生 > >Soot I: 基本瞭解

Soot I: 基本瞭解

Static analysis指在不對program進行執行的情況下,對其行為進行分析。搞compiler的人用之於優化,搞安全的用於做taint analysis。對於Java有兩大開源的static analysis 框架,Soot和WALA,前者由McGill大學維護,後者是IBM。最新的Soot開始支援對Andoird 程式碼分析,包括static taint analysis。

從這篇開始,我們將由淺入深的對Soot進行了解,使用,甚至擴充套件。在這裡預設讀者對static analysis理論(lattice, 不動點, may & must等),Java source code和bytecode語法有基本瞭解。將主要依據Soot 官網提供的資料作為依據,特別是那本『生存手冊』(

點選開啟連結)。

好了,現在我們開始。

Soot包括四種IR(中間碼-表示形式),分別代表了四種對Java Sourcode或者bytecode的不同程度的抽象。

Baf - 基於棧的bytecode

傳統的JVM bytebode是基於棧操作的指令集(Dalvik 基於暫存器操作),與之對應的Baf同樣如此。那Baf抽象了什麼呢?兩個,忽略了constant pool(常量池)和bytecode指令中的type依賴。在bytecode中對不同保留型別,如int和float,的同一操作(如add),有不同的指令。這是因為在計算機中整形和浮點型的表達方式是不一樣的,在底層實現時無法讓兩個操作符分屬於這兩種不同型別,也就是需要不同的指令對應不同的資料型別的操作。我們做分析時不用在意它到底呼叫的什麼型別的指令,不對int還是float做細緻區分,只要知道它是個數且知道是對這數的什麼樣的操作就行了。Baf因此用於在bytecode層面上的分析。 Jimple - typed, 3-addresses, statement based。
Jimple是Soot的核心,是四種IR中最重要的。Soot能直接建立Jimple碼,也可由Java sourcecode或者bytecode轉化翻譯而來。bytecode會被翻譯成untyped Jimple,再通過type inference 方法對區域性變數加上型別。翻譯的重要一步是對錶達式作線性化使得每個statement只能最多refernce 3個區域性變數或者常量(沒懂。。)。相對於bytecode的200多種指令,Jimple只有15條,分別對應著核心指令的 NopStmt, IdentityStmt, AssignStmt;函式內控制流指令的IfStmt, GotoStt, TableSwitchStmt和LookUpSwitchStmt,函式間控制流的InvoeStmt, ReturnStmt, ReturnVoidStmt, 監視器指令EnterMonitorStmt和ExitMonitorStmt,最後處理異常ThrowStmt和退出的RetStmt。 看段書中的例子。用

java -cp soot-trunk.jar soot.Main -f J Foo

讀入sootOutput資料夾中的Foo.Jimple。其所對應的Java 原始碼為,

public class Foo {
public static void main(String[] args) { Foo f = new Foo();
int a = 7;
int b = 14;
int x = (f.bar(21) + a) * b; }
public int bar(int n) { return n + 42; } }
Jimple為
public static void main(java.lang.String[]) { java.lang.String[] r0;
Foo $r1, r2;
int i0, i1, i2, $i3, $i4;
r0 := @parameter0: java.lang.String[]; $r1 = new Foo;
specialinvoke $r1.<Foo: void <init>()>(); r2 = $r1;
i0 = 7;
i1 = 14;
// InvokeStmt
$i3 = virtualinvoke r2.<Foo: int bar()>(21); 
$i4 = $i3 + i0;
i2 = $i4 * i1;
return;
}
public int bar() { Foo r0;
int i0, $i1;
r0 := @this: Foo; // IdentityStmt
i0 := @parameter0: int; // IdentityStmt $i1 = i0 + 21; // AssignStmt
return \$i1; // ReturnStmt
}
可以看出Jimple是一種Java sourcecode和 bytecode的混合體。對於區域性變數的宣告和賦值statement用的是Java,而控制流和函式呼叫採用的是bytecode。和反編譯Dalvik bytecode所得的smali類似,Jimple在每個method body前會把所有用到的區域性變數和stack位置做出宣告(即Var -> Loc, 把變數對映到棧中的地址並在body中用地址替代變數名)。帶"$"的區域性變量表示是stack中的位置而不是原java code中真正宣告過的區域性變數,表示在java中不出現而bytecode中少不了的隱含變數如this, 儲存中間結果的變數等。反之不帶的即與原java code中區域性變數相對應。 線性化過程把int x = (f.bar(21)) + a) * b拆成了三條statement:函式呼叫一條, $i4 = $i3 + i0和i2 = $i4 * i1;因此保證了每條stament至多隻帶3條地址。 i0 := @parameter0和r0 := @this作為IdentityStmt分別代表對形參和this的複製。所有的區域性變數都是帶型別的。 Jimple適用於絕大多數的不需要精確control flow或者SSA的靜態分析。 Shimple -- Static Single Assignment 版的Jimple 和Jimple基本一樣,只有兩點不同: SSA 和phi-node。SSA保證了每個區域性變數都有一個靜態定義。 目前還沒有看到用SSA的可能,先暫時略過。 Grimp -- 更適合人讀的   和Jimple類似,多了允許樹形表達和new指令。相比於Jimple,更貼近Java code,所以更適合人來讀。
public static void main(java.lang.String[]) { java.lang.String[] r0;
Foo r2;
int i0, i1, i2;
r0 := @parameter0: java.lang.String[]; r2 = new Foo();
i0 = 7;
i1 = 14;
i2 = (r2.<Foo: int bar(int)>(21) + i0) * i1;
return; }

可以看到樹形表示式沒有被linerazation, 物件的初始化濃縮到new中,一些臨時區域性變數被省去了。Grimp更適用於做available expr(表達expr簡潔)和反編譯(適合人讀)。 介紹完Soot的基本IR們,我們來說說Soot的基本使用。command為
java -cp soot-2.5.0.jar soot.Main -cp . -pp A B 
輸入的class有兩種 1. application class 即要被分析和翻譯的(在Soot裡叫transformation)class 2. Library class. 為application class所引用,有助於分析但不會直接被分析的class。

關於具體的命令就不再這裡敷述了。

Soot的執行過程被分成了好幾大步,每一大步被稱為一個pack。第一步是把輸入的bytecode (.class)或者.java 檔案或者.jimple 翻譯成Jimple code。再把生成的Jimple作為剩下packs的輸入。"函式中分析(intra-procedure analysis)"執行流程示意如下:

首字母s, j, b, g分別代表四種不同的IR。b代表body 建立,o 表示優化,a表標註(生成屬性),t表示使用者定義的transformation, p即packs,其中jtp和stp使用者可能更有興趣,因為任何使用者定義的transformation (如從分析中得出對資訊的標籤)可以被插入到這倆packs中因而作為Soot執行過程的一部分。

"函式間分析(inter-procedure analysis)"執行流程如下

執行函式間分析需要指明'-w',表示Whole-program mode (全程式模式)。在此模式下Soot包含3個額外的packs:cg(呼叫圖生成),wjtp (whole jtb)和wjap (whole jap)。用-W還會引入wjop做全程式優化。與intra-procedure analysis不同在於這裡得出的結果對每個application class都有效。

java soot.Main -pl
檢視所有可用的packs。

java soot.Main -ph PACK 

表示對指定PACK檢視幫助。

 OPT:VAl 來對pack進行設定,下面語句表示關閉所有使用者定義的函式內tansformation。

java soot.Main -p jtp enabled:false MyClass 

Soot 所支援的靜態分析

Null pointer analysis (空指標分析):位於 jap pack中,包含兩部分, 空指標檢查和空指標著色。前者用於找到可能會觸發NullPointerException異常的指令,後者再對這些指令做出特殊顏色的標註(在Eclipse中)。command如下:

java soot.Main -xml-attributes -f J -p jap.npcolorer on MyClass 

將會生成一個Jimple檔案,把這個檔案用Eclipse開啟,綠色的指令表絕對不是Null的,藍色表未知,紅色表絕對是Null的。


陣列越界分析

位於jap pack下的jap.abc。使用命令如下

java soot.Main -xml-attributes -f J -p jap.abc on -p jap.abcadd-color-tags:true MyClass 


Liveliss Analysis 

命令如下 : 

java soot.Main -xml-attributes -f J -p jap.lvtagger on MyClass 

對Soot的擴充套件

在我們設計和實現了一個自己的分析後,要把它與Soot帶的分析相結合,這時就需要對Soot的Main class做修改。此處的擴充套件指的是在在保留其它Soot特性的同時,在Soot分析中加入我們自己設計的(中間)步驟。對於函式內分析我們加到jtp步驟中,函式間分析則加到wjtp。

public class MySootMainExtension {
public static void main(String[] args) { // Inject the analysis tagger into Soot PackManager.v().getPack("jtp").add(new
Transform("jpt.myanalysistagger", MyAnalysisTagger.instance()));
// Invoke soot.Main with arguments given
Main.main(args); }
}