Android ASM字节码插桩(下)

开发者 2024-9-4 23:58:52 82 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
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-19 06:21, Processed in 0.176214 second(s), 32 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表