原文链接:https://github.com/Sable/soot/wiki/Fundamental-Soot-objects
Soot有一个庞大且复杂的类继承关系。这篇文档将向读者介绍一些最重要的类,以便于对Soot进行扩展开发。
我们将描述以下几个概念:Body, Unit, Local, Value, UnitBox和 ValueBox。
一切都是关于Body(s)
在 Creating a class from scratch(链接:https://github.com/Sable/soot/wiki/Creating-a-class-from-scratch)文档中,对Body的概念进行了简要的介绍,下面这个部分将更加详细的描述Body。
Soot使用Body来存储一个方法的代码。Soot中共有四种不同类型的Body,即BafBody、JimpleBody、ShimpleBody和GrimpBody,每一个对应于一种中间表示。
回想一下一条链(chain)就像一个列表的数据结构,提供对链中的元素进行常数时间内的访问(constant-time access),包括插入和移除。
Body中的三条主要的链是:Units链、Locals链和Traps链。下面这个例子将说明这三条链各自的角色。
考虑下面这个Java方法:
public static void main(String[] argv) throws Exception { int x=2,y=6; System.out.println("Hi!"); System.out.println(x*y+y); try { int z=y*x; } catch(Exception e) { throw e; } }
将它转换为Jimple代码之后,我们有下面简短的Jimple代码:
public static void main(java.lang.String[])throws java.lang.Exception { java.lang.String[] r0; int i0,i1,i2,$i3,$i4; java.io.PrintStream $r1,$r2; java.lang.Exception $r3,r4; r0 :=@parameter0; i0=2; i1=6; $r1=java.lang.System.out; $r1.println(''Hi!''); $r2=java.lang.System.out; $i3=i0*i1; $i4=$i3+i1; $r2.println($i4); label0: i2=i1*i0; label1: goto label3; label2: $r3 :=@caughtexception; r4=$r3; throw r4; label3: return; catch java.lang.Exception from label0 to label1 with label2; }
Local 变量
这个方法里的locals在该方法的最开始处:
java.lang.String[]r0;
int i0,i1,i2,$i3,$i4;
java.io.PrintStream$r1,$r2;
java.lang.Exception$r3,r4;
Local(s)被收集在localChain中,可通过body.getLocals()访问。每一种中间表示都有它自己对Local的实现;然后这些实现都得保证对于每一个定义Local r0,能够调用r0.getName(),r0.getType(),r0.setName()和r0.setType()。注意到local变量必须是有类型的。
Traps
为了支持Java的异常处理,Soot的Body定义了traps这一概念。其观点是在Java字节码中,异常处理程序是由一个多元组(异常、开始点、结束点、处理程序)表示;在开始和结束单元之间(包括开始处,但不包括结束处),如果异常被抛出,则执行处理程序。
在上面这个例子中,有一个trap:
catch java.lang.Exception from label0 to label1 with label2;
Units
Body中最有意思的部分即Unit(s)链(chain),这是Body中的实际代码。Jimple用Stmt实现了Unit,Grimp则用Inst来实现。这表明每个中间表示(IR)有它自己的对语句(statement)的概念。
Jimple Stmt中的一个例子便是AssignStmt,这代表一条Jimple的分配语句。AssignStmt可能是下面这种形式:
x = y + z;
Value
代码通常会对数据进行操作。为了表示数据,Soot提供了Value接口。下面是一些不同类型的Value(s):
n Local(s)
n Constant(s)
n 表达式(Expr)
n ParameterRef(s), CaughtExceptionRef(s)和 ThisRef(s)
Expr接口,反过来,有一整套实现;其中有NewExpr和AddExpr。通常,一个Expr会对一个或多个Value(s)执行一些动作并返回另一个Value。
下面是使用Value(s)的一个例子:
x = y + 2;
这是一个AssignStmt,左操作数是x,右操作数是y+2,右操作数是一个AddExpr。这个AddExpr反过来包含Value(s) y和2作为它的操作数;前一个操作数是一个Local,后一个操作数是一个Constant。
在Jimple中,我们强制要求所有的Value(s)最多包含一个表达式(contain at most 1 expression)。Grimp则没有这个限制,这使得其产生的代码很容易阅读但很难分析。
Boxes
Boxes在Soot中无处不在。主要要记住就是一个Box是一个指针,它提供对Soot对象的间接访问。
对于Box,更具描述性的名称可能是Ref。但不幸的是,在Soot中Ref代表其它的意思。
在Soot中有两类Box(es),分别为ValueBox和UnitBox。一个UnitBox包含Unit(s),一个ValueBox包含Value(s),在C++中,分别表示为(Unit*)和(Value*)。
下面将描述每一种Box。
UnitBox
某些Unit(s)可能需要包含对其它Unit(s)的引用。比如,一个GotoStmt需要知道它的目标是什么。因此,Soot提供了UnitBox,UnitBox是一个包含了一个Unit的Box。
考虑下面的Jimple代码:
x = 5;
goto l2;
y = 3;
l2: z = 9;
每个Unit都必须提供getUnitBoxes()方法,对于大多数UnitBox(es)而言,这个方法会返回一个空列表,对于GotoStmt而言,getUnitBoxes()方法将会返回包含一个元素的列表。包含一个指向l2的Box。
注意到对于SwitchStmt而言,通常getUnitBoxes()方法将会返回包含许多boxes的列表。
Box的概念对于修改代码而言最为有用。比如我们有下面一条语句s:
s: goto l2;
在l2处有一个Stmt:
l2: goto l3;
很明显,不管s的实际类型是什么,s可以指向l3而不指向l2。因此对于各种类型的
Unit(s),我们可以统一做下面的事情:
public void readjustJumps(Unit s, Unit oldU, Unit newU)
{
Iterator ubIt = s.getUnitBoxes.iterator();
while (ubIt.hasNext())
{
StmtBox tb = (StmtBox)ubIt.next();
Stmt targ = (Stmt)tb.getUnit();
if (targ == oldU)
tb.setUnit(newU);
}
}
Unit自身使用了一些与上面类似的代码,这样便能够创建
PatchingChain,
PatchingChain是对
Chain的一种实现,它能够调整那些指向将要从
Chain中删除的
Unit(s)的指针。
ValueBox
类似于Unit(s),我们经常需要指向Value的指针。这由ValueBox 类表示。对于一个Unit,我们可以获得ValueBox的列表,包括在这个Unit中已使用和被定义的值(values)。
我们可以利用这些boxes来进行常数合并(constant folding):对于一个AssignStmt,其计算一个AddExpr,将两个常数值相加,我们则可以静态的将它们相加然后把结果放置到UseBox。
下面是一个合并 AddExpr 的例子:
public void foldAdds(Unit u)
{
Iterator ubIt = u.getUseBoxes().iterator();
while (ubIt.hasNext())
{
ValueBox vb = (ValueBox) ubIt.next();
Value v = vb.getValue();
if (v instanceof AddExpr)
{
AddExpr ae = (AddExpr) v;
Value lo = ae.getOp1(), ro = ae.getOp2();
if (lo instanceof IntConstant && ro instanceof IntConstant)
{
IntConstant l = (IntConstant) lo,
r = (IntConstant) ro;
int sum = l.value + r.value;
vb.setValue(IntConstant.v(sum));
}
}
}
}
注意到不管
Unit 的类型是什么,这都适用。
Unit 重新访问
我们现在讨论任何Unit 都必须提供的几种不同的方法。
public List getUseBoxes();
public List getDefBoxes();
public List getUseAndDefBoxes();
上面三个方法返回在该
Unit被使用、被定义或二者兼有的
ValueBox(s)的一个
List(s),对于
getUseBoxes(),它返回所有被使用的值(values),包括表达式以及这些表达式的组成部分。
public List getUnitBoxes();
这个方法返回由该方法指向的的unit 的
UnitBox(es)的一个
List。
public List getBoxesPointingToThis();
这个方法返回
UnitBox(es)的列表,这些
UnitBox都指向此Unit(即调用这个方法的Unit)。
public boolean fallsThrough();
public boolean branches();
这些方法处理该
Unit之后的的执行流,前面一个方法返回真如果可以接着执行接下来的Unit,后面一个方法返回真如果执行流可能流入其他的Unit,这个Unit不是紧跟当前Unit之后的Unit。
public void redirectJumpsToThisTo(Unit newLocation);
这个方法使用
getBoxesPointingToThis()来改变所有跳转至该
Unit的跳转语句,将它们指向
newLocation。