美文网首页APP & program
Android ASM字节码插桩(下)

Android ASM字节码插桩(下)

作者: Bfmall | 来源:发表于2022-07-22 16:36 被阅读0次

    由于内容篇幅限制,紧接着上篇
    https://www.jianshu.com/p/c975081b43fd?u_atoken=de4d112a-6864-4703-ad69-f82922d505de&u_asession=01X_C9UMmFccLMXpIfjRM8FYXsq1g_CrgffyPiHHUpgZGBrWiw193wtdhvbyaJ4chEX0KNBwm7Lovlpxjd_P_q4JsKWYrT3W_NKPr8w6oU7K_OI3-HIFVcmZbMNunYRvYCslvTX-jMTLEIhdGFg3rxgWBkFo3NEHBv0PZUm6pbxQU&u_asig=05e9XZPq2Q7yJkKdk86YtiyFfiNPAdPSg-Voed-oPaPvY2QwBnCAT6a9bvIxBzozUzQDvsYuznxmEgCniYpEFR8BE5VfTwKn3vCIpIwgp7gRXcA22dI9OX-SjB0FJAYc2ww3dPL_NNCbygKg7jU_P9L1CtmgFQaXXOFbG5UybdQYj9JS7q8ZD7Xtz2Ly-b0kmuyAKRFSVJkkdwVUnyHAIJzet9GpoGT6ubHEJ3SNQzl3mpaIfenFB8MFcEbsDwJo4a6FPw117USKdEPc8n7HkzU-3h9VXwMyh6PgyDIVSG1W-IddwkY7v-CAh1IBL0sYO20tCNasH120WVmgCT0pzm_wUd5n3s6RPzq9rJ9ybbMWH4abeDNZysnYRhxU5ybSOYmWspDxyAEEo4kbsryBKb9Q&u_aref=%2FnUp0P8E0Oe%2BxeO5Bz5i0j215Eo%3D

    3.6 执行InjectUnitTest.java的test()方法查看InjectTest.class结果

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    package com.xyaty.asmdemo;
    
    public class InjectTest {
        public InjectTest() {
            long var1 = System.currentTimeMillis();
            long var3 = System.currentTimeMillis();
            System.out.println("execute: " + (var3 - var1) + "ms");
        }
    
        public static void main(String[] var0) throws InterruptedException {
            long var1 = System.currentTimeMillis();
            Thread.sleep(1000L);
            long var3 = System.currentTimeMillis();
            System.out.println("execute: " + (var3 - var1) + "ms");
        }
    
        public void methodA() {
            long var1 = System.currentTimeMillis();
            System.out.println("methodA");
            long var3 = System.currentTimeMillis();
            System.out.println("execute: " + (var3 - var1) + "ms");
        }
    }
    

    发现在每个方法中都加入了

    long var1 = System.currentTimeMillis();
    和
    long var3 = System.currentTimeMillis();
    System.out.println("execute: " + (var3 - var1) + "ms");
    

    如果仅仅在main()方法才注入代码,就需要引入自定义注解来标记指定的方法

    四、引入自定义注解,标记方法才注入代码
    4.1 注解类ASMTest.java,并通过javac编译成ASMTest.class(略)

    package com.xyaty.asmdemo;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * DESC   :
     */
    @Retention(RetentionPolicy.CLASS)
    @Target(ElementType.METHOD)
    public @interface ASMTest {
    
    }
    

    4.2 在InjectTest.java的main()方法上加上注解@ASMTest,标记此方法,并通过javac生成class

    package com.xyaty.asmdemo;
    
    /**
     * DESC   :
     */
    public class InjectTest {
    
        @ASMTest
        public static void main(String[] args) throws InterruptedException {
            Thread.sleep(1000);
        }
    
        public void methodA() {
            System.out.println("methodA");
        }
    }
    

    4.3 在上述MyMethodVisitor的下列方法中加入注解判断

            @Override
            public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
                System.out.println("visitAnnotation===>methodName="+getName()+", descriptor="+descriptor);
                //如果方法的注解名字是@ASMTest,则给此方法注入代码
                if ("Lcom/xyaty/asmdemo/ASMTest;".equals(descriptor)) {
                    isInject = true;
                } else {
                    isInject = false;
                }
                return super.visitAnnotation(descriptor, visible);
            }
    
            /**
             * 进入方法插入内容
             */
            @Override
            protected void onMethodEnter() {
                super.onMethodEnter();
    
                if (!isInject) {
                    return;
                }
            }
    
    @Override
            protected void onMethodExit(int opcode) {
                super.onMethodExit(opcode);
    
                if (!isInject) {
                    return;
                }
    }
    

    InjectUnitTest.java完整代码如下:

    package com.xyaty.asmdemo;
    
    import org.junit.Test;
    import org.objectweb.asm.AnnotationVisitor;
    import org.objectweb.asm.ClassReader;
    import org.objectweb.asm.ClassVisitor;
    import org.objectweb.asm.ClassWriter;
    import org.objectweb.asm.MethodVisitor;
    import org.objectweb.asm.Opcodes;
    import org.objectweb.asm.Type;
    import org.objectweb.asm.commons.AdviceAdapter;
    import org.objectweb.asm.commons.Method;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    
    /**
     * DESC   :
     */
    public class InjectUnitTest {
        /**
         * 单元测试方法,右击test()方法,选择run test()方法即可查看结果
         */
        @Test
        public void test() {
            try {
                //读取待插桩的class
                FileInputStream fis = new FileInputStream(
                        new File("src/test/java/com/xyaty/asmdemo/InjectTest.class"));
    
                /**
                 * 执行分析与插桩
                 * ClassReader是class字节码的读取与分析引擎
                 */
                ClassReader classReader = new ClassReader(fis);
                // ClassWriter写出器, COMPUTE_FRAMES表示自动计算栈帧和局部变量表的大小
                ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
                /**
                 * 执行分析,处理结果写入classWriter, EXPAND_FRAMES表示栈图以扩展格式进行访问
                 * 执行插桩的代码就在MyClassVisitor中实现
                 */
                classReader.accept(new MyClassVisitor(Opcodes.ASM9, classWriter), ClassReader.EXPAND_FRAMES);
    
                //获得执行了插桩之后的字节码数据
                byte[] bytes = classWriter.toByteArray();
                // 重新写入InjectTest.class中(也可以写入到其他class中,InjectTest1.class),完成插桩
                FileOutputStream fos = new FileOutputStream(
                        new File("src/test/java/com/xyaty/asmdemo/InjectTest.class"));
                fos.write(bytes);
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public class MyClassVisitor extends ClassVisitor {
    
            public MyClassVisitor(int api, ClassVisitor classVisitor) {
                super(api, classVisitor);
            }
    
            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                System.out.println("visitMethod==>name="+name);
                /**
                 * 会输出以下方法:
                 * visitMethod==>name=<init>
                 * visitMethod==>name=main
                 */
                MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
                return new MyMethodVisitor(api, methodVisitor, access, name,descriptor);
            }
        }
    
    
        /**
         * 之所以继承自AdviceAdapter,是因为AdviceAdapter是MethodVisitor的子类,
         * AdviceAdapter封装了指令插入方法,更为直观与简单,
         * 要使用其中的onMethodEnter和 onMethodExit方法进行字节码插桩,
         *
         * 继承关系如下:
         * AdviceAdapter extends GeneratorAdapter
         * GeneratorAdapter extends LocalVariablesSorter
         * LocalVariablesSorter extends MethodVisitor
         */
        public class MyMethodVisitor extends AdviceAdapter {
            long start;
            private int startIdentifier;
            private boolean isInject = false;//是否注入代码
    
            @Override
            public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
                System.out.println("visitAnnotation===>methodName="+getName()+", descriptor="+descriptor);
                //如果方法的注解名字是@ASMTest,则给此方法注入代码
                if ("Lcom/xyaty/asmdemo/ASMTest;".equals(descriptor)) {
                    isInject = true;
                } else {
                    isInject = false;
                }
                return super.visitAnnotation(descriptor, visible);
            }
    
            protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
                super(api, methodVisitor, access, name, descriptor);
            }
    
            /**
             * 进入方法插入内容
             */
            @Override
            protected void onMethodEnter() {
                super.onMethodEnter();
    
                if (!isInject) {
                    return;
                }
    
    //            start = System.currentTimeMillis();
                /**
                 * @Type owner 调用哪个类
                 * @Method method 调用某个类的静态方法(参数name: 方法名字,descriptor:方法中参数和方法返回值类型)
                 */
                invokeStatic(Type.getType("Ljava/lang/System;"), new Method("currentTimeMillis", "()J"));
                //调用newLocal创建一个long类型的变量,返回一个int类型索引identifier
                startIdentifier = newLocal(Type.LONG_TYPE);
                //保存到本地变量索引中,用一个本地变量接收上一步执行的结果
                storeLocal(startIdentifier);
            }
    
            /**
             * 在方法结尾插入内容
             * @param opcode
             */
            @Override
            protected void onMethodExit(int opcode) {
                super.onMethodExit(opcode);
    
                if (!isInject) {
                    return;
                }
    
    //            long end = System.currentTimeMillis();
    //            System.out.println("execute: "+(end - start)+"ms");
    
                invokeStatic(Type.getType("Ljava/lang/System;"), new Method("currentTimeMillis", "()J"));
                //调用newLocal创建一个long类型的变量,返回一个int类型索引identifier
                int endIdentifier = newLocal(Type.LONG_TYPE);
                //保存到本地变量索引中,用一个本地变量接收上一步执行的结果
                storeLocal(endIdentifier);
    
                //获取System的静态字段out,类型为PrintStream
                getStatic(Type.getType("Ljava/lang/System;"),
                        "out", Type.getType("Ljava/io/PrintStream;"));
    
                /**
                 * "execute: "+(end - start)+"ms"实际是内部创建StringBuilder来拼接
                 * 源码:NEW java/lang/StringBuilder
                 * 创建一个对象StringBuilder
                 */
                newInstance(Type.getType("Ljava/lang/StringBuilder;"));
                // dup压入栈顶,让下面的INVOKESPECIAL 知道执行谁的构造方法创建StringBuilder
                dup();
                /**
                 * 源码:INVOKESPECIAL java/lang/StringBuilder.<init> ()V
                 * 创建StringBuilder的构造方法,用init来代替
                 */
                invokeConstructor(Type.getType("Ljava/lang/StringBuilder;"),
                        new Method("<init>", "()V"));
    
                visitLdcInsn("execute: ");
                /**
                 * 源码:INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
                 * 调用append方法
                 */
                invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"),
                        new Method("append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"));
                /**
                 * 对结束时间和开始时间进行减法操作
                 * LLOAD 3 先加载结束时间
                 * LLOAD 1 后加载开始时间
                 * LSUB    执行减法操作
                 */
                loadLocal(endIdentifier);
                loadLocal(startIdentifier);
                //执行减法操作,返回long类型
                math(SUB, Type.LONG_TYPE);
    
                /**
                 * 源码:INVOKEVIRTUAL java/lang/StringBuilder.append (J)Ljava/lang/StringBuilder;
                 * LDC "ms"
                 */
                invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"),
                        new Method("append", "(J)Ljava/lang/StringBuilder;"));
                //拼接毫秒
                visitLdcInsn("ms");
    
                /**
                 * 源码:
                 * INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
                 * INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
                 * INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
                 */
                invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"),
                        new Method("append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"));
                invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"),
                        new Method("toString", "()Ljava/lang/String;"));
                invokeVirtual(Type.getType("Ljava/io/PrintStream;"),
                        new Method("println", "(Ljava/lang/String;)V"));
            }
    
        }
    }
    
    

    再次运行InjectTest.class结果如下,可以看到只有main()方法注入了代码

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    package com.xyaty.asmdemo;
    
    public class InjectTest {
        public InjectTest() {
        }
    
        @ASMTest
        public static void main(String[] var0) throws InterruptedException {
            long var1 = System.currentTimeMillis();
            Thread.sleep(1000L);
            long var3 = System.currentTimeMillis();
            System.out.println("execute: " + (var3 - var1) + "ms");
        }
    
        public void methodA() {
            System.out.println("methodA");
        }
    }
    

    到此结束。

    参考文章:
    https://blog.csdn.net/zenmela2011/article/details/125586333
    https://blog.csdn.net/huangbin123/article/details/123322667

    相关文章

      网友评论

        本文标题:Android ASM字节码插桩(下)

        本文链接:https://www.haomeiwen.com/subject/fnwmirtx.html