字节码插桩技术---ASM的使用(一)

字节码插桩技术---ASM的使用(一)​​​​​​​

字节码插桩技术---Android项目实操(二)

字节码插桩技术---Transform配合ASM进行插桩(三)​​​​​​​

字节码插桩技术简单来讲就是通过操作class文件的字节码,对class文件进行改造。在很多三方框架都有应用,比如路由框架ARouter,热修复框架Robust等。而字节码插桩一般都会使用ASM工具进行操作,这篇文章会介绍ASM的简单使用,下篇博客会详细介绍,Android项目中如何进行字节码插桩

一、ASM的基本使用

ASM的官方地址

先创建一个原文件ASMTest.java

public class ASMTest {
    public void test(){
    }
}

编译之后会得到一个class文件,我们将这个class文件保存在相应的路径中,我们之后将会对这个class文件进行操作

之后创建一个目标文件,我们将使用ASM,对上面的原文件操作,效果就是和这个目标文件一样,如下:

public class ASMTest {
    public void test(){
        System.out.println("ASMTest=====>test");
    }
}

使用javap或者ASMPlugin插件,获取这个类的字节码,我们将根据字节码指令,去写代码,字节码指令如下:

public class org.example.ASMTest {
  public org.example.ASMTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void test();
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String ASMTest=====>test
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

由于只多了一行操作,所以很好确定将要操作的字节码是哪里,就是上面test中的0,3,5标识的地方。

文件都已经准备好,开始准备ASM工具,首先在项目中依赖ASM的两个库:

    implementation 'org.ow2.asm:asm:9.3'
    implementation 'org.ow2.asm:asm-commons:9.3'

执行类如下:

import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;
import org.objectweb.asm.commons.Method;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        //被改造的原class文件
        FileInputStream fis = new FileInputStream("/Projects/Java/ASMTest/src/main/java/org/example/clazz/ASMTest.class");

        ClassReader classReader = new ClassReader(fis);
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM7,classWriter) {
            //每个类的方法,都回调到这里
            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions);
                return new MyMethodVisitor(Opcodes.ASM7,visitor,access,name,descriptor);
            }
        };
        classReader.accept(classVisitor,0);

        byte[] bytes = classWriter.toByteArray();
        //改造后的class文件
        FileOutputStream fos = new FileOutputStream("/Projects/Java/ASMTest/src/main/java/org/example/clazz/ASMTest2.class");
        fos.write(bytes);
        fos.close();

    }

    static class MyMethodVisitor extends AdviceAdapter{
        protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
            super(api, methodVisitor, access, name, descriptor);
        }

        @Override
        protected void onMethodEnter() {
            super.onMethodEnter();
            Type type1 = Type.getType("Ljava/lang/System;");
            Type type2 = Type.getType("Ljava/io/PrintStream;");
            //对应字节码指令getstatic,
            getStatic(type1,"out",type2);
            //对应字节码指令ldc
            visitLdcInsn("ASMTest=====>test");
            //对应字节码指令invokevirtual
            invokeVirtual(Type.getType("Ljava/io/PrintStream"),new Method("println","(Ljava/lang/String;)V"));
        }
    }
}

比较关键的地方我写了注释,我就不解释了,因为这只是AMS的一小部分api,更多的api可以自己找相关的文档来熟悉了解

二、在指定的方法进行插桩

执行上面的代码后,class如下:

package org.example;

public class ASMTest {
    public ASMTest() {
        System.out.println("ASMTest=====>test");
    }

    public void test() {
        System.out.println("ASMTest=====>test");
    }
}

我们发现,除了test方法中有了我们想要的代码,在构造方法中也多了这段代码。当然,我们可以在visitMethod方法中进行名称的判断,找到我们想要的方法,拦截不想改造的方法。但是很多时候,我们在进行插桩的时候,方法名称千万种,使用名称判断的这种办法是不可行的,那怎么办呢?我们可以使用注解,也就是说如果某个方法有特定的注解,则进行插桩操作;反之,不进行插桩

我们先创建一个注解:

@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD})
public @interface MyAnnotation {
}

然后给ASMTest.java中的test方法加上这个注解

public class ASMTest {
    @MyAnnotation
    public void test(){
    }
}

我们对执行类中的静态类MyMethodVisitor进行改造,如下:

static class MyMethodVisitor extends AdviceAdapter{
        boolean inject = false;
        protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
            super(api, methodVisitor, access, name, descriptor);
        }

        @Override
        public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
            if(descriptor.equals("Lorg/example/MyAnnotation;")){
                inject = true;
            }
            return super.visitAnnotation(descriptor, visible);
        }

        @Override
        protected void onMethodEnter() {
            super.onMethodEnter();
            if(inject) {
                Type type1 = Type.getType("Ljava/lang/System;");
                Type type2 = Type.getType("Ljava/io/PrintStream;");
                //对应字节码指令getstatic,
                getStatic(type1, "out", type2);
                //对应字节码指令ldc
                visitLdcInsn("ASMTest=====>test");
                //对应字节码指令invokevirtual
                invokeVirtual(Type.getType("Ljava/io/PrintStream;"), new Method("println", "(Ljava/lang/String;)V"));
            }
        }
    }

我们可以看见有如下几个变化:

inject:这个变量用来控制是否进行插桩操作

visitAnnotation:根据方法名称,我们也可以猜出来,就是解析方法的注解,如果方法没有注解,则不执行。方法里,我们进行了注解签名的判断,如果是我们想要的注解,则修改inject为true

之后,我们再执行,会得到如下结果:

package org.example;

public class ASMTest {
    public ASMTest() {
    }

    @MyAnnotation
    public void test() {
        System.out.println("ASMTest=====>test");
    }
}

只有@MyAnnotation注解的方法,才进行了插桩

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值