Jimple 学习
jimple 是一种中间代码,用来简化分析,以及简化向 java 字节码的过渡。
jimple 是一种类型化的、三地址的、基于语句的中间代码
int i;
将上面代码转化为 jimple 之后,类型依旧保留,并没有丢失
int i;
Jimple 只需要 15 条语句指令即可,而 Java 字节码所需的指令多达 200 条,使代码的操作更加方便。
Jimple语句类型
-
核心语句:NopStmt, DefinitionStmt(IdentityStmt, AssignStmt)
-
负责过程内控制流的语句:IfStmt , GotoStmt , TableSwitchStmt , LookupSwitchStmt
-
负责过程间的控制流语句:InvokeStmt , ReturnStmt , ReturnVoidStmt.
-
监控语句:EnterMonitorStmt , ExitMonitorStmt.
-
ThrowStmt , RetStmt
Jimple 转 Java
public class Lab1 extends java.lang.Object
{
public void <init>()
{
Lab1 r0;
r0 := @this: Lab1;
specialinvoke r0.<java.lang.Object: void <init>()>();
return;
}
...
这里类代码的Jimple不仅要将类名写完整,还要将继承的类显式写出来,以保全语义信息。
源程序中没有显式给出构造函数,Jimple中的<init>
是默认生成的构造函数,然后$r0
指向this,再用specialinvoke
调用其父类(这里是Object
,见方法签名)的构造函数。
JVM 里四种主要方法调用:
- specialinvoke:用于调用构造方法、父类方法、私有方法
- virtualinvoke:用于调用普通的成员方法,进行virtual dispatch
- interfaceinvoke:用于调用继承的接口的方法,不能做优化,需要检查是否实现了接口中的方法
- staticinvoke:用于调用静态方法
在上面的 Jimple 代码中方法调用(invoke)的地方,尖括号<
和>
之间的内容是方法签名(Method Signature),它一般会包含方法所在的类名、方法的返回值类型、形参列表中各个参数的类型,有些还会包含方法名。
public static void main(java.lang.String[])
{
Lab1 $r0;
java.lang.String[] r2;
r2 := @parameter0: java.lang.String[]; //IdentityStmt
$r0 = new Lab1;
specialinvoke $r0.<Lab1: void <init>()>();
virtualinvoke $r0.<Lab1: void foo(int,int)>(5, 8);
return;
}
main 函数中 r2 := @parameter0: java.lang.String[];
表示变量 r2 是该方法的第一个参数,其类型为 java.lang.String[]
,其中带 $ 的变量表示其在栈内位置,并不是源程序中的局部变量,源程序中的局部变量名与 Jimple 中的变量名相同。
specialinvoke 对 Lab1 类进行初始化,virtualinvke 调用 foo 函数,传参为 5、8。
所以可以得到源程序为
public static void main(String[] args){
new Lab1().foot(5, 8);
}
public void foo(int, int)
{
int i0, i1;
Lab1 r0;
boolean $z0;
java.io.PrintStream $r1, $r2, $r3;
r0 := @this: Lab1;
i0 := @parameter0: int;
i1 := @parameter1: int;
if i0 != i1 goto label1;
$r3 = <java.lang.System: java.io.PrintStream out>;
virtualinvoke $r3.<java.io.PrintStream: void println(java.lang.String)>("a == b");
goto label3;
label1:
$z0 = virtualinvoke r0.<Lab1: boolean lt(int,int)>(i0, i1);
if $z0 == 0 goto label2;
$r2 = <java.lang.System: java.io.PrintStream out>;
virtualinvoke $r2.<java.io.PrintStream: void println(java.lang.String)>("a > b");
goto label3;
label2:
$r1 = <java.lang.System: java.io.PrintStream out>;
virtualinvoke $r1.<java.io.PrintStream: void println(java.lang.String)>("a < b");
label3:
return;
}
i0 和 i1 分别对应于传进来的两个参数,其中语句 if i0 != i1 goto label1;
判断两个参数是否相等。
virtualinvoke $r3.<java.io.PrintStream: void println(java.lang.String)>("a == b");
对应于 System.out.println
函数。
对于 label1 中情况:
首先调用 lt 函数,然后判断返回值是否为 0,然后调用 System.out.println
函数。
对于 label2 中情况:
直接调用 System.out.println
函数。
所以可以得到源程序为
public void foo(int a, int b) {
if (a == b) {
System.out.println("a == b");
} else if (lt(a, b)) {
System.out.println("a > b");
} else {
System.out.println("a < b");
}
}
public boolean lt(int, int)
{
int i0, i1;
Lab1 r0;
r0 := @this: Lab1;
i0 := @parameter0: int;
i1 := @parameter1: int;
if i0 <= i1 goto label1;
return 1;
label1:
return 0;
}
ro 对应 Lab1 类,i0 和 i1 对应两个输入的参数,然后判断两个数值大小来进行返回,比较简单的大小判断,源程序为
public boolean lt(int a, int b) {
if (a > b) {
return true;
}
return false;
}