12月中旬的时候又按照页面:
http://www.sable.mcgill.ca/soot/tutorial/index.html
中的教程More on profiling,继续对Soot的Instrumentation功能学习。上面这个文档中已经把基本内容解释得很清楚了,我这里就简单总结一下。首先,假设要分析的字节码文件对应的源代码文件是:TestInvoke.java
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++; } }
为了记录TestInvoke中调用了多少次静态方法bar,我们再生成一个很简单的MyCounter类(MyCounter.java):
public class MyCounter { private static int c = 0; public static synchronized void increase(int howmany){ c +=howmany; } public static synchronized void report(){ System.err.println("counter : " + c); } }
之后,需要把我们自己的分析代码插入到Soot自己分析的phase中,和我之前一篇博客介绍的内容类似,我们生成一个MainDriver.java:
import soot.*; public class MainDriver { public static void main(String[] args) { if (args.length == 0){ System.err.println("Usage: java MainDriver [options] classname"); System.exit(0); } Pack jtp = PackManager.v().getPack("jtp"); jtp.add(new Transform("jtp.instrumanter", new InvokeStaticInstrumenter())); soot.Main.main(args); } }
最后,我们需要实现上面的InvokeStaticInstrumenter类,文档中对这个类的实现原理已经写得很清楚,这里就只把代码拷贝过来:
import soot.*; import soot.jimple.*; import soot.util.*; import java.util.*; public class InvokeStaticInstrumenter extends BodyTransformer{ static SootClass counterClass; static SootMethod increaseCounter , reportCounter; static { counterClass = Scene.v().loadClassAndSupport("MyCounter"); increaseCounter = counterClass.getMethod("void increase(int)"); reportCounter = counterClass.getMethod("void report()"); } protected void internalTransform(Body body, String phase, Map options) { SootMethod method = body.getMethod(); System.out.println("instrumenting method : " + method.getSignature()); Chain units = body.getUnits(); Iterator stmtIt = units.snapshotIterator(); while (stmtIt.hasNext()) { Stmt stmt = (Stmt)stmtIt.next(); if (!stmt.containsInvokeExpr()){ continue; } InvokeExpr expr = (InvokeExpr)stmt.getInvokeExpr(); if (!(expr instanceof StaticInvokeExpr)) { continue; } InvokeExpr incExpr = Jimple.v().newStaticInvokeExpr(increaseCounter.makeRef(), IntConstant.v(1)); Stmt incStmt = Jimple.v().newInvokeStmt(incExpr); units.insertBefore(incStmt, stmt); } 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(); if ((stmt instanceof ReturnStmt) ||(stmt instanceof ReturnVoidStmt)){ InvokeExpr reportExpr = Jimple.v().newStaticInvokeExpr(reportCounter.makeRef()); Stmt reportStmt = Jimple.v().newInvokeStmt(reportExpr); units.insertBefore(reportStmt, stmt); } } } } }
之后,我们在已经配置好Soot开发环境的IDE(如Eclipse)中,以"TestInvoke"作为参数运行MainDriver.java,可以看到在工程文件夹下生成了一个"sootOutput"文件夹,里面有经过处理后的TestInvoke.class。为了方便起见,我们将MyCounter.class、MainDriver.class、InvokeStaticInstrumenter.class也拷贝到这个目录下,再次通过命令行运行TestInvoke,得到的结果如下图所示:
和没有处理之前的TestInvoke相比:
可以很明显看到两者的差别。这个思路是很清楚,但是个人觉得,过程太过繁琐,完全以这种思路做实验会很辛苦。