ASM初步使用(一)

Android ASM插桩的初步使用(一)

今天看了插桩,主要还是对ASM的初步使用。废话也不多说,介绍网上都有,直接上干货。
前面介绍部分 摘抄了不少
奶盖ww 的 成为架构师的路上,你究竟了解多少字节码插桩?
大家可以看一看,我也不太懂。

ASM插桩流程

  1. 需要创建一个 ClassReader 对象,将 .class 文件的内容读入到一个字节数组中
  2. 然后需要一个 ClassWriter 的对象将操作之后的字节码的字节数组回写
  3. 需要事件过滤器 ClassVisitor。在调用 ClassVisitor 的某些方法时会产生一个新的 XXXVisitor 对象
  4. 当我们需要修改对应的内容时只要实现自己的 XXXVisitor 并返回就可以了

ClassReader 类

这个类会将 .class 文件读入到 ClassReader 中的字节数组中,它的 accept 方法接受一个 ClassVisitor 实现类,并按照顺序调用 ClassVisitor 中的方法

ClassWriter 类

ClassWriter 是一个 ClassVisitor 的子类,是和 ClassReader 对应的类,ClassReader 是将 .class 文件读入到一个字节数组中,ClassWriter 是将修改后的类的字节码内容以字节数组的形式输出。

ClassVisitor 抽象类

void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
该方法是当扫描类时第一个调用的方法,主要用于类声明使用。下面是对方法中各个参数的示意:visit( 类版本 , 修饰符 , 类名 , 泛型信息 , 继承的父类 , 实现的接口)* AnnotationVisitor visitAnnotation(String desc, boolean visible)
该方法是当扫描器扫描到类注解声明时进行调用。
下面是对方法中各个参数的示意:

  • visitAnnotation(注解类型 , 注解是否可以在 JVM 中可见)。
  • FieldVisitor visitField(int access, String name, String desc, String signature, Object value)该方法是当扫描器扫描到类中字段时进行调用。下面是对方法中各个参数的示意:visitField(修饰符 , 字段名 , 字段类型 , 泛型描述 , 默认值)
  • MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
    该方法是当扫描器扫描到类的方法时进行调用。下面是对方法中各个参数的示意:visitMethod(修饰符 , 方法名 , 方法签名 , 泛型信息 , 抛出的异常)
  • void visitEnd()
    该方法是当扫描器完成类扫描时才会调用,如果想在类中追加某些方法

MethodVisitor & AdviceAdapter

MethodVisitor 是一个抽象类,当 ASM 的 ClassReader 读取到 Method 时就转入 MethodVisitor 接口处理。
AdviceAdapter 是 MethodVisitor 的子类,使用 AdviceAdapter 可以更方便的修改方法的字节码。

AdviceAdapter

其中比较重要的几个方法如下:

void visitCode():表示 ASM 开始扫描这个方法
void onMethodEnter():进入这个方法
void onMethodExit():即将从这个方法出去
void onVisitEnd():表示方法扫码完毕

引用

首先,不用多想,肯定先上gradle插件引用啦

 	testImplementation 'org.ow2.asm:asm:7.1'
    testImplementation 'org.ow2.asm:asm-commons:7.1'

由于本身使用asm,上面的包肯定是要导进来的,下面的主要还是对AdciceAdapter的使用。比传统自带的MethodVisitor好用些,我也没用过MethodVisitor,可能后面看看,先用AdciceAdapter吧。

写一个java文件

这个不多说,直接写一个java最简单的sout

 public static void main(String[] var0) throws InterruptedException {
        long s1 = System.currentTimeMillis();
        Thread.sleep(1000L);
        long s2 = System.currentTimeMillis();
        System.out.println(s2 - s1);
    }

运行下 得到了.class文件。
很nice。下面就开始ASM字节码编写了

推荐个插件 在这里插入图片描述
这个直接在AS的插件就能找到,方便开发。

对上面的进行插件转化得到下面的代码:

public class com/example/asmtest/MyClass {

