示例Java代码
Copy import java . util . Scanner ;
public class test {
public static void main ( String [] args) {
Scanner myInput = new Scanner( System . in ) ;
int a = myInput . nextInt ();;
int y = 1 ;
while (y > 0 ) {
while (a > 50 ) {
a = a + 1 ;
y = y + 1 ;
if (a > 10 ) {
if (a > 100 ) {
a = a * 5 ;
break ;
} else {
y = y / a;
}
}
}
}
System . out . println (y);
}
}
编译成Smali后
Copy import java.util.Scanner;
.method public static main([Ljava/lang/String;)V
.registers 3
.line 5
p0 = new-instance java/util/Scanner;
sget-object v0, Ljava/lang/System;->in:Ljava/io/InputStream;
invoke-direct {p0, v0}, Ljava/util/Scanner;-><init>(Ljava/io/InputStream;)V
.line 6
invoke-virtual {p0}, Ljava/util/Scanner;->nextInt()I
move-result p0
.line 7
const/4 v0, 0x1
.line 8
:cond_c
:goto_c
if-lez v0, :cond_23
.line 9
:cond_e
:goto_e
const/16 v1, 0x32
if-le p0, v1, :cond_c
.line 10
add-int/lit8 p0, p0, 0x1
.line 11
add-int/lit8 v0, v0, 0x1
.line 12
const/16 v1, 0xa
if-le p0, v1, :cond_e
.line 13
const/16 v1, 0x64
if-le p0, v1, :cond_21
.line 14
mul-int/lit8 p0, p0, 0x5
.line 15
goto :goto_c
.line 17
:cond_21
div-int/2addr v0, p0
goto :goto_e
.line 23
:cond_23
sget-object p0, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-virtual {p0, v0}, Ljava/io/PrintStream;->println(I)V
.line 24
return-void
.end method
虽然相比于汇编,字节码还是很人性化的,为了观感,将这个代码进行优化
1、暂时不考虑try-exception-catch的情况
2、优化跳转指令展示,if + goto-label形式
3、将函数调用转换为便于阅读的形式,优化寄存器类型信息
4、删除冗余代码,将字节码指令优化,改为适合人类阅读的形态
现在,这个代码变成了这个样子
Copy public static void main( java . lang . String [] r2) {
java . util . Scanner r2 = new java . util . Scanner
java . io . InputStream r0 = java . lang . System . in
r2 . < init > (r0)
int r2 = r2 . nextInt ()
r0 = 1
Lc :
if (r0 <= 0 ) goto L23
Le :
r1 = 50
if (r2 <= r1) goto Lc
int r2 = r2 + 1
int r0 = r0 + 1
r1 = 10
if (r2 <= r1) goto Le
r1 = 100
if (r2 <= r1) goto L21
int r2 = r2 * 5
goto Lc
L21 :
int r0 = r0 / r2
goto Le
L23 :
java . io . PrintStream r2 = java . lang . System . out
r2 . println (r0)
return
}
为了更好的理解,我简单的画了一下对应,当然稍微偷懒了省略了一写
好了已经很有伪代码的样子了,是啊,字节码就是这么轻松,不用解决各种麻烦的汇编转换的麻烦,再画一下CFG图
下一步,通过SSA+PHI还原变量,没有定义一个IR而是直接操作了原有的smali代码
1、生成CFG图
2、构建支配树、支配边界、:计算支配边界用于构造SSA, 循环不变量提取
算法来源:<http://www.hipersoft.rice.edu/grads/publications/dom14.pdf*
>
3.1、循环识别
3.2、构造SSA
在basic-block上构建寄存器的def-use结构,这里我给出上面代码的def-use结构
register define-use-pair
R0:(1,1) (1,2) (1,4) (1,8) (4,4) (4,2) (4,7) (4,8) (7,2) (7,4) (7,8)
R1: (3,3) (4,4) (5,5)
R2: (1,1) (1,4) (4,4) (4,5) (4,6) (6,4) (8,8)
基于def-use-pair,进行活跃变量分析(Live Variable Analysis)删除冗余的寄存器分配
也即是说存在两种情况,第一种,变量被定义了却没有被使用过(这里不存在),第二种,变量的定义与使用都在同一个基本块内,很明显,R1寄存器就是这种情况,这种寄存器将被活跃变量分析处理后inline掉
活跃变量分析后的register define-use-pair
R0: (1,2) (1,4) (1,8) (4,2) (4,7) (4,8) (7,2) (7,4) (7,8)
R2: (1,4) (4,5) (4,6) (6,4)
基于支配边界分配寄存器的PHI,即在变量定义的基本块的支配边界添加PHI,直到不动点(TODO,这个描述过于抽象,是否有办法更加简略一点),这里只要处理R1和R2:
关于支配边界+phi的构造关系,北大的熊英飞老师的课程有提供,虽然讲的不算详细,但是够用了
R0支配边界计算过程:
DF{1,4,7} ⇒ {3} : DF{1} = {}; DF{4} = {3}; DF{7} = {3};
DF1{1,3,4,7} ⇒ {2,3} : DF{3} = {2}
DF2{1,2,3,4,7} ⇒ {2,3} : DF{2} = {}
R2计算过程:
DF{1,4,6} ⇒ {2,3} : DF{1} = {}; DF{4} = {3}; DF{6} = {2};
DF1{1,2,3,4,6} ⇒ {2,3}: DF{3} = {2}; DF{2} = {}
按照支配边界转换顺序,构建SSA并在BasicBlock 2与3中插入phi节点,插入并清理R1的残留后,带有SSA的结构如下
生成代码
Copy public static void main(String[] strArr) {
int nextInt = new Scanner(System.in).nextInt();
int i = 1;
L2:
if (i <= 0) goto L20;
L4:
if (nextInt <= 50) goto L2;
nextInt = nextInt + 1;
i = i + 1;
if (nextInt <= 10) goto L4;
if (nextInt > 100) goto L14;
i = i / nextInt;
goto L4
L14:
nextInt = nextInt * 5;
goto L2
L20:
System.out.println(i);
}
最终翻译为伪代码
Copy public static void main(String[] strArr) {
int nextInt = new Scanner(System.in).nextInt();
int i = 1;
while (i > 0) {
while (true) {
if (nextInt > 50) {
nextInt++;
i++;
if (nextInt > 10) {
if (nextInt > 100) {
nextInt *= 5;
break;
}
i /= nextInt;
}
}
}
}
System.out.println(i);
}
Last updated 2 months ago