代码之家  ›  专栏  ›  技术社区  ›  tom

通过ASM替换完整方法

  •  1
  • tom  · 技术社区  · 8 年前

    我试图编写一个脚本,用基本的throw new exception()行替换每个方法体。我正处于学习ASM的起步阶段,因此,如果有任何关于在哪里寻找的建议,我将不胜感激。

    我到目前为止所做的:

    package methodtester;
    
    import java.io.DataOutputStream;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import org.objectweb.asm.ClassReader;
    import org.objectweb.asm.ClassVisitor;
    import org.objectweb.asm.ClassWriter;
    import org.objectweb.asm.MethodVisitor;
    import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
    import static org.objectweb.asm.Opcodes.ASM4;
    import static org.objectweb.asm.Opcodes.ATHROW;
    import static org.objectweb.asm.Opcodes.DUP;
    import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
    import static org.objectweb.asm.Opcodes.NEW;
    
    public class MethodTransformer {
        public static void main(String[] args) throws IOException {
            InputStream in = MethodTester.class.getResourceAsStream("/methodtester/testingMethod.class");
    
            ClassReader classReader = new ClassReader(in);
    
            ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    
            ExceptionThrower exceptionThrower = new ExceptionThrower(classWriter);
    
            classReader.accept(exceptionThrower, 0);
    
            File outputDir = new File("build/classes/methodtester/");
    
            outputDir.mkdirs();
    
            DataOutputStream dataOutputStream =
                new DataOutputStream(
                    new FileOutputStream(
                        new File(outputDir,"testingMethod-postASM.class")));
    
            dataOutputStream.write(classWriter.toByteArray());
        }
    
        public static class ExceptionThrower extends ClassVisitor {
            private String _className;
            private boolean _isInterface;
    
            public ExceptionThrower(ClassVisitor classVisitor) {
                super(ASM4, classVisitor);
            }
    
            @Override
            public void visit(
                int version, int access, String name, String signature,
                String superName, String[] interfaces) {
    
                cv.visit(version, access, name, signature, superName, interfaces);
    
                _className = name;
    
                _isInterface = (access & ACC_INTERFACE) != 0;
            }
    
            @Override
            public MethodVisitor visitMethod(
                int access, String name, String desc, String signature,
                String[] exceptions) {
    
                MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
                    exceptions);
    
                if (!_isInterface && mv != null && !name.equals("<init>")) {
                    ExceptionThrowerMethod exceptionThrowerMethod =
                        new ExceptionThrowerMethod(mv);
    
                    return exceptionThrowerMethod;
                }
    
                return mv;
            }
    
            public static class ExceptionThrowerMethod extends MethodVisitor {
                public ExceptionThrowerMethod(MethodVisitor methodVisitor) {
                    super(ASM4, methodVisitor);
                }
    
                @Override
                public void visitCode() {
                    mv.visitCode();
                    mv.visitTypeInsn(NEW, "java/io/IOException");
                    mv.visitInsn(DUP);
                    mv.visitMethodInsn(INVOKESPECIAL, "java/io/IOException", "<init>", "()V", false);
                    mv.visitInsn(ATHROW);
                    mv.visitMaxs(2, 0);
                    mv.visitEnd();
                }
            }
        }
    }
    

    到目前为止,我能够将抛出新的IOException()指令插入到方法的开头,但这不会在运行时运行,因为给出了以下错误:

    Exception in thread "main" java.lang.ClassFormatError: Invalid start_pc 8 in LocalVariableTable in class file methodtester/testingMethod
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
        at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at methodtester.MethodTester.main(MethodTester.java:27)
    

    查看javap-c-v,我得到:

     public void testing() throws java.io.IOException;
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: new           #14                 // class java/io/IOException
             3: dup
             4: invokespecial #15                 // Method java/io/IOException."<init>":()V
             7: athrow
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                8       0     0  this   Lmethodtester/testingMethod;
          LineNumberTable:
            line 24: 8
            line 25: 8
        Exceptions:
          throws java.io.IOException
    }
    

    查看使用Asmier的类,我发现该方法是:

    mv = cw.visitMethod(ACC_PUBLIC, "testing", "()V", null, null);
    mv.visitCode();
    mv.visitTypeInsn(NEW, "java/io/IOException");
    mv.visitInsn(DUP);
    mv.visitMethodInsn(INVOKESPECIAL, "java/io/IOException", "<init>", "()V", false);
    mv.visitInsn(ATHROW);
    mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
    mv.visitLdcInsn("testing method");
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    mv.visitInsn(RETURN);
    mv.visitMaxs(2, 1);
    mv.visitEnd();
    }
    

    看起来我需要更改LocalVariableTable以使其从0开始,长度为0。

    谢谢

    编辑:使用COMPUTE_FRAMES更新并更新新错误代码

    1 回复  |  直到 8 年前
        1
  •  3
  •   Holger    8 年前

    你正在通过原件 MethodVisitor ,您从 ClassWriter ,到自定义的超级构造函数 方法访问者 这意味着 visit… 呼叫,您没有覆盖,将被委托给 方法访问者 ,复制整个原始代码。

    当然,首先,您不想复制原始代码,其次,接收矛盾 参观 拜访作家 之后 你对 visitMaxs visitEnd 这无助于创建有效代码。

    当你想完全 代替 方法,您不应显示目标编写器 方法访问者 基类,但仅用于您自己的代码生成:

    public static class ExceptionThrowerMethod extends MethodVisitor {
        private final MethodVisitor target;
    
        public ExceptionThrowerMethod(MethodVisitor methodVisitor) {
            super(ASM4, null);
            this.target=methodVisitor;
        }
    
        @Override
        public void visitCode() {
            target.visitCode();
            target.visitTypeInsn(NEW, "java/io/IOException");
            target.visitInsn(DUP);
            target.visitMethodInsn(INVOKESPECIAL,"java/io/IOException","<init>","()V",false);
            target.visitInsn(ATHROW);
            target.visitMaxs(2, 0);
            target.visitEnd();
        }
    }
    

    顺便说一下,您可以通过让 Class 本地解析资源,例如。 testingMethod.class.getResourceAsStream("testingMethod.class") .