我正在尝试检测Java字节码。
我想识别 java循环 的 进入和退出 ,但是我发现识别循环非常具有挑战性。我花了很多时间研究 ASM 和 开源反编译器
(我一直认为自己必须始终解决此问题),但是我的想法很短。
我正在扩充/扩展的工具正在使用ASM,因此理想情况下, 我想知道如何通过ASM在Java中检测不同循环结构的进入和退出
。但是,我也欢迎关于一个好的开源反编译器的建议,因为显然他们可以解决相同的问题。
编辑4 :一些背景/前言。
0: goto 2
1: goto 3
2: goto 1
当然,这个特定示例非常人为,有点愚蠢。但是,对源代码到字节码编译器的行为进行假设可能会导致意外。正如Peter和我在各自的答案中所表明的那样,两个流行的编译器可以产生截然不同的输出(即使没有混淆)。几乎没有关系,因为在执行代码时,JIT编译器会很好地优化所有这些功能。话虽如此,在大多数情况下,向后跳将是循环从何处开始的合理指示。与其余的相比,找出循环的入口点是“容易”的部分。
break
语句(有时带有标签),return
语句和/或异常(是否被明确捕获)引起的。尽管您没有提供有关正在调查的工具类型的详细信息,但是当然值得考虑您要在何处插入代码(如果您要这样做)。通常,可能需要在每个exit语句之前或每个后继语句之前进行某种检测(在这种情况下,您必须移动原始语句)。煤烟是一个很好的框架。它具有许多中间表示形式,使字节码分析更加方便(例如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;
}
这次,使用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
在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);
}
}
在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
在循环中执行任何操作之前,请直接从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);
}
}
}
}
由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
这是一个使用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());
}
}
}
查看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;
}
方块:
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;
循环:
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
LoopNestTree
使用LoopFinder
,它使用ExceptionalBlockGraph
来构建块列表。该Loop
班会给你进入语句和退出声明。然后,您可以根据需要添加其他语句。Jimple对此非常方便(它与字节码足够接近,但级别略高,以免手动处理所有内容)。然后,.class
如果需要,您可以输出修改后的文件。(有关此信息,请参见Soot文档。)
问题内容: 我现在专注于需要深入了解Java字节码的项目。 借助于bcel,我现在可以完成大部分工作。我现在不清楚的一点是如何识别子类方法覆盖其基本代码?文件中是否记录了与方法相关联的任何属性,表明该重写关系,或者我应该返回其基类可以比较方法签名吗? 任何提示将不胜感激。 问题答案: 您需要查找层次结构链-字节码中没有任何内容表明它是被重写的方法,因为不需要。
我正在尝试识别使用for循环提供的输入中是否有数字。我已经尝试了很长时间,甚至在Google的第二页上进行了搜索。我决定寻求一些帮助。到目前为止,我已经尝试了以下内容: 基本上,我试着扫描每个字母,然后分别测试它们是字母还是数字,这就是我遇到的问题,因为只扫描第一个字母。我也试着在循环中输入“c”。我怎么扫描每封信? 编辑:哎呀,那是个错误。我把它改成c=c 1,但它不起作用,我把它改回c,但它最
本文向大家介绍字节码和机器码的区别相关面试题,主要包含被问及字节码和机器码的区别时的应答技巧和注意事项,需要的朋友参考一下 机器码,学名机器语言指令,有时也被称为原生码,是电脑的CPU可直接解读的数据。 字节码是一种中间状态(中间码)的二进制代码(文件)。需要直译器转译后才能成为机器码。 -----------
我正在编写一个程序,处理两个原型消息,我需要处理从不同来源发送的字节[],这些来源发送foo消息或bar消息。由于我无法弄清楚它属于哪个消息,我使用任何类(附带的协议)来解析字节数组并找到它属于哪个类,但遇到了编译时错误。如果我将来添加更多的原型消息类,我可以使用其他方法来检测吗? 第二个原型 代码: 尝试调用any时if语句出错。is(): 方法是(类
问题内容: 我想识别Java套接字 中数据流的结尾 。当我运行下面的代码时,它只是卡住并继续运行(它卡在value上)。 我也希望该程序下载二进制文件,但是最后一个字节始终是不同的,因此我不知道如何(实用地)停止 一会儿 。 感谢您的任何提示。 问题答案: 您应该将现有库用于HTTP。看这里。 您的代码按预期工作。服务器不会关闭连接,并且永远不会变为。发生这种情况是因为默认情况下,连接在HTTP
问题内容: 给定以下代码示例: 这是一个有效的Java程序,尽管doSomething()方法应返回一个int,但永远不会返回。如果运行它,它将以无限循环结束。如果将while循环的参数放在单独的变量中(例如boolean bool = true),则编译器将告诉您在此方法中返回int。 所以我的问题是:这在Java规范中是否存在,是否存在这种行为可能有用的情况? 问题答案: 我将引用Java语言