1 概论
1.1 课题的研究意义,国内外研究现状、水平和发展趋势
通过对源码的分析更加熟练地掌握 Java 程序设计语言、掌握 Java 语言
的规范、学习程序设计框架与程序设计技巧以及提高源代码的分析能力,
同时学会使用该软件应用与软件分析与优化中。
由于 Java 的字节码通常是在虚拟机上解释执行的,因此速度比较慢。
一般来说,解释执行的速度比编译成本地码后的速度要慢一个数量级。既使
使用了及时编译(Just-in-time)技术,也要慢 2 倍~4 倍。这对于 Java Applet
来说也许并不是什么问题,但是对于需要高性能的应用程序,例如计算密
集型应用程序或服务器端程序,在某种程度上却是不能忍受的。虽然 Java
在诸多方面都有着其它语言不可比拟的优势,但是要使 Java 真正替代 C、
C++成为网络计算语言,就必须解决性能的问题。一个重要的思路便是将成
熟的编译技术移植到 Java 上,进行代码优化。
所谓代码优化是指对程序代码进行等价变换。程序代码可以是中
间代码(如四元式代码),也可以是目标代码。等价的含义是使得变换
后的代码运行结果与变换前代码运行结果相同。优化的含义是最终生
成的目标代码短(而运行速度快),时空效率优化。 编译过程中可进行
的优化可按阶段划分,优化可在编译的不同阶段进行,分为中间代码
一级和目标代码一级的优化。可按优化涉及的程序范围划分,对同一
阶段,分为局部优化,循环优化和全局优化。进行优化所需要的基础
是对代码进行数据流分析和控制流分析。如划分 DAG,查找循环,分
析变量的定值点和引用点等等。最常用的代码优化技术有删除多余运
算,循环不变代码外提,强度削弱,变换循环控制条件,合并已知量
与复写传播,以及删除无用赋值等等。
Soot 就是由 McGill 大学的 Sable 研究组编写的一个用于代码优化的软
件。
1.2 可行性分析
对于一个 Java 程序来说经常出现数组越界,空指针等异常甚至是错误,
虽然说 Java 语言有捕获异常机制,但是在程序中我们最好尽可能的消除这
些异常。虽然对于小的程序来说我们可以通过 Java 编译后的错误报告来查
找发生异常或出现错误的地方,但是随着程序的增大工作量将急剧增加。
然而 Eclipse 中的 Soot 插件提供了良好的用户交互界面,使得程序员可以在
可视化的界面中查看分析的结果,如本地变量是否为空。
如 Soot 提供了空指针分析和空指针着色功能:首先通过空指针分析和
着色分析后生成相应的 Jimple 码(一般都是生成 Jimple 码,也可以生成其
它中间表示的格式),然后在 Eclipse 中打开所生成.jimple 文件,程序中所
使用的变量就会被着色,关于空指针共分 3 种情况:肯定为空、肯定不为
空和不确定是否为空,3 种情况分别用红色、绿色、蓝色标注出来。如果我
们打开 Java 源文件,当把光标移到相应的变量上时也会出现变量是否为空
的提示信息。
通过 Soot 还可以进行别名分析等复杂的数据流分析。
Soot 还可以对程序进行优化,如通过优化可以检测出程序中的常量,
以及一些无用的循环或者 if 条件语句,并且如果是通过 Shimple 优化后得
到的 Jimple 码,程序编译后可以把这些冗余的变量和语句删除
Soot 是一个优秀的 Java 数据分析和优化的工具,因此有必要对其框架
结构进行分析,并且了解它的安装和使用方法,为以后的使用做好准备。
2 Soot 的总体框架
本章将简略的介绍 Soot 的总体结构、Soot 的文件组成、Soot 中主要的
数据结构、Soot 的优化框架结构以及总体流程。
2.1 Soot 简介
Java 是一种先将源程序编译成字节码,再在虚拟机上解释执行字节码
的面向对象的语言。Java 的平台独立、类型安全及垃圾回收等特性使得其
被广泛应用。但与 C/C++相比 Java 程序的运行要慢的多,为此许多机构已
经或正在开展对 Java 程序优化的研究。为简化 Java 字节码优化工具开放,
McGill 大学的 Sable 研究组研制了名为 Soot 的工具。它为字节码的分析、
优化、反编译、注解等提供一个可扩展的框架;它本身实现了不少优化变
换算法,因此也可以单独使用。2.0 版本以上的 Soot 还包含一个 Eclipse 插
件。
2.2 Soot 概览
Soot 的总体结构如图 2-1 所示:
图 2-1 Soot 的总体结构
该图表示 Soot 的输入文件有 3 种:.class 文件、.jimple 文件和.java 文件,
然后通过编译、分析和变换、添加标记等操作最后输出不同格式的文件,
输出的文件也有 3 种:.class 文件、.java 文件以及.jimple 和.xml 文件。
但是 Soot 以 Java 源程序或字节码(class 文件)等为输入,以优化的
class 文件为输出;其分析和变换建立在内部的中间表示(IR)之上,它们
可以是局部的(仅对某方法体),也可以是全局的(对整个程序)。早期的
Soot 调用 javac 将源程序编译成字节码在处理;近来则用 Polyglot 编译 Java
源程序(见 JavaToJimple 类),再构造 JimpleIR。另外,Soot 本身不直接生
成字节码,而是先生成 Jasmin 汇编码,再调用 Jasmin 汇编器生成字节码(见
JasminOutputStream 类)。
Jimple 是 Soot 的核心中间表示,是一种类型化的三地址无栈表示。此
外,Soot 还提供了 4 种中间表示用于分析和变换:(1)Baf 是基于栈的字节
码的简洁表示;(2)Grimp 是 Jimple 的聚合版本,适于反编译和代码审查;
(3)Shimple 是 Jimple 的 SSA(static single assignment)变种;(4)Dava
是适用于反编译的结构化表示。这些中间表示之间可以相互转化。
要在 Soot 上开展优化变换,必需进一步分析 Soot 源码,以了解其主要
的数据结构、优化框架的组织和已有的变换一其实现机制。
2.3 Soot 剖析
Soot2.2.3 有近 2000 个 Java 文件,按其是否为自动生成分别存放在
generated 和 src 目录中,文件组织复杂。所有类的包路径以 soot 为根,
soot.Main 是主类。Soot 包中定义有为各种中间表示共享的基本类,并含有
13 个子包:baf、dava、grimp、jimple、shimple 包分别包含对应的中间表示
的表示及变换类;javaToJimple 包实现 Java 到 Jimple 的转化;options 包提
供对命令行参数的分析;tagkit 包支持对字节码的注解;coffi 包来自 Coffi
工具;toolkits 包提供如控制流图 CFG、流分析等工具包;tools 包存放 Soot
扩展,它们可以取代 soot.Main 被执行;util、xml 包提供其它有用的类。
类 soot.G 收集 Soot 的全局变量,大部分为类 Singletons(由
make_singletons 自动生成)收集的 singleton 对象,可通过相应类中的 v 方
法取得。
2.3.1 主要的数据结构
类 Scene、SootClass、SootMethod、SootField 和 Body 是 SootField 的主
要数据结构。Scene 表示整个分析变换的环境,其主要的域如图 2-2 所示:
图 2-2 Soot 类的主要域
SootClass 表示装入 Soot 或由 Soot 创建的单个类。SootMethod 表示类
中的单个方法。SootField 表示类中的一个域。Body 表示一个方法体,不同
的中间表示由它派生不同的子类,见图 2-3 所示:
图 2-3 Body 及其子类
Body 类包含域 localChain、trapChain 和 unitChain,分别代表局部变量、
Trap 和 Unit 3 个链。Trap 是描述异常捕获的接口。Unit 是描述代码片段的
接口,子接口 Inst 和 Stmt 分别表示 Baf 中间表示上的指令和其他中间表示
上的语句。许多类由 Unit 的抽象子类 AbstractUnit 派生并实现 Inst 或 Stmt
接口,表示具体的指令或语句类。
接口 ValueBox 和 UnitBox 分别提供对值引用和语句引用的封装。值的
公共接口是 Value。通过 Unit 可以取得语句中定义或使用的值;取得跳到该
unit 的各 unit,以及由该 unit 跳到的各 unit;还可以查询分支行为。
2.3.2 优化框架结构
为便于扩展,Soot 采用以 Pack 为中心的优化框架。Pack 是一套优化变
换的包装类,其 opts 域收集各变换对象(为 Transform 实例),方法 apply
调用 internalApply 执行所包装的各种变换。Pack 由 BodyPack 和 ScenePack
两个子类,继承前者的类包装方法体内的变换,继承后者的类则是包装全
局变换。各 Pack 实现类需重写 internalApply 方法。
类 Transform 维护二元组<阶段名,变换器>。以 Transformer 为基类的
变换器是实现从字节码到中间表示以及中间表示间的转化,或基于中间表
示的分析、优化、反编译、标注等的类。和 Pack 类似,它有 BodyTransformer
和 SceneTransformer 两个子类。变换由类中的 Transform 方法调用
internalTransform 完成,各 Transformer 实现类需要重写 internalTransform 方
法,不论是全局还是局部变换,一般都是对方法体中的语句逐条分析变换
的,故操作的核心是 Unit 实例。
各种 Pack 由类 PackManager 管理,其 init 方法负责创建各 Pack 实例对
象,并为之添加变换器。图 2-4 中列举了 Soot 中的部分 Pack。
图 2-4 部分 Pack
Pack名所属的Pack类说明jbJimpleBodyPack(BodyPack的子类)创建Jimple体jjJavaToJimpleBodyPack(BodyPack的子类)实现Java到Jimple的转换cgCallGraphPack(由ScenePack派生)调用图生成、指针分析、类层析分析(CHA)wstpScenePack全局Shimple变换包wsopScenePack全局Shimple优化包wjtpScenePack全局Jimple变换包wjopScenePack全局Jimple优化包wjapScenePack全局Jimple注解包jtpBodyPackJimple变换包jopBodyPackJimple优化包japBodyPackJimple注释包tagBodyPack代码属性tag聚集包
2.3.3 总体流程
soot.Main.run(args)反映 Soot 的主要流程,步骤如下:
(1) 调用 Options.v().parse(args)解析参数并设置全局选项对象。
(2) 调用 Scene.v().loadNecessaryClasses()装载必需的类,包括
基本类、库中的类和待分析应用中的类,并设置全局选项对象。
(3) 调用 PackManager.v().runPacks()执行各 Pack 变换,步骤为:
1) 由选项决定是否执行行号变换(LineNumberAdder);
2) 由选项决定是否调用 runWholeProgramPacks 进行全局变
换,它除执行行名为 cg 的 Pack 外,还执行当前中间表示有
关的全局 Pack;
3) 调用 retrieverAllBodyes 确认方法体全部载入;
4) 由选项决定是否预处理 Dava 类和设置交互模式;
5) 执行各方法体内的变换 runBodyPacks();
6) 处理内部类;
(4) 调用 PackManager.v().writeOutput()输出结果。
3 详细设计
本章将详细的介绍 Soot 的部分数据结构、四种中间表示以及 Soot 框架
的一些用法。
3.1 Body 的详细分析
Soot 利用一个 Body 来储存一个方法的代码。Soot 中有三种 Body 即
BafBody, JimpleBody and GrimpBody,每个对应一个中间表示。
一个 chain 是一种类似列表的数据结构,它提供了固定次数的获取、插
入、移除 chain 中元素。Body 中三个主要的 chain 是:Units chain,Locals chain
和 Traps chain。下面的例子将说明每个 chain 的作用。
考虑下面这个 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;