美文网首页
Transform Api + ASM 插桩(实现示例)

Transform Api + ASM 插桩(实现示例)

作者: MaxDCc | 来源:发表于2020-12-17 11:53 被阅读0次

    基础知识准备

    1.如何实现gradle插件
    2.什么是Transform以及如何使用?
    3.什么是ASM以及如何使用?

    下面请阅读:https://www.jianshu.com/p/16ed4d233fd1 (大佬树下好乘凉 ,上来就是一顿神操作)

    注:以下场景只是入门示例 希望通过这几个示例能够触类旁通 了解一些ASM的基础应用和API调用


    以下为一些场景的使用

    在下面示例中的字节码中会有一些如下指令:
    LINENUMBER 34 L0
    L0 L1 L2 L3
    遇见 LINENUMBER 直接忽略它的存在
    L0 L1 一般方法内添加代码块的情况下才会使用 例如if判断 trycatch等, 无代码块的时候忽略即可,具体使用看示例即可

    1.方法计时统计
    //方法计时
    public fun setContent(){
         val startTime = System.currentTimeMillis()
         //......   
         //doSomething
         //......
         val endTime = System.currentTimeMillis()
         Log.e("TAG" ,"执行setContent方式耗时:${endTime - startTime}")
     }
    
    
    //上述统计代码转化为字节码后的内容
    LINENUMBER 34 L0
     INVOKESTATIC java/lang/System.currentTimeMillis ()J
     LSTORE 1
    L1
     LINENUMBER 35 L1
     INVOKESTATIC java/lang/System.currentTimeMillis ()J
     LSTORE 3
    L2
     LINENUMBER 36 L2
     LDC "TAG"
     NEW java/lang/StringBuilder
     DUP
     INVOKESPECIAL java/lang/StringBuilder.<init> ()V
     LDC "\u6267\u884csetContent\u65b9\u5f0f\u8017\u65f6\uff1a"
     INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
     LLOAD 3
     LLOAD 1
     LSUB
     INVOKEVIRTUAL java/lang/StringBuilder.append (J)Ljava/lang/StringBuilder;
     INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
     INVOKESTATIC android/util/Log.e (Ljava/lang/String;Ljava/lang/String;)I
     POP
    L3
    
    //MethodVisitor具体插入细节
    class TransformTimeMethodVisitor extends MethodVisitor{
    
        TransformTimeMethodVisitor(int api, MethodVisitor mv) {
            super(api, mv)
        }
    
        @Override
        void visitCode() {
            super.visitCode()
            //方法前插入
            //val startTime = System.currentTimeMillis()
            mv.visitMethodInsn(Opcodes.INVOKESTATIC,"java/lang/System","currentTimeMillis","()J",false)
            mv.visitVarInsn(Opcodes.LSTORE,1)
        }
    
    
        @Override
        void visitInsn(int opcode) {
            //方法结束处
            if (opcode == Opcodes.RETURN){
                //val endTime = System.currentTimeMillis()
                mv.visitMethodInsn(Opcodes.INVOKESTATIC,"java/lang/System","currentTimeMillis","()J",false)
                mv.visitVarInsn(Opcodes.LSTORE,3)
    
                //Log.e("TAG" ,"执行setContent方式耗时:${endTime - startTime}")
                mv.visitLdcInsn("TAG")
                mv.visitTypeInsn(Opcodes.NEW,"java/lang/StringBuilder")
                mv.visitInsn(Opcodes.DUP)
                mv.visitMethodInsn(Opcodes.INVOKESPECIAL,"java/lang/StringBuilder","<init>","()V",false)
                mv.visitLdcInsn("\u6267\u884csetContent\u65b9\u5f0f\u8017\u65f6\uff1a")
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/lang/StringBuilder","append","(Ljava/lang/String;)Ljava/lang/StringBuilder;",false)
                mv.visitVarInsn(Opcodes.LLOAD,3)
                mv.visitVarInsn(Opcodes.LLOAD,1)
                mv.visitInsn(Opcodes.LSUB)
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/lang/StringBuilder","append","(J)Ljava/lang/StringBuilder;",false)
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/lang/StringBuilder","toString","()Ljava/lang/String;",false)
                mv.visitMethodInsn(Opcodes.INVOKESTATIC,"android/util/Log","e","(Ljava/lang/String;Ljava/lang/String;)I",false)
                mv.visitInsn(Opcodes.POP)
    
            }
            super.visitInsn(opcode)
        }
    }
    

    2.onClick事件添加防快速点击

    注意:onClick方法名也不一定是View.OnClickListener的onClick 所以在指定onClick时需要指定下方法的所属 具体实现是在过滤方法时添加判断 指定下onClick的方法的参数以及返回类型信息

    //示例代码
    public fun click(){
        val bt = findViewById<TextView>(R.id.mainBt);
        bt.setOnClickListener{
            //此处if分支为即将插入的代码
            if (FastClickUtils.isFastClick()){
                return@setOnClickListener
            }
            //doSomething
        }
     }
    
    //需要插入代码的字节码
    LINENUMBER 41 L0
    GETSTATIC com/example/utils/FastClickUtils.Companion : Lcom/example/utils/FastClickUtils$Companion;
    INVOKEVIRTUAL com/example/utils/FastClickUtils$Companion.isFastClick ()Z
    IFEQ L1
    L2
    LINENUMBER 42 L2
    RETURN
    L1
    
    //MethodVisitor中的具体实现
    class TransformOnclickMethodVisitor extends MethodVisitor{
    
        TransformOnclickMethodVisitor(int api, MethodVisitor mv) {
            super(api, mv)
        }
    
        private Label L1 = new Label()
    
        @Override
        void visitCode() {
            super.visitCode()
            //方法前插入
            mv.visitFieldInsn(Opcodes.GETSTATIC,"com/example/utils/FastClickUtils","Companion","Lcom/example/utils/FastClickUtils\$Companion;")
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"com/example/utils/FastClickUtils\$Companion","isFastClick","()Z",false)
            mv.visitJumpInsn(Opcodes.IFEQ,L1)
            mv.visitInsn(Opcodes.RETURN)
            mv.visitLabel(L1)
        }
    
        @Override
        void visitInsn(int opcode) {
            if (opcode == Opcodes.RETURN){
            }
            super.visitInsn(opcode)
        }
    
    }
    

    3.给方法加try catch (适用场景:某些三方sdk造成的崩溃没有捕获)
    //捕获异常
    fun tryCatch(){
        //异常代码
        try {
            val number = 10/0
        }catch (e: Exception){
            e.printStackTrace()
        }
    }
    
    //上述代码转字节码
     TRYCATCHBLOCK L0 L1 L2 java/lang/Exception
       L3
        LINENUMBER 52 L3
       L0
        NOP
       L4  //L4 此处为运算部分 不需要再次插入 val number = 10/0
        LINENUMBER 53 L4
        BIPUSH 10
        ICONST_0
        IDIV
        ISTORE 1
       L1
        GOTO L5
       L2
        LINENUMBER 54 L2
        ASTORE 1
       L6
        LINENUMBER 55 L6
        ALOAD 1
        INVOKEVIRTUAL java/lang/Exception.printStackTrace ()V
       L7
        LINENUMBER 56 L7
       L5
    
    class TransformTryCatchMethodVisitor extends MethodVisitor{
    
        private def l0 = new Label(),l1 = new Label(),l2 = new Label()
    
        TransformTryCatchMethodVisitor(int api, MethodVisitor mv) {
            super(api, mv)
        }
    
        @Override
        void visitCode() {
            super.visitCode()
            //全局加tryCatch  try{ 部分
            mv.visitTryCatchBlock(l0,l1,l2,"java/lang/Exception")
            mv.visitLabel(l0)
            mv.visitInsn(Opcodes.NOP)
    
        }
    
    
        @Override
        void visitInsn(int opcode) {
            if (opcode == Opcodes.RETURN){
                // } catch(e : Exception) { e.printStackTrace() } 部分
                mv.visitLabel(l1)
                def label = new Label()
                mv.visitJumpInsn(Opcodes.GOTO,label)
                visitLabel(l2)
                mv.visitVarInsn(Opcodes.ASTORE,1)
                mv.visitVarInsn(Opcodes.ALOAD,1)
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/lang/Exception","printStackTrace","()V",false)
                mv.visitLabel(label)
            }
            super.visitInsn(opcode)
        }
    }
    

    4.替换new Thread 为自定义的Thread

    替换的核心思想
    1.找到 NEW 指令 将NEW 指令后的Thread对象替换为自己的Thread
    2.替换构造函数的对象 每个对象都至少都会有一个默认的构造函数

    以下面字节码为例
    NEW java/lang/Thread
    INVOKESPECIAL java/lang/Thread.<init> (Ljava/lang/Runnable;)V
    需要将java/lang/Thread 替换为自己的Thread全路径名 eq: com/xxx/xxx/MyThread

    //替换Thread
    private fun replaceThread(){
        Thread{
        }.start()
    }
    
    //创建Thread对象的字节码内容
    L2
        LINENUMBER 51 L2
        NEW java/lang/Thread //创建对象指令
        DUP
        GETSTATIC com/example/aaaa/MainActivity$replaceThread$1.INSTANCE : Lcom/example/aaaa/MainActivity$replaceThread$1;
        CHECKCAST java/lang/Runnable
        INVOKESPECIAL java/lang/Thread.<init> (Ljava/lang/Runnable;)V //初始化构造函数
       L3
        LINENUMBER 52 L3
        INVOKEVIRTUAL java/lang/Thread.start ()V
    
    //具体替换过程
    class TransformThreadMethodVisitor extends MethodVisitor{
    
        private final static String THREAD = "java/lang/Thread"  //系统Thread的路径
        private final static String MY_THREAD = "com/example/aaaa/MyThread" //自定义Thread的路径
    
        TransformThreadMethodVisitor(int api, MethodVisitor mv) {
            super(api, mv)
        }
    
        @Override
        void visitTypeInsn(int opcode, String type) {
            //替换NEW指令的Thread
            if (opcode == Opcodes.NEW && type == THREAD){
                mv.visitTypeInsn(Opcodes.NEW,MY_THREAD)
                return
            }
            super.visitTypeInsn(opcode, type)
        }
    
        @Override
        void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
            //替换init的Thread
            if (owner == THREAD && name.equalsIgnoreCase("<init>")){
                mv.visitMethodInsn(opcode,MY_THREAD,name,descriptor,isInterface)
                return
            }
            super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
        }
    
        @Override
        void visitCode() {
            super.visitCode()
        }
    
    
        @Override
        void visitInsn(int opcode) {
            if (opcode == Opcodes.RETURN){
            }
            super.visitInsn(opcode)
        }
    }
    

    Demo

    Demo地址

    相关文章

      网友评论

          本文标题:Transform Api + ASM 插桩(实现示例)

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