前言
利用CHA完成调用图的构造
处理Java中四种调用:
invokestatic
invokespecial
invokeinterface
invokevirtual
一些准备
- 目录设置
这次我们是要进行过程间分析,所以有些细节的作用就显现出来了。
我之前喜欢将工作目录直接设置到要测试的代码目录上,比如:
Options.v().set_process_dir(Arrays.asList("target/classes/com/DeadCodeDetection/TestDC"));
之前确实不会有什么问题,而且直接不包括我们的分析代码,比较方便。但对于这次来说就不一样了,假如我们还这么设置:
Options.v().set_process_dir(Arrays.asList("target/classes/com/CHA/TestCHA"));
对我们要分析的代码
package com.CHA.TestCHA;
public class TestCode{}
class A {
static void main() {
A.foo();
}
static void foo() {
A a = new A();
a.bar();
}
void bar() {
C c = new C();
c.bar();
}
}
class B extends A {
void bar() { }
}
class C extends A {
void bar() {
if (Math.random() > 0.5) {
A.foo();
}
}
void m() { }
}
生成的Jimple如下(不是全称类名):
但内部有关调用时函数签名却是:
包含了类的全名,所以从生成的Jimple结果来看,在分析中会找不到对应的方法,debug一下确实如此:
这是初始化时,因为工作目录的设置得到的方法签名,到后面利用Unit
分析时:
就不会进入if了,所以稍微注意下就行,我就直接这么安排目录:
init中这么写即可:
Options.v().set_process_dir(Arrays.asList("target/classes/"));
//不分析的目录
List<String> excluded = new LinkedList<>();
excluded.add("com.*");
Options.v().set_exclude(excluded);
Options.v().set_no_bodies_for_excluded(true);
- 分析阶段
需要开启全局模式(可能是因为要进行过程间分析?)
PackManager.v().getPack("wjtp").add(new Transform("wjtp.cg_CHA", new CHATransformer()));
PackManager.v().getPack("wjtp").apply();
- 明确需要的类
CHATransformer
CallKind
:java中的枚举类,其实就是用来判断是哪种调用CallEdge
:代表调用边CallGraphBuilder
,JimpleCallGraph
:Builder利用后者提供的各种操作,实现算法分析
过程分析
对JimpleCallGraph
初始化,将方法和它所有的units关系保存下来,方便后面建立调用边时寻找。那么思路就是对于要分析的类的方法,如果不为空,就将其保存在一个Map中:
那么就可以将其传入到我们的CallGraphBuilder
中进行分析了,回顾一下课上的算法:
那我们就准备好WL,RM,按照简单,方法的entry都从main开始,所以JimpleCallGraph
中提供一个操作:
剩下差不多一样的过程,对照着算法来写,缺什么补什么就行,不再写的那么麻烦了,但还是希望写代码前能考虑考虑,BuildCallGraph
如下:
cg的一些操作如下:
private Set<SootMethod> reachableMethods = new HashSet<>();
private Map<SootMethod, Set<CallEdge>> caller2callee = new HashMap<>();//caller ---> callees
public Collection<Unit> getCallSiteIn(SootMethod method) {//一个方法中所有的callsites
List<Unit> callSites = new LinkedList<>();
if (method.hasActiveBody()) {
Body body = method.getActiveBody();
for (Unit unit : body.getUnits()) {
Stmt stmt = (Stmt) unit;
if (stmt.containsInvokeExpr()) {
callSites.add(stmt);
}
}
}
return callSites;
}
public boolean addEdge(Unit callsite, SootMethod callee, CallKind callKind){
CallEdge callEdge = new CallEdge(callKind, callsite, callee);
SootMethod caller = unit2Owner.get(callsite);
Set<CallEdge> callees = caller2callee.computeIfAbsent(caller, k -> new HashSet<>());
callees.add(callEdge);
return ret;
}
public boolean contains(SootMethod method) {
return reachableMethods.contains(method);
}
public void addRM(SootMethod method) {
reachableMethods.add(method);
}
对于Resolve
来说,也是这样的
接下来就是Dispatch
:
剩下的就是些其他类的补充了
CallKind
:
public enum CallKind {
INTERFACE("invokeinterface"),
VIRTUAL("invokevirtual"),
SPECIAL("invokespecial"),
STATIC("invokestatic");
private String inst;
CallKind(String inst) {
this.inst = inst;
}
public static CallKind getCallKind(Unit unit) throws IllegalArgumentException {//返回给定Unit的调用类型
InvokeExpr invoke = ((Stmt) unit).getInvokeExpr();
if (invoke instanceof InterfaceInvokeExpr) {
return INTERFACE;
}
if (invoke instanceof VirtualInvokeExpr) {
return VIRTUAL;
}
if (invoke instanceof SpecialInvokeExpr) {
return SPECIAL;
}
if (invoke instanceof StaticInvokeExpr) {
return STATIC;
}
throw new IllegalArgumentException(invoke.toString());
}
@Override
public String toString() {
return inst;
}
}
CallEdge
:
public class CallEdge {
private CallKind callKind;
private Unit callSite;
private SootMethod callee;
public CallEdge(CallKind callKind, Unit callSite, SootMethod callee) {
this.callKind = callKind;
this.callSite = callSite;
this.callee = callee;
}
public CallKind getCallKind() {
return callKind;
}
public Unit getCallSite() {
return callSite;
}
public SootMethod getCallee() {
return callee;
}
@Override
public String toString() {
StringBuilder buff = new StringBuilder();
buff.append("@").append(callSite.getTag(LineNumberTag.IDENTIFIER))
.append(": ").append(callSite)
.append(" -> ").append(callee.getSignature());
return buff.toString();
}
}
然后我们再Transformer
中添加如下测试即可:
public class CHATransformer extends SceneTransformer {
@Override
protected void internalTransform(String s, Map<String, String> map) {
CHACallGraphBuilder cgBuilder = CHACallGraphBuilder.v();
JimpleCallGraph cg = new JimpleCallGraph();
cgBuilder.buildCallGraph(cg);
for (SootClass clazz : Scene.v().getApplicationClasses()) {
for (SootMethod method : clazz.getMethods()) {
StringBuilder buff = new StringBuilder();
// basic information
buff.append(method.getSignature())
.append(": \n")
.append("\t ").append(cg.contains(method) ? "Reachable" : "Unreachable")
.append("\n");
// call edge
Set<CallEdge> edgeSet = cg.getCallOutOf(method);
for (CallEdge callEdge : edgeSet) {
buff.append("\t ").append(callEdge).append("\n");
}
buff.append("\n");
System.out.println(buff);
}
}
}
}
结果