ASM的第一个使用例子

553 阅读3分钟

1.build.gradle中添加asm的jar依赖

dependencies {
    implementation files('libs/asm-9.2.jar')
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation group: 'io.appium', name: 'java-client', version: '7.5.1'
}

2.一个测试的例子,主要是动态修改一个class文件Person.class,class的原始内容如下:

public class Person {
    private boolean passAuth = false;

    public Person() {
    }

    public boolean validation() {
        System.out.println("我是超级大黑客");
        return this.passAuth;
    }
}

如果是正常的加载这个文件,进行validation方法的调用,那应该是输出文本“我是超级大黑客”,下面通过ASM进行修改。

3.修改的ASM的代码如下:

public class FirstAsmTest {

    public static void main(String[] args) throws Exception {
        // 生成二进制字节码
        MyClassLoader cl = new MyClassLoader();
        File file = new File("/Users/zhangqi59/AndroidStudioProjects/JavApplication/" +
                "javaDemo/src/main/java/com/justalk/javademo/asm/Person.class");
//        System.out.println(file.exists());
//        System.out.println(file.getAbsolutePath());
        Path p = Paths.get(file.getAbsolutePath());
//        System.out.println(p.getFileName());
        ClassReader cr = new ClassReader(Files.newInputStream(p));
        ClassWriter cw = new ClassWriter(cr,Opcodes.ASM4);
        ClassVisitor cv = new ChangeClassAdapter(cw);
        cr.accept(cv,0);
        byte[] data = cw.toByteArray();
        Class<?> personClass = cl.defineClass("com.justalk.javademo.asm.Person", data);
        Object person = personClass.newInstance();
        Method validation = personClass.getMethod("validation");
        validation.invoke(person);
        FileOutputStream fos = new FileOutputStream("./Person.class");
        fos.write(data);
        System.out.println("-----------------------");
        Person person1 = new Person();
        boolean validation1 = person1.validation();
        System.out.println("validation1:"+validation1);

    }

    private static byte[] generate() {
        ClassWriter cw = new ClassWriter(0);
        // 定义对象头:版本号、修饰符、全类名、签名、父类、实现的接口
        cw.visit(Opcodes.V1_8, ACC_PUBLIC, "com/dadiyang/asm/HelloWorld",
                null, "java/lang/Object", null);
        // 添加方法:修饰符、方法名、描述符、签名、抛出的异常
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main",
                "([Ljava/lang/String;)V", null, null);
        // 执行指令:获取静态属性
        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        // 加载常量 load constant
        mv.visitLdcInsn("HelloWorld!");
        // 调用方法
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        // 返回
        mv.visitInsn(RETURN);
        // 设置栈大小和局部变量表大小
        mv.visitMaxs(2, 1);
        // 方法结束
        mv.visitEnd();
        // 类完成
        cw.visitEnd();
        // 生成字节数组
        return cw.toByteArray();
    }

    public static byte[] dump() throws Exception {

        ClassWriter classWriter = new ClassWriter(0);
        FieldVisitor fieldVisitor;
        MethodVisitor methodVisitor;
        AnnotationVisitor annotationVisitor0;

        classWriter.visit(V1_7, ACC_PUBLIC | ACC_SUPER, "com/justalk/javademo/asm/Test", null, "java/lang/Object", null);

        classWriter.visitSource("Test.java", null);

        {
            fieldVisitor = classWriter.visitField(ACC_PRIVATE, "num1", "I", null, null);
            fieldVisitor.visitEnd();
        }
        {
            fieldVisitor = classWriter.visitField(ACC_PUBLIC | ACC_STATIC, "NUM1", "I", null, null);
            fieldVisitor.visitEnd();
        }
        {
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(3, label0);
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLineNumber(5, label1);
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitFieldInsn(PUTFIELD, "com/justalk/javademo/asm/Test", "num1", "I");
            methodVisitor.visitInsn(RETURN);
            Label label2 = new Label();
            methodVisitor.visitLabel(label2);
            methodVisitor.visitLocalVariable("this", "Lcom/justalk/javademo/asm/Test;", null, label0, label2, 0);
            methodVisitor.visitMaxs(2, 1);
            methodVisitor.visitEnd();
        }
        {
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "func", "(II)I", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(8, label0);
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitVarInsn(ILOAD, 1);
            methodVisitor.visitVarInsn(ILOAD, 2);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/justalk/javademo/asm/Test", "add", "(II)I", false);
            methodVisitor.visitInsn(IRETURN);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLocalVariable("this", "Lcom/justalk/javademo/asm/Test;", null, label0, label1, 0);
            methodVisitor.visitLocalVariable("a", "I", null, label0, label1, 1);
            methodVisitor.visitLocalVariable("b", "I", null, label0, label1, 2);
            methodVisitor.visitMaxs(3, 3);
            methodVisitor.visitEnd();
        }
        {
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "add", "(II)I", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(11, label0);
            methodVisitor.visitVarInsn(ILOAD, 1);
            methodVisitor.visitVarInsn(ILOAD, 2);
            methodVisitor.visitInsn(IADD);
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitFieldInsn(GETFIELD, "com/justalk/javademo/asm/Test", "num1", "I");
            methodVisitor.visitInsn(IADD);
            methodVisitor.visitInsn(IRETURN);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLocalVariable("this", "Lcom/justalk/javademo/asm/Test;", null, label0, label1, 0);
            methodVisitor.visitLocalVariable("a", "I", null, label0, label1, 1);
            methodVisitor.visitLocalVariable("b", "I", null, label0, label1, 2);
            methodVisitor.visitMaxs(2, 3);
            methodVisitor.visitEnd();
        }
        {
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "sub", "(II)I", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(14, label0);
            methodVisitor.visitVarInsn(ILOAD, 1);
            methodVisitor.visitVarInsn(ILOAD, 2);
            methodVisitor.visitInsn(ISUB);
            methodVisitor.visitFieldInsn(GETSTATIC, "com/justalk/javademo/asm/Test", "NUM1", "I");
            methodVisitor.visitInsn(ISUB);
            methodVisitor.visitInsn(IRETURN);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLocalVariable("this", "Lcom/justalk/javademo/asm/Test;", null, label0, label1, 0);
            methodVisitor.visitLocalVariable("a", "I", null, label0, label1, 1);
            methodVisitor.visitLocalVariable("b", "I", null, label0, label1, 2);
            methodVisitor.visitMaxs(2, 3);
            methodVisitor.visitEnd();
        }
        {
            methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(6, label0);
            methodVisitor.visitIntInsn(BIPUSH, 100);
            methodVisitor.visitFieldInsn(PUTSTATIC, "com/justalk/javademo/asm/Test", "NUM1", "I");
            methodVisitor.visitInsn(RETURN);
            methodVisitor.visitMaxs(1, 0);
            methodVisitor.visitEnd();
        }
        classWriter.visitEnd();

        return classWriter.toByteArray();
    }
}
/**
 * 自定义ClassLoader以支持加载字节数组形式的字节码
 * @author dadiyang
 */
