通过ASM在字节码中添加try/catch块

Far*_*man 7 java java-bytecode-asm

我是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");
    }
}
Run Code Online (Sandbox Code Playgroud)

我想转换上面的代码,如下所示,使用ASM字节码检测引入try/catch块.

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

下面的代码添加了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();
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

为了调试我使用了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
Run Code Online (Sandbox Code Playgroud)

我无法理解上述验证消息.

Raf*_*ter 5

您需要遍历您的类并MethodVisitor在此过程中使用已修改的类.如果将整个方法包装在try- catch构造中.您可以通过拦截调用块的开始和结束的回调来插入构造.这些方法visitCode和visitEnd您可以拦截这样的:

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();
  }
}
Run Code Online (Sandbox Code Playgroud)

但请注意,此示例不包括在生成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);
}
Run Code Online (Sandbox Code Playgroud)

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


Far*_*man 4

上述异常与计算堆栈图帧有关。ASM 本身提供了提供堆栈映射帧的机制。我们需要在 ClassWriter 构造函数中使用参数标志作为 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。