【问题标题】:Adding try/catch block in bytecode through ASM通过 ASM 在字节码中添加 try/catch 块
【发布时间】:2014-05-06 10:16:23
【问题描述】:

我是 ASM 新手,我需要一些有关字节码转换的帮助。

问题:我想通过 ASM 在字节码中为整个方法添加 try/catch 块,并希望在不使用 java -noverify 选项的情况下运行该方法。我可以为整个方法添加 try/catch 块,但是当我尝试执行该方法时,我得到了“java.lang.VerifyError”。如果我使用 java -noverify 选项,那么它将运行。请帮帮我。

以下是详细信息。

public class Example {
    public static void hello() {
        System.out.println("Hello world");
    }
}

我想将上面的代码转换为下面引入 try/catch 块,使用 ASM 字节码检测。

public class Example {
  public static void hello() {
       try
       {
          System.out.println("Hello world");
       } catch(Exception ex) {
         ex.printStackTrace();
       }
    }
}

下面的代码添加了 try/catch 块,但在没有 java -noverify 选项的情况下无法执行代码。

public class InstrumentExample {

    /**
     * Our custom method modifier method visitor class. It delegate all calls to
     * the super class. Do our logic of adding try/catch block
     * 
     */
    public static class ModifierMethodWriter extends MethodVisitor {

        // methodName to make sure adding try catch block for the specific
        // method.
        private String methodName;

        // below label variables are for adding try/catch blocks in instrumented
        // code.
        private Label lTryBlockStart;
        private Label lTryBlockEnd;
        private Label lCatchBlockStart;
        private Label lCatchBlockEnd;

        /**
         * constructor for accepting methodVisitor object and methodName
         * 
         * @param api: the ASM API version implemented by this visitor
         * @param mv: MethodVisitor obj
         * @param methodName : methodName to make sure adding try catch block for the specific method.
         */
        public ModifierMethodWriter(int api, MethodVisitor mv, String methodName) {
            super(api, mv);
            this.methodName = methodName;
        }

        // We want to add try/catch block for the entire code in the method
        // so adding the try/catch when the method is started visiting the code.
        @Override
        public void visitCode() {
            super.visitCode();

            // adding try/catch block only if the method is hello()
            if (methodName.equals("hello")) {
                lTryBlockStart = new Label();
                lTryBlockEnd = new Label();
                lCatchBlockStart = new Label();
                lCatchBlockEnd = new Label();

                // set up try-catch block for RuntimeException
                visitTryCatchBlock(lTryBlockStart, lTryBlockEnd,
                        lCatchBlockStart, "java/lang/Exception");

                // started the try block
                visitLabel(lTryBlockStart);
            }

        }

        @Override
        public void visitMaxs(int maxStack, int maxLocals) {

            // closing the try block and opening the catch block if the method
            // is hello()
            if (methodName.equals("hello")) {
                // closing the try block
                visitLabel(lTryBlockEnd);

                // when here, no exception was thrown, so skip exception handler
                visitJumpInsn(GOTO, lCatchBlockEnd);

                // exception handler starts here, with RuntimeException stored
                // on stack
                visitLabel(lCatchBlockStart);

                // store the RuntimeException in local variable
                visitVarInsn(ASTORE, 2);

                // here we could for example do e.printStackTrace()
                visitVarInsn(ALOAD, 2); // load it
                visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception",
                        "printStackTrace", "()V", false);

                // exception handler ends here:
                visitLabel(lCatchBlockEnd);
            }

            super.visitMaxs(maxStack, maxLocals);
        }

    }

    /**
     * Our class modifier class visitor. It delegate all calls to the super
     * class Only makes sure that it returns our MethodVisitor for every method
     * 
     */
    public static class ModifierClassWriter extends ClassVisitor {
        private int api;

        public ModifierClassWriter(int api, ClassWriter cv) {
            super(api, cv);
            this.api = api;
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc,
                String signature, String[] exceptions) {

            MethodVisitor mv = super.visitMethod(access, name, desc, signature,
                    exceptions);

            // Our custom MethodWriter
            ModifierMethodWriter mvw = new ModifierMethodWriter(api, mv, name);
            return mvw;
        }

    }

    public static void main(String[] args) throws IOException {

        DataOutputStream dout = null;
        try {
            // loading the class
            InputStream in = InstrumentExample.class
                    .getResourceAsStream("Example.class");
            ClassReader classReader = new ClassReader(in);
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);

            // Wrap the ClassWriter with our custom ClassVisitor
            ModifierClassWriter mcw = new ModifierClassWriter(ASM4, cw);
            ClassVisitor cv = new CheckClassAdapter(mcw);

            classReader.accept(cv, 0);

            byte[] byteArray = cw.toByteArray();
            dout = new DataOutputStream(new FileOutputStream(new File("Example.class")));
            dout.write(byteArray);

        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (dout != null)
                dout.close();
        }

    }
}

为了调试,我使用了 CheckClassAdapter,但遇到了以下验证问题。