class MyClassLoader extends ClassLoader {
    public Class<?> defineClass(String name, byte[] b) {
        // ClassLoader是个抽象类,而ClassLoader.defineClass 方法是protected的
        // 所以我们需要定义一个子类将这个方法暴露出来
        return super.defineClass(name, b, 0, b.length);
    }
}
class ChangeClassAdapter extends ClassVisitor {

    public ChangeClassAdapter(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
        // TODO Auto-generated constructor stub
    }

    public ChangeClassAdapter(ClassVisitor cv) {
        super(Opcodes.ASM4);
        this.cv = cv;
    }


    public ChangeClassAdapter(int asm7) {
        // TODO Auto-generated constructor stub
        super(asm7);
    }

    @Override
    public MethodVisitor visitMethod(
            int access,
            String name,
            String desc,
            String signature,
            String[] exceptions)
    {
        //这里mv实际最终拿到的是MethodWriter
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
        if (cv != null && "validation".equals(name)) {
            //validation方法重新构造
//            mv.visitCode();
//            mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
//            mv.visitLdcInsn("i hacker");
//            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
//            mv.visitInsn(Opcodes.ICONST_1);
//            mv.visitInsn(Opcodes.IRETURN);
//            mv.visitMaxs(2, 1);
//            mv.visitEnd();

            mv.visitCode();
            Label label0 = new Label();
            mv.visitLabel(label0);
            mv.visitLineNumber(8, label0);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("我是超级大黑客");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            Label label1 = new Label();
            mv.visitLabel(label1);
            mv.visitLineNumber(14, label1);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, "com/justalk/javademo/asm/Person", "passAuth", "Z");
            mv.visitInsn(IRETURN);
            Label label2 = new Label();
            mv.visitLabel(label2);
            mv.visitLocalVariable("this", "Lcom/justalk/javademo/asm/Person;", null, label0, label2, 0);
            mv.visitMaxs(2, 1);
            mv.visitEnd();
            //注意这里的返回值;只有返回null,才意味着修改是生效的;具体可以看一下源码,方法里直接有说明;
            return null;
        }
        return mv;
    }
}