通过soot来分析一个java classfile 对于Sable官网的介绍的文档,我们将使用soot flow analysis framework
学习这个例子的目地在于:
1. how to inspect a class file by using Soot, and // 怎么样去用soot去检测一个class文件
2. how to profile a program by instrumenting the class file. //通过instrumenting class file to 剖析你的程序
任务:计算我们到底进行了多少次静态调用
1.Testinvoke 类 ,该类是我们要进行分析的类的sourcecode。
class TestInvoke {
private static int calls = 0;
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
foo();
}
System.out.println("I made " + calls + " static calls");
}
private static void foo() {
calls++;
bar();
}
private static void bar() {
calls++;
}
}
为了计算我们到底进行了多少次静态调用,我们需要一个辅助类MyCounter
/* The counter class */
public class MyCounter {
/* the counter, initialize to zero */
private static int c = 0;
/**
* increases the counter by
*
* <pre>
* howmany
* </pre>
*
* @param howmany
* , the increment of the counter.
*/
public static synchronized void increase(int howmany) {
c += howmany;
}
/**
* reports the counter content.
*/
public static synchronized void report() {
System.err.println("counter : " + c);
}
}
接下来我们要创建一个wrapper class(封装类)通过加入一个phrase 进入soot来完成我们的任务:剖析TestInvoke需要进行多少staticInvoke?然后在再执行运行soot(通过soot.Main.main()).
wrapper class: MainDriver:
import soot.*;
/* import necessary soot packages */
public class MainDriver {
/**
* 这里放入: TestInvoke.java
* @param args
*/
public static void main(String[] args) {
/* check the arguments */
if (args.length == 0){
System.err.println("Usage: java MainDriver [options] classname");
System.exit(0);
}
/* add a phase to transformer pack by call Pack.add */
Pack jtp = PackManager.v().getPack("jtp");
jtp.add(new Transform("jtp.instrumanter",
new InvokeStaticInstrumenter()));
/* Give control to Soot to process all options,
* InvokeStaticInstrumenter.internalTransform will get called.
*/
soot.Main.main(args);
}
}
我们要如何去统计TestInvoke中staticInvoke调用了多少次呢,我们就要在它每次staticInvoke之前先放入一个类似计数器的语句来统计staticInvoke的运行次数,这个任务就需要InvokeStaticInstrumanters
import soot.*;
import soot.jimple.*;
import soot.toolkits.graph.ExceptionalUnitGraph;
import soot.toolkits.graph.UnitGraph;
import soot.util.*;
import java.util.*;
/**
* InvokeStaticInstrumenter inserts count instructions before INVOKESTATIC
* bytecode in a program. The instrumented program will report how many static
* invocations happen in a run.
*
* Goal: Insert counter instruction before static invocation instruction. Report
* counters before program’s normal exit point. Approach: 1. Create a counter
* class which has a counter field, and a reporting method. 2. Take each method
* body, go through each instruction, and insert count instructions before
* INVOKESTATIC. 3. Make a call of reporting method of the counter class.
*
* Things to learn from this example: 1. How to use Soot to examine a Java
* class. 2. How to insert profiling instructions in a class.
* InvokeStaticInstrumenter extends the abstract class BodyTransformer, and
* implements
*
* <pre>
* internalTransform
* </pre>
*
* method.
*/
public class InvokeStaticInstrumenter extends BodyTransformer {
/* some internal fields */
static SootClass counterClass;
static SootMethod increaseCounter, reportCounter;
//在soot中注册我们的辅助类
static {
counterClass = Scene.v().loadClassAndSupport("MyCounter");
increaseCounter = counterClass.getMethod("void increase(int)");
reportCounter = counterClass.getMethod("void report()");
}
/** internalTransform goes through a method body and inserts
* counter instructions before an INVOKESTATIC instruction
*/
@Override
protected void internalTransform(Body body, String arg1, Map arg2) {
// 得到该方法
SootMethod method = body.getMethod();
// 调试
System.out.println("instrumenting method : " + method.getSignature());
System.out.println("MethodName: " + method.getName());
// 得到该方法的UnitChain
Chain units = body.getUnits();
//当遍历它的时候,改变它的语句链
Iterator stmtIt = units.snapshotIterator();
// 遍历每一条语句
while (stmtIt.hasNext()) {
// 得到statement
Stmt stmt = (Stmt) stmtIt.next();
if (!stmt.containsInvokeExpr()) {
continue;
}
// take out the invoke expression
InvokeExpr expr = (InvokeExpr) stmt.getInvokeExpr();
// 跳过 non-static invocations
if (!(expr instanceof StaticInvokeExpr)) {
continue;
}
// now we reach the real instruction
// call Chain.insertBefore() to insert instructions
//插入一条Expr,调用我们构造的辅助类的方法,参数为1(显然)
InvokeExpr incExpr = Jimple.v().newStaticInvokeExpr(
increaseCounter.makeRef(), IntConstant.v(1));
Stmt incStmt = Jimple.v().newInvokeStmt(incExpr);
// 3. insert new statement into the chain
//we are mutating the unit chain)
units.insertBefore(incStmt, stmt);
}
//最后,我们要插入一条语句来输出最后的结果
//判断当前是不是main方法
String signature = method.getSubSignature();
boolean isMain = signature.equals("void main(java.lang.String[])");
if (isMain) {
stmtIt = units.snapshotIterator();
while (stmtIt.hasNext()) {
Stmt stmt = (Stmt) stmtIt.next();
// check if the instruction is a return with/without value
if ((stmt instanceof ReturnStmt)
|| (stmt instanceof ReturnVoidStmt)) {
// 2. then, make a invoke statement
InvokeExpr reportExpr = Jimple.v().newStaticInvokeExpr(
reportCounter.makeRef());
// 3. insert new statement into the chain
Stmt reportStmt = Jimple.v().newInvokeStmt(reportExpr);
units.insertBefore(reportStmt, stmt);
}
}
}
}
}
下面是
改变后的TestInvoke.class改变后的截图:
6 invokestatic MyCounter.increase(int) : void [36]
50 invokestatic MyCounter.report() : void [17]
可以看到 6,50行的变化~我们插入了我们需要的expr