我正在尝试使用java字节码。
我想识别进入和退出java循环,但我发现循环的识别非常具有挑战性。 我花了好几个小时看着 ASM 和开源反编译器(我认为他们必须一直解决这个问题),但是,我做得很短。
我正在扩充/扩展的工具正在使用ASM,所以理想情况下我想知道如何通过ASM 来检测java中不同循环结构的进入和退出。但是,我也欢迎建议一个好的开源反编译器,因为很明显他们会解决同样的问题。
答案 0 :(得分:21)
答案 1 :(得分:6)
在代码中向后跳转的唯一方法是通过循环。所以你正在寻找一个转到前一个字节代码指令的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;
}
此字节代码显示在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
你可以看到0到12之间有一个内部循环,0到15之间有一个try / catch块,0到22之间有一个外部循环。
答案 2 :(得分:0)
你实际上是逐字节构建你的类吗?多奇怪! ASM的首页链接到Eclipse的Bytecode Outline插件,我假设您正在使用它。如果你点击那里的第一个图像,你会发现代码有一个while循环,你可以看到至少一些用于实现该循环的字节代码。这里的参考是截图:
看起来循环只是通过边界检查实现为GOTO。我在谈论这一行:
L2 (173)
GOTO L3
我确信L3标记具有用于检查索引边界的代码,并且决定了JMP。我想如果你想一次循环一个字节代码,这对你来说会很困难。 ASM可以选择使用模板类作为检测的基础,您尝试过使用它吗?
答案 3 :(得分:0)
我知道这是一个古老的问题-但是,对于如何使用ASM库实现此目标特别感兴趣,这可能对将来的访问者有用。牢记警告,其他答案针对与“ goto”语句相关的广义假设发出警告,有一种方法可以做到这一点。 (这假定应该检测给定方法中可以“循环”的任何代码组-通常这是一个实际的循环结构,但是已经提供了其他(罕见但存在)示例,说明如何发生这种情况。)>
您需要做的主要事情是在ASM称为“跳转指令”之前,先跟踪ASM访问的“标签”(字节代码中的位置)-如果它跳转到的标签已经如果在同一方法的上下文中遇到该问题,那么您就有可能循环代码。
我在这里看到的答案与ASM的行为之间的一个显着区别是,它读取了一个简单文件的“循环”跳转命令,而不是“ goto”之外的操作码-这可能只是自那时以来Java编译中的变化这是被问到的,但似乎值得注意。
ASM的基本示例代码如下(通过checkForLoops
方法输入):
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public void checkForLoops(Path classFile) {
LoopClassVisitor classVisitor = new LoopClassVisitor();
try (InputStream inputStream = Files.newInputStream(classFile)) {
ClassReader cr = new ClassReader(inputStream);
cr.accept(classVisitor, 0);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public class LoopClassVisitor extends ClassVisitor {
public LoopClassVisitor() {
super(Opcodes.ASM7);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
String[] exceptions) {
return new LoopMethodVisitor();
}
}
public class LoopMethodVisitor extends MethodVisitor {
private List<Label> visitedLabels;
public LoopMethodVisitor() {
super(Opcodes.ASM7);
visitedLabels = new ArrayList<>();
}
@Override
public void visitLineNumber(final int line, final Label start) {
System.out.println("lnLabel: " + start.toString());
visitedLabels.add(start);
}
@Override
public void visitLabel(final Label label) {
System.out.println("vLabel: " + label.toString());
visitedLabels.add(label);
}
@Override
public void visitJumpInsn(final int opcode, final Label label) {
System.out.println("Label: " + label.toString());
if (visitedLabels.contains(label)) {
System.out.println("Op: " + opcode + ", GOTO to previous command - possible looped execution");
}
}
}
您还可以在标签可用时附加行号信息,并在方法访问者中跟踪行号信息,以输出检测循环在源内开始和结束的位置。