  // compiled from: MyClass.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/example/asmtest/MyClass; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V throws java/lang/InterruptedException 
   L0
    LINENUMBER 6 L0
    INVOKESTATIC java/lang/System.currentTimeMillis ()J
    LSTORE 1
   L1
    LINENUMBER 7 L1
    LDC 1000
    INVOKESTATIC java/lang/Thread.sleep (J)V
   L2
    LINENUMBER 8 L2
    INVOKESTATIC java/lang/System.currentTimeMillis ()J
    LSTORE 3
   L3
    LINENUMBER 9 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LLOAD 3
    LLOAD 1
    LSUB
    INVOKEVIRTUAL java/io/PrintStream.println (J)V
   L4
    LINENUMBER 10 L4
    RETURN
   L5
    LOCALVARIABLE args [Ljava/lang/String; L0 L5 0
    LOCALVARIABLE s1 J L1 L5 1
    LOCALVARIABLE s2 J L3 L5 3
    MAXSTACK = 5
    MAXLOCALS = 5
}

准备完毕,开始写代码了。

开始

步骤一

首先要把.class文件加载进来
将插桩结果写在另一个class文件

 try {
            FileInputStream fis = new FileInputStream("D:\\Example\\ASMTest\\app\\src\\test\\java\\com\\example\\asmtest\\MyClass.class");
            ClassReader classReader = new ClassReader(fis);
            ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

            classReader.accept(new MyClassVisitor(7 << 16 | 0 << 8, classWriter), ClassReader.EXPAND_FRAMES);
            byte[] bytes = classWriter.toByteArray();
            FileOutputStream fos = new FileOutputStream("D:\\Example\\ASMTest\\app\\src\\test\\java\\com\\example\\asmtest\\MyClass1.class");
            fos.write(bytes);
            fos.close();
            fis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
这个就是我将\MyClass.class读进来然后写在\MyClass1.class文件中。

MyClassVisitor

这个类是我自己继承ClassVisitor写的自定义类

  @Override
        public MethodVisitor visitMethod(int i, String s, String s1, String s2, String[] strings) {
            MethodVisitor methodVisitor = super.visitMethod(i, s, s1, s2, strings);
            System.out.println(s);
            return new MyMethod(api, methodVisitor, i, s, s1);
        }

主要重写了visitMethod方法, 返回调用MyMethod类。

MyMethod

如前文所说,onMethodEnter()和onMethodExit(int opcode)方法十分重要。我们重要重写这个方法。

 public class MyMethod extends AdviceAdapter {

            protected MyMethod(int i, MethodVisitor methodVisitor, int i1, String s, String s1) {
                super(i, methodVisitor, i1, s, s1);
            }

            int s;

            @Override
            protected void onMethodEnter() {
                super.onMethodEnter();
                invokeStatic(Type.getType("Ljava/lang/System;"), new Method("currentTimeMillis", "()J"));
                s = newLocal(Type.LONG_TYPE);
                storeLocal(s);

            }

            int s2;

            @Override
            protected void onMethodExit(int opcode) {
                super.onMethodExit(opcode);
                super.endMethod();
                invokeStatic(Type.getType("Ljava/lang/System;"), new Method("currentTimeMillis", "()J"));
                s2 = newLocal(Type.LONG_TYPE);
                storeLocal(s2);
                /*
                *  GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
                    LLOAD 3
                    LLOAD 1
                    LSUB
                    INVOKEVIRTUAL java/io/PrintStream.println (J)V
                * */
                getStatic(Type.getType("Ljava/lang/System;"), "out", Type.getType("Ljava/io/PrintStream;"));

                loadLocal(s);
                loadLocal(s2);
                math(SUB, Type.LONG_TYPE);
                invokeVirtual(Type.getType("Ljava/io/PrintStream;"), new Method("println", "(J)V"));
            }

这个代码是怎么出来的呢,主要还是依靠上面的插件啦!
在这里插入图片描述
这样,依靠这个就写出来了,对了,附上方法签名表
在这里插入图片描述
引用的地址附上 https://www.jianshu.com/p/44d3c7d46e5f

验证

我们看看结果,把.java文件的main方法只留一个Thread.sleep(1000L);
得到.class文

public class MyClass {
    public MyClass() {
    }
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(1000L);
    }
}

运行代码:
在这里插入图片描述
OK成功啦,自动生成了!入门就这样。现在是都没有任何的过滤,所有的全部都加上这个语句,那么如果想要进行添加的话,可以自定义添加注解,通过注解再进行过滤。
ASM初步使用(二)
可以看看我写的代码:https://github.com/wcz201/ASMTest

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值