use*_*203 17 java instrumentation loops bytecode
我正在尝试设法java字节码.
我想识别java循环的进入和退出,但我发现循环的识别非常具有挑战性.我花了几个小时看ASM和开源反编译器(我认为他们必须一直解决这个问题)但是,我做得很短.
我正在扩充/扩展的工具是使用ASM,所以理想情况下我想知道如何通过ASM来检测java中不同循环结构的进入和退出.但是,我也欢迎建议一个好的开源解编译器,因为显然它们可以解决同样的问题.
Bru*_*uno 21
编辑4:一点背景/序言.
" 在代码中向后跳的唯一方法是通过一个循环. "在Peter的回答中并不完全正确.你可以在没有它的情况下来回跳跃意味着它是一个循环.一个简化的案例是这样的:
0: goto 2
1: goto 3
2: goto 1
Run Code Online (Sandbox Code Playgroud)
当然,这个特殊的例子非常人为,有点傻.但是,假设源到字节码编译器将如何表现可能会导致意外.正如彼得和我在各自的答案中所表明的那样,两个流行的编译器可以产生相当不同的输出(即使没有混淆).它很少重要,因为当您执行代码时,所有这些都会被JIT编译器很好地优化.这就是说,在绝大多数情况下,向后跳跃将是一个关于循环开始位置的合理指示.与其余部分相比,找出循环的切入点是"简单"部分.
在考虑任何循环启动/退出检测之前,您应该查看入口,出口和后继的定义.虽然循环只会有一个入口点,它可能有多个出口和/或多个继任者,通常由引起break语句(有时与标签),return陈述和/或异常(明确抓与否).虽然您没有提供有关您正在调查的仪器类型的详细信息,但是当然您还需要考虑插入代码的位置(如果这是您想要做的).通常,某些检测可能必须在每个退出语句之前完成,或者代替每个后继语句(在这种情况下,您必须移动原始语句).
Soot是一个很好的框架来做到这一点.它有许多中间表示,使字节码分析更方便(例如Jimple).
您可以根据方法体构建BlockGraph,例如ExceptionalBlockGraph.一旦您将控制流图分解为这样的块图,从节点中,您应该能够识别支配者(即有箭头回到它们的块).这将为您提供循环的开始.
您可以在本文的第4.3至4.7节中找到类似的内容.
编辑:
与@Peter讨论后,他的回答是评论.说同样的例子:
public int foo(int i, int j) {
while (true) {
try {
while (i < j)
i = j++ / i;
} catch (RuntimeException re) {
i = 10;
continue;
}
break;
}
return j;
}
Run Code Online (Sandbox Code Playgroud)
这一次,使用Eclipse编译器编译(没有特定选项:只需从IDE中自动编译).这段代码没有被混淆(除了代码不好,但这是另一回事).结果如下(来自javap -c):
public int foo(int, int);
Code:
0: goto 10
3: iload_2
4: iinc 2, 1
7: iload_1
8: idiv
9: istore_1
10: iload_1
11: iload_2
12: if_icmplt 3
15: goto 25
18: astore_3
19: bipush 10
21: istore_1
22: goto 10
25: iload_2
26: ireturn
Exception table:
from to target type
0 15 18 Class java/lang/RuntimeException
Run Code Online (Sandbox Code Playgroud)
有一个介于3和12之间的循环(从10开始跳转)和另一个循环,因为从8到22的除零发生异常.与javac编译器结果不同,可以猜测有一个外部在0到22之间循环,在0到12之间循环,这里的嵌套不太明显.
编辑2:
用一个不那么尴尬的例子来说明你可能遇到的问题.这是一个相对简单的循环:
public void foo2() {
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
}
Run Code Online (Sandbox Code Playgroud)
在Eclipse中进行(正常)编译之后,javap -c给出:
public void foo2();
Code:
0: iconst_0
1: istore_1
2: goto 15
5: getstatic #25; //Field java/lang/System.out:Ljava/io/PrintStream;
8: iload_1
9: invokevirtual #31; //Method java/io/PrintStream.println:(I)V
12: iinc 1, 1
15: iload_1
16: iconst_5
17: if_icmplt 5
20: return
Run Code Online (Sandbox Code Playgroud)
在循环中执行任何操作之前,直接从2跳到15.块15到17是循环的标题("入口点").有时,标头块可能包含更多指令,特别是如果退出条件涉及更多评估,或者它是do {} while()循环."入口"和循环的"退出"的概念可能并不总是反映你理智写的东西像Java源代码(包括事实,你可以重写for循环的while回路,例如).使用break也可以导致多个退出点.
顺便说一句,通过"块",我的意思是一个字节码序列,你不能跳到其中,你不能跳到中间:它们只是从第一行输入(不一定是从前一行输入)线,可能来自其他地方的跳跃)并从最后一个退出(不一定到下一行,它也可以跳到其他地方).
编辑3:
自从我上次看到Soot以来,似乎已经添加了用于分析循环的新类/方法,这使得它更方便.
这是一个完整的例子.
要分析的类/方法(TestLoop.foo())
public class TestLoop {
public void foo() {
for (int j = 0; j < 2; j++) {
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
当由Eclipse编译器编译时,这会产生这个字节码(javap -c):
public void foo();
Code:
0: iconst_0
1: istore_1
2: goto 28
5: iconst_0
6: istore_2
7: goto 20
10: getstatic #25; //Field java/lang/System.out:Ljava/io/PrintStream;
13: iload_2
14: invokevirtual #31; //Method java/io/PrintStream.println:(I)V
17: iinc 2, 1
20: iload_2
21: iconst_5
22: if_icmplt 10
25: iinc 1, 1
28: iload_1
29: iconst_2
30: if_icmplt 5
33: return
Run Code Online (Sandbox Code Playgroud)
这是一个程序,使用Soot加载类(假设它在类路径上)并显示其块和循环:
import soot.Body;
import soot.Scene;
import soot.SootClass;
import soot.SootMethod;
import soot.jimple.toolkits.annotation.logic.Loop;
import soot.toolkits.graph.Block;
import soot.toolkits.graph.BlockGraph;
import soot.toolkits.graph.ExceptionalBlockGraph;
import soot.toolkits.graph.LoopNestTree;
public class DisplayLoops {
public static void main(String[] args) throws Exception {
SootClass sootClass = Scene.v().loadClassAndSupport("TestLoop");
sootClass.setApplicationClass();
Body body = null;
for (SootMethod method : sootClass.getMethods()) {
if (method.getName().equals("foo")) {
if (method.isConcrete()) {
body = method.retrieveActiveBody();
break;
}
}
}
System.out.println("**** Body ****");
System.out.println(body);
System.out.println();
System.out.println("**** Blocks ****");
BlockGraph blockGraph = new ExceptionalBlockGraph(body);
for (Block block : blockGraph.getBlocks()) {
System.out.println(block);
}
System.out.println();
System.out.println("**** Loops ****");
LoopNestTree loopNestTree = new LoopNestTree(body);
for (Loop loop : loopNestTree) {
System.out.println("Found a loop with head: " + loop.getHead());
}
}
}
Run Code Online (Sandbox Code Playgroud)
有关如何加载类的更多详细信息,请查看Soot文档.它Body是循环体的模型,即从字节码生成的所有语句.这使用了中间的Jimple表示,它相当于字节码,但更容易分析和处理.
这是该程序的输出:
身体:
public void foo()
{
TestLoop r0;
int i0, i1;
java.io.PrintStream $r1;
r0 := @this: TestLoop;
i0 = 0;
goto label3;
label0:
i1 = 0;
goto label2;
label1:
$r1 = <java.lang.System: java.io.PrintStream out>;
virtualinvoke $r1.<java.io.PrintStream: void println(int)>(i1);
i1 = i1 + 1;
label2:
if i1 < 5 goto label1;
i0 = i0 + 1;
label3:
if i0 < 2 goto label0;
return;
}
Run Code Online (Sandbox Code Playgroud)
块:
Block 0:
[preds: ] [succs: 5 ]
r0 := @this: TestLoop;
i0 = 0;
goto [?= (branch)];
Block 1:
[preds: 5 ] [succs: 3 ]
i1 = 0;
goto [?= (branch)];
Block 2:
[preds: 3 ] [succs: 3 ]
$r1 = <java.lang.System: java.io.PrintStream out>;
virtualinvoke $r1.<java.io.PrintStream: void println(int)>(i1);
i1 = i1 + 1;
Block 3:
[preds: 1 2 ] [succs: 4 2 ]
if i1 < 5 goto $r1 = <java.lang.System: java.io.PrintStream out>;
Block 4:
[preds: 3 ] [succs: 5 ]
i0 = i0 + 1;
Block 5:
[preds: 0 4 ] [succs: 6 1 ]
if i0 < 2 goto i1 = 0;
Block 6:
[preds: 5 ] [succs: ]
return;
Run Code Online (Sandbox Code Playgroud)
循环:
Found a loop with head: if i1 < 5 goto $r1 = <java.lang.System: java.io.PrintStream out>
Found a loop with head: if i0 < 2 goto i1 = 0
Run Code Online (Sandbox Code Playgroud)
LoopNestTreeuses LoopFinder,使用an ExceptionalBlockGraph来构建块列表.该Loop班会给你进入语句和退出声明.如果您愿意,您应该能够添加额外的声明.Jimple非常方便(它与字节码足够接近,但有一个稍高的级别,以免手动处理所有内容).然后,您可以.class根据需要输出修改后的文件.(有关此内容,请参阅Soot文档.)
在代码中向后跳转的唯一方法是通过循环.所以你正在寻找一个转到前一个字节代码指令的goto,if_icmplt等.一旦找到循环结束并且它跳回到的位置就是循环的开始.
这是一个复杂的例子,来自Bruno建议的文件.
public int foo(int i, int j) {
while (true) {
try {
while (i < j)
i = j++ / i;
} catch (RuntimeException re) {
i = 10;
continue;
}
break;
}
return j;
}
Run Code Online (Sandbox Code Playgroud)
其字节代码显示javap -c为
public int foo(int, int);
Code:
0: iload_1
1: iload_2
2: if_icmpge 15
5: iload_2
6: iinc 2, 1
9: iload_1
10: idiv
11: istore_1
12: goto 0
15: goto 25
18: astore_3
19: bipush 10
21: istore_1
22: goto 0
25: iload_2
26: ireturn
Exception table:
from to target type
0 15 18 Class java/lang/RuntimeException
Run Code Online (Sandbox Code Playgroud)
你可以看到0到12之间有一个内部循环,0到15之间有一个try/catch块,0到22之间有一个外部循环.