识别java字节代码中的循环

时间:2011-07-22 15:28:42

标签: java loops bytecode instrumentation

我正在尝试使用java字节码。

我想识别进入和退出java循环,但我发现循环的识别非常具有挑战性。 我花了好几个小时看着 ASM 开源反编译器(我认为他们必须一直解决这个问题),但是,我做得很短。

我正在扩充/扩展的工具正在使用ASM,所以理想情况下我想知道如何通过ASM 来检测java中不同循环结构的进入和退出。但是,我也欢迎建议一个好的开源反编译器,因为很明显他们会解决同样的问题。

4 个答案:

答案 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循环,你可以看到至少一些用于实现该循环的字节代码。这里的参考是截图:

Bytecode Outline Screenshot

Direct link

看起来循环只是通过边界检查实现为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");
        }
    }

}

您还可以在标签可用时附加行号信息,并在方法访问者中跟踪行号信息,以输出检测循环在源内开始和结束的位置。