Message:org.objectweb.asm.tree.analysis.AnalyzerException: Execution can fall off end of the code
    at org.objectweb.asm.tree.analysis.Analyzer.findSubroutine(Unknown Source)
    at org.objectweb.asm.tree.analysis.Analyzer.findSubroutine(Unknown Source)
    at org.objectweb.asm.tree.analysis.Analyzer.analyze(Unknown Source)
    at org.objectweb.asm.util.CheckClassAdapter.verify(Unknown Source)
    at org.objectweb.asm.util.CheckClassAdapter.verify(Unknown Source)
    at com.mfr.instrumentation.selenium.work.InstrumentExample.main(InstrumentExample.java:166)
hello()V
00000 ?      :    L0
00001 ?      :     GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
00002 ?      :     LDC "Hello world"
00003 ?      :     INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
00004 ?      :     RETURN
00005 ?      :    L1
00006 ?      :     GOTO L2
00007 ?      :    L3
00008 ?      :     ASTORE 2
00009 ?      :     ALOAD 2
00010 ?      :     INVOKEVIRTUAL java/lang/Exception.printStackTrace ()V
00011 ?      :    L2
     TRYCATCHBLOCK L0 L1 L3 java/lang/Exception

我无法理解上述验证信息。

【问题讨论】:

  • 问题是什么?
  • 问题是如何使用 ASM 字节码检测将没有 try/catch 块的初始代码转换为具有 try/catch 块的后一个代码。

标签: java java-bytecode-asm


【解决方案1】:

你需要遍历你的类,并在过程中使用修改后的MethodVisitor。如果您将整个方法包装在 try-catch 构造中。您可以通过拦截调用块开始和结束的回调来插入构造。这些方法是 visitCodevisitEnd,您可以像这样拦截它们:

class MyMethodVisitor extends MethodVisitor {
  // constructor omitted

 private final Label start = new Label(), 
                     end = new Label(), 
                     handler = new Label();

  @Override
  public void visitCode() {
    super.visitCode();
    visitTryCatchBlock(start, 
        end, 
        handler, 
        "java/lang/Exception");
    visitLabel(start);
  }

  @Override
  public void visitEnd() {
    visitJumpInsn(GOTO, end); 
    visitLabel(handler);
    visitMethodInsn(INVOKEVIRTUAL, 
        "java/lang/RuntimeException", 
        "printStackTrace", 
        "()V");
    visitInsn(RETURN);
    visitLabel(lCatchBlockEnd);
    super.visitEnd();
  }
}

但是,请注意,如果您为 Java 7+ 生成字节码,则此示例不包括您需要添加的堆栈映射帧。

但请注意,此解决方案将在您方法的异常表的开头注册一个主要处理程序,该处理程序会覆盖您方法中已经存在的所有其他 try-catch-finally 块!

注意:在较新版本的 ASM 中,处理程序的代码需要写在方法 visitMaxs(int, int) 中:

@Override
public void visitMaxs(int maxStack, int maxLocals) {
    // visit the corresponding instructions
    super.visitMaxs(maxStack, maxLocals);
}

这是因为标签和指令只能在visitMaxs之前访问,visitMaxsvisitEnd之前,因此在visitEnd中生成代码会导致错误。

【讨论】:

  • 我已经实现了您的建议,我可以成功地为整个方法添加 try/catch 块。我已经反编译并验证了它。但是当我尝试运行该方法时,我得到了“java.lang.VerifyError”。如果我使用 -noverify 选项运行该方法,我可以成功执行该方法。我已经编辑了我的问题并添加了代码以添加 try/catch。你能告诉我在不使用 -noverify 选项的情况下我应该怎么做才能执行代码。
  • 验证者怎么说?以及您使用的是什么 Java 版本。正如我在回答中所说,您可能需要堆叠地图框。 Asm 可以为您计算它们。
  • 刚才我已经用验证者消息更新了问题。我正在使用 Java 7 版本。和 ASM 5.0.2 jar
  • 对于 Java 7,您需要堆栈映射框架。另外,我忘了添加返回指令。请参阅我的更新答案。
  • 非常感谢。你能在这个堆栈图框架上提供更多指导吗,我用谷歌搜索了这个,但我无法弄清楚我该如何使用它。您能否就此提供一些示例或参考。我应该在我的代码 w.r.t stackmap 框架中进行哪些更改?
【解决方案2】:

上述异常与计算堆栈图帧有关。 ASM 提供了机制来提供堆栈图帧本身。我们需要在 ClassWriter 构造函数中使用参数 flag 作为 COMPUTE_FRAMES。

例如:ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

public static final int COMPUTE_FRAMES 标记以从头开始自动计算方法的堆栈映射帧。如果设置了此标志,则忽略对 MethodVisitor.visitFrame(int, int, java.lang.Object[], int, java.lang.Object[]) 方法的调用,并从方法字节码。 visitMaxs 方法的参数也被忽略并从字节码中重新计算。换句话说,computeFrames 意味着 computeMaxs。

来自 ASM ClassWriter API。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-06
    • 1970-01-01
    • 1970-01-01
    • 2017-08-09
    • 2015-06-21
    • 1970-01-01
    • 2011-10-10
    相关资源
    最近更新 更多