史上最全的ASM原理解析与应用

2022-10-20  本文已影响0人  唯爱_0834

ASM简介

ClassFile

ClassFile {
    u4             magic;  //魔数
    u2             minor_version; //次版本号
    u2             major_version; //主版本号
    u2             constant_pool_count; //常量池容量:从1开始,0:不引用任何一个常量池数据
    cp_info        constant_pool[constant_pool_count-1]; //常量数据,数据构成有17种,以首位u1表示tag类型
    u2             access_flags; //访问标记,识别类或接口的访问信息如:ACC_PUBLIC;ACC_ABSTRACT,由于每个标记占用二进制位不同,使用|表示交集;
    u2             this_class; //当前类索引:常量池中偏移量指向一个类型为CONSTANT_Class_info的类描述符常量
    u2             super_class; //当前类的父类索引:java单继承机制
    u2             interfaces_count; //当前类实现的接口计数器
    u2             interfaces[interfaces_count]; //接口索引表:常量池中偏移量
    u2             fields_count;  //字段计数器
    field_info     fields[fields_count]; //字段表:由类级变量static和实例变量(全局),不包括局部变量
    u2             methods_count; //方法数
    method_info    methods[methods_count]; //方法表
    u2             attributes_count; //属性数
    attribute_info attributes[attributes_count]; //属性表
}

字段表

field_info {
    u2             access_flags; //字段的访问标记
    u2             name_index;  //字段的名称
    u2             descriptor_index; //字段的描述符
    u2             attributes_count; //字段属性
    attribute_info attributes[attributes_count];
}
  1. 全限定名:类的全限定名是将类全名的.全部替换为/,如java.lang.String ,全限定名java/lang/String

  2. 简单名称:方法main() 简单名称为main,全局变量num为名称num

  3. 描述符:基本数据类型及void的由大写字母表示,对象类型有L+全限定名=> Ljava/lang/String;表示String字段描述符;对于数组类型根据维度在前面加上“[”,如int[] => [I ; String[] => [Ljava/lang/String;


    desc_asm.png
  4. attribute_info:字段的属性表,存储一些额外信息;

方法表

method_info {
    u2             access_flags;  //访问标记
    u2             name_index;  //方法简单名称常量池索引
    u2             descriptor_index; //方法描述符索引
    u2             attributes_count; //方法属性表
    attribute_info attributes[attributes_count];
}

属性表

public class HelloWorld {

    public void foo() {
        int i = 0;
        int j = 0;
        if (i > 0) {
            int k = 0;
        }
        int l = 0;
    }
}

//使用javap -v 获取foo字节码code数据如下:
public void foo();
    descriptor: ()V //方法描述符
    flags: (0x0001) ACC_PUBLIC //方法访问标记
    Code:  033C033D 1B9E0005 033E033E B1
      stack=1, locals=4, args_size=1  //stack:操作数栈深度,locals:局部变量表,args_size:方法参数的个数,包括方法参数、this
         0: iconst_0
         1: istore_1
         2: iconst_0
         3: istore_2
         4: iload_1    // 1B
          /**
          * ifle 字节码 9E -->后面u2类型字段为 0005 表示偏移量,当前5 + 偏移量 = 10
          *  10由code字节码偏移量6,7位置,code属性中字节码长度由u4表示,
          *   但是《Java虚拟机规范》中明确限制了一个方法不允许超过65535条字节码指令,即实际只使用了u2的长度,如果超过这个限制,Javac编译器就会拒绝编译,因此这里使用u2表示跳转字节码偏移量
          */
         5: ifle          10       //9E 0005
         8: iconst_0     //03
         9: istore_3
        10: iconst_0
        11: istore_3
        12: return
      LineNumberTable: //源码行数与code中偏移量对应关系,常用于log中输出日志
        line 11: 0
        line 12: 2
        line 13: 4
        line 14: 8
        line 18: 10
        line 20: 12
      LocalVariableTable:  //描述栈帧中局部变量表的变量与Java源码中定义的变量之间的关系
        //start:局部变量的生命周期开始的字节码偏移量
        //Length:其作用范围覆盖的长度,两者结合起来就是这个局部变量在字节码之中的作用域范围
        //Slot: 局部变量表位置:对应上方最大locals=4,根据字节码可以验证stack最大为1
        Start  Length  Slot  Name   Signature  
            0      13     0  this   Lsample/HelloWorld;
            2      11     1     i   I
            4       9     2     j   I
           12       1     3     l   I
      StackMapTable: number_of_entries = 1  //栈映射帧Stack Map Frame个数:1
      
      //虚拟机类加载的字节码验证阶段被新类型检查验证器使用,目的代替以前比较消耗性能的基于数据流分析的类型推导验证器;其中记录的是一个方法中操作数栈与局部变量区的类型在一些特定位置的状态。
        frame_type = 253 /* append */  //基本块开头处的状态:frame_type = 251,表示多了2个局部变量,append 增加变量,chop 减少变量
        offset_delta = 10  //栈映射帧的code偏移量为10,记录的是if跳转语句
        locals = [ int, int ]  //增量设置,进入时有默认Frame局部变量区:[this],在10位置变量k已经过了作用域局部变量区:[ this, int, int],增量为 locals = [ int, int ]

ASM组成

ClassReader拆分

public class ClassReader {
        
    //真实的数据部分
    final byte[] classFileBuffer;
    
    //数据的索引信息:标识了classFileBuffer中数据里包含的常量池位置
    private final int[] cpInfoOffsets;
    
    //标记访问标识(access flag)在classFileBuffer中的位置信息
    public final int header;
   
    /**
    * 全局变量初始化
    * classFileOffset :默认为0,起始位置
    */
    ClassReader(byte[] classFileBuffer, int classFileOffset, boolean checkClassVersion) {
        this.classFileBuffer = classFileBuffer; //class文件数据字节数组
        this.b = classFileBuffer;
        if (checkClassVersion && this.readShort(classFileOffset + 6) > 60) {
            throw new IllegalArgumentException("Unsupported class file major version " + this.readShort(classFileOffset + 6));
        } else {
                //读取第8个字节位置:常量池大小constant_pool_count
            int constantPoolCount = this.readUnsignedShort(classFileOffset + 8);
            this.cpInfoOffsets = new int[constantPoolCount];
                        
                        //当前常量池起始位置:注意ClassFile由1开始,保留0位置用于未指定任何数据
            int currentCpInfoIndex = 1;
            
            //起始偏移量,首位常量池位置:(魔数u4,次版本号u2,主版本号u2,常量池大小u2)
            int currentCpInfoOffset = classFileOffset + 10; //从第10个字节开始保存常量

            
            //当前各个常量的偏移量
            int cpInfoSize;
            for(hasConstantDynamic = false; currentCpInfoIndex < constantPoolCount; currentCpInfoOffset += cpInfoSize) {        
                    /**
                    * 常量池的数据:u1:表示当前数据类型
                    CONSTANT_Utf8_info {
                        u1 tag;  // == 1
                        u2 length; 
                        u1 bytes[length];
                } 
                    */
                this.cpInfoOffsets[currentCpInfoIndex++] = currentCpInfoOffset + 1; //去掉u1数据类型保存常量数据
                switch(classFileBuffer[currentCpInfoOffset]) { //currentCpInfoOffset记录的为当前常量类型tag
                case 1:  //字符串计算字符串长度作为偏移量cpInfoSize
                    //tag:u1 length:u2 = 3 加上Short位的length表示bytes数组长度的
                    cpInfoSize = 3 + this.readUnsignedShort(currentCpInfoOffset + 1);  
                  
                    ...
                    break;
                    ...
                }
                /**
                * currentCpInfoOffset:常量池数据已经全部遍历完存入cpInfoOffsets中,此时位置为:access_flags
                */
                this.header = currentCpInfoOffset;
public void accept(ClassVisitor classVisitor, Attribute[] attributePrototypes, int parsingOptions) {
        ...
        int currentOffset = this.header; //currentOffset指定位置为:u2 access_flags
        int accessFlags = this.readUnsignedShort(currentOffset); //获取类标识位 Short
        String thisClass = this.readClass(currentOffset + 2, charBuffer); // +2获取当前类索引 this_class
        String superClass = this.readClass(currentOffset + 4, charBuffer); // +4 获取当前父类索引 super_class
        String[] interfaces = new String[this.readUnsignedShort(currentOffset + 6)]; //接口集合数据:大小 + 6
        currentOffset += 8; //u2:access_flags, u2:this_class, u2:super_class , u2:interfaces_count
                
                //获取实现接口数据
                int innerClassesOffset;
        for(innerClassesOffset = 0; innerClassesOffset < interfaces.length; ++innerClassesOffset) {
            interfaces[innerClassesOffset] = this.readClass(currentOffset, charBuffer);
            currentOffset += 2; //interfaces[] 数组:数据类型u2
        }
        
        ...
        //获取属性表位置:attribute_info
        int currentAttributeOffset = this.getFirstAttributeOffset();
                //属性表个数:attributes_count
        int fieldsCount;
        for(fieldsCount = this.readUnsignedShort(currentAttributeOffset - 2); fieldsCount > 0; --fieldsCount) {
            String attributeName = this.readUTF8(currentAttributeOffset, charBuffer);
            int attributeLength = this.readInt(currentAttributeOffset + 2);
            currentAttributeOffset += 6;
            ...
            if ("Signature".equals(attributeName)) { //若当前类属性有泛型,则读取其信息
                signature = this.readUTF8(currentAttributeOffset, charBuffer);
            } 
                        ...
            currentAttributeOffset += attributeLength;
                        
        }
        
        
        //调用visit方法,每个类只会调用一次,参数为我们读取到字节码数据
    classVisitor.visit(this.readInt(this.cpInfoOffsets[1] - 7), accessFlags, thisClass, signature, superClass, interfaces);
        ...
    
    //获取filed字段个数
    fieldsCount = this.readUnsignedShort(currentOffset);
        //readField() 调用classVisitor.visitField()方法
    for(currentOffset += 2; fieldsCount-- > 0; currentOffset = this.readField(classVisitor, context, currentOffset)) {}
        //获取method方法个数
    methodsCount = this.readUnsignedShort(currentOffset);
        //readMethod() 调用classVisitor.visitMethod()方法
    for(currentOffset += 2; methodsCount-- > 0; currentOffset = this.readMethod(classVisitor, context, currentOffset)) {}

    classVisitor.visitEnd();    
private int readField(ClassVisitor classVisitor, Context context, int fieldInfoOffset) {
        //descriptor :字段描述符 int:I ; constantValue :字段默认值
    FieldVisitor fieldVisitor = classVisitor.visitField(accessFlags, name, descriptor, signature,constantValue);
    ...
    fieldVisitor.visitEnd();
    return currentOffset;
}

private int readMethod(ClassVisitor classVisitor, Context context, int methodInfoOffset) {
        //调用classVisitor.visitMethod()扫描类中每一个方法
    MethodVisitor methodVisitor = classVisitor.visitMethod(context.currentMethodAccessFlags, context.currentMethodName, context.currentMethodDescriptor, signatureIndex == 0 ? null : this.readUtf(signatureIndex, charBuffer), exceptions);
    ...
    //获取方法注解
    if (annotationDefaultOffset != 0) {
        AnnotationVisitor annotationVisitor = methodVisitor.visitAnnotationDefault();
        this.readElementValue(annotationVisitor, annotationDefaultOffset, (String)null, charBuffer);
        if (annotationVisitor != null) {
            annotationVisitor.visitEnd();
        }
    }
    ...
    //如果方法存在code属性
    if (codeOffset != 0) {
        methodVisitor.visitCode();
        this.readCode(methodVisitor, context, codeOffset);
    }

    methodVisitor.visitEnd();
    return currentOffset;
}
private void readCode(MethodVisitor methodVisitor, Context context, int codeOffset) {
    byte[] classBuffer = this.classFileBuffer;
    //常用加载字符串字节码命令编号:0x12 -> 18 ldc 表示int、float或String型常量从常量池推送至栈顶
    case 18:
            //调用visitLdcInsn获取常量池中数据readConst读取utf-8字符串
        methodVisitor.visitLdcInsn(this.readConst(classBuffer[currentOffset + 1] & 255, charBuffer));
        currentOffset += 2;
    break;
    ...
    
    case 178: //0xb2 getstatic
    case 179: //0xb3 putstatic
    case 180: //0xb4 getfield
    case 181: //0xb5 putfield
    case 182: //0xb6 invokevirtual
    case 183: //0xb7 invokespecial
    case 184: //0xb8 invokestatic
    case 185: //0xb9 invokeinterface
    
        typeAnnotationOffset = this.cpInfoOffsets[this.readUnsignedShort(currentOffset + 1)];
        targetType = this.cpInfoOffsets[this.readUnsignedShort(typeAnnotationOffset + 2)];
        annotationDescriptor = this.readClass(typeAnnotationOffset, charBuffer);
        descriptor = this.readUTF8(targetType, charBuffer);
        signature = this.readUTF8(targetType + 2, charBuffer);
        if (startPc < 182) { 
                //如果字节码是对字段操作,调用methodVisitor.visitFieldInsn
            methodVisitor.visitFieldInsn(startPc, annotationDescriptor, descriptor, signature);
        } else {
                //如果字节码是对方法操作,则调用methodVisitor.visitMethodInsn
            boolean isInterface = classBuffer[typeAnnotationOffset - 1] == 11;
            methodVisitor.visitMethodInsn(startPc, annotationDescriptor, descriptor, signature, isInterface);
        }

        if (startPc == 185) {
            currentOffset += 5;
        } else {
            currentOffset += 3;
        }
        break;
        ...
    //对方法栈桢中的最大操作数栈和最大局部变量表进行赋值     
    methodVisitor.visitMaxs(maxStack, maxLocals);
}

ClassWriter组合

public class ClassWriter extends ClassVisitor {
    public static final int COMPUTE_MAXS = 1;
    public static final int COMPUTE_FRAMES = 2;
    
    private int version; //版本号
    private final SymbolTable symbolTable; //常量池信息
     
    private int accessFlags;  //标识位
    private int thisClass;  //当前类索引
    private int superClass; //当前类父类索引
    private int interfaceCount; //接口数据
    private int[] interfaces;
    
    private FieldWriter firstField; //字段表
    private FieldWriter lastField;
    
    private MethodWriter firstMethod; //方法表
    private MethodWriter lastMethod;
    
    private Attribute firstAttribute; //属性表
    
    //通过构造函数封装为SymbolTable对象:主要是解析类信息中,主要是常量池信息
    public ClassWriter(ClassReader classReader, int flags) {
        super(589824);
        this.symbolTable = classReader == null ? new SymbolTable(this) : new SymbolTable(this, classReader);
    }
    
    public final void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        this.version = version;
        this.accessFlags = access;
        //根据类名全限定名获取在常量池中下标
        this.thisClass = this.symbolTable.setMajorVersionAndClassName(version & '\uffff', name);
        if (signature != null) {
            this.signatureIndex = this.symbolTable.addConstantUtf8(signature);
        }
            
        this.superClass = superName == null ? 0 : this.symbolTable.addConstantClass(superName).index;
        if (interfaces != null && interfaces.length > 0) {
            this.interfaceCount = interfaces.length;
            this.interfaces = new int[this.interfaceCount];

            for(int i = 0; i < this.interfaceCount; ++i) {
                this.interfaces[i] = this.symbolTable.addConstantClass(interfaces[i]).index;
            }
        }
    }
    //字段及方法通过链表连接,由firstField -> lastField; firstMethod -> lastMethod
     public final FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
        FieldWriter fieldWriter = new FieldWriter(this.symbolTable, access, name, descriptor, signature, value);
        if (this.firstField == null) {
            this.firstField = fieldWriter;
        } else {
            this.lastField.fv = fieldWriter;
        }
        return this.lastField = fieldWriter;
    }

    public final MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        ...
    }
    //属性表也是通过链表
    public final void visitAttribute(Attribute attribute) {
        attribute.nextAttribute = this.firstAttribute;
        this.firstAttribute = attribute;
    }
}

组装.class文件

  1. 计算byte[]数组,即class文件大小size;
  2. 向byte数组中按照classFile格式添加对应元素;
  3. 将byte[] 数据返回;
计算size
public byte[] toByteArray() {
        /**
        * 24: u4 magic , 10个必须字段u2(minor_version, major_version, constant_pool_count, access_flags,
        *    this_class , super_class, interfaces_count, fields_count, methods_count, attributes_count)
        * 接口字段集合为 u2 * interfaceCount
        * 剩余未计算: cp_info , field_info , method_info , attribute_info
        */
    int size = 24 + 2 * this.interfaceCount;
    int fieldsCount = 0;
        
        /**
        * 链表计算字段占用大小 field_info
        */
    FieldWriter fieldWriter;
    for(fieldWriter = this.firstField; fieldWriter != null; fieldWriter = (FieldWriter)fieldWriter.fv) {
        ++fieldsCount;
        size += fieldWriter.computeFieldInfoSize();
    }

        /**
        * 链表计算方法占用大小 method_info
        */
    int methodsCount = 0;
    MethodWriter methodWriter;
    for(methodWriter = this.firstMethod; methodWriter != null; methodWriter = (MethodWriter)methodWriter.mv)      {
        ++methodsCount;
        size += methodWriter.computeMethodInfoSize();
    }
    
    /**
        * 计算属性表占用大小 attribute_info
        */
    int attributesCount = 0;
    ......

    if (firstAttribute != null) {
        attributesCount += firstAttribute.getAttributeCount();
        size += firstAttribute.computeAttributesSize(symbolTable);
    }
    /**
        * 计算常量池占用大小 cp_info
        */
    size += this.symbolTable.getConstantPoolLength();
  1. 必要位: 由classFile格式可知总计有24个字节,接口数据有 2*interfaceCount
  2. 其他位: 依次计算剩下的常量池,字段,方法,属性大小
  3. 汇总以上数据获取.class文件大小
添加数据
public byte[] toByteArray() { 
        ...
        //创建ByteVector存储对象,大小为上方计算的size
    ByteVector result = new ByteVector(size);
    //添加魔数,version int u4:次版本号+主版本号
    result.putInt(0xCAFEBABE).putInt(this.version);
    
    /**
    * 添加常量池数据:常量池大小u2 + 常量数组内容大小
    * void putConstantPool(ByteVector output) {
        output.putShort(this.constantPoolCount).putByteArray(this.constantPool.data, 0,this.constantPool.length);
    }
    */
    this.symbolTable.putConstantPool(result);
    
    int mask = (version & 0xFFFF) < Opcodes.V1_5 ? Opcodes.ACC_SYNTHETIC : 0;
    //添加类标识位,当前类索引,父类索引
    result.putShort(this.accessFlags & ~mask).putShort(this.thisClass).putShort(this.superClass);
    //添加接口长度
    result.putShort(this.interfaceCount);
        //添加接口数组
    for(int i = 0; i < this.interfaceCount; ++i) {
    result.putShort(this.interfaces[i]);
    }
        
        //添加字段长度u2
    result.putShort(fieldsCount);
        //循环添加字段信息
    for(fieldWriter = this.firstField; fieldWriter != null; fieldWriter = (FieldWriter)fieldWriter.fv) {
        fieldWriter.putFieldInfo(result);
    }
        
        //添加方法长度
    result.putShort(methodsCount);
    boolean hasFrames = false;
    boolean hasAsmInstructions = false;

    for(methodWriter = this.firstMethod; methodWriter != null; methodWriter = (MethodWriter)methodWriter.mv) {
        hasFrames |= methodWriter.hasFrames();
        hasAsmInstructions |= methodWriter.hasAsmInstructions();
        methodWriter.putMethodInfo(result);
    }
        
        //添加属性长度
    result.putShort(attributesCount);
    
    ···
    //添加属性表
    if (this.firstAttribute != null) {
        this.firstAttribute.putAttributes(this.symbolTable, result);
    }
  1. 创建大小为size的字节集合对象ByteVector
  2. 按照classFile格式由前往后依次添加元素
返回byte数据
public byte[] toByteArray() {
        ...
    // Third step: replace the ASM specific instructions, if any.
    if (hasAsmInstructions) { //如果有ASM特定说明,需要替换为JVM字节码,否则JVM不认识的
      return replaceAsmInstructions(result.data, hasFrames);
    } else {
      return result.data;
    }
}

应用

无状态转换

查找方法
  1. 创建自定义MethodFindRefVisitor继承自ClassVisitor,重写visitMethod()判断code中是否调用了指定查找信息
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
    boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
    boolean isNativeMethod = (access & ACC_NATIVE) != 0;
    if (!isAbstractMethod && !isNativeMethod) {
        return new MethodFindRefAdaptor(api, null, owner, name, descriptor);
    }
    return null;
}
  1. 创建自定义MethodFindRefAdaptor继承自MethodVisitor,对方法体code进行解析重写visitMethodInsn判断是否调用查找方法
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
    // 首先,处理自己的代码逻辑,判断当前方法体中调用了指定查找的方法,则存储当前类和方法信息
    if (methodOwner.equals(owner) && methodName.equals(name) && methodDesc.equals(descriptor)) {
        String info = String.format("%s.%s%s", currentMethodOwner, currentMethodName, currentMethodDesc);
        if (!resultList.contains(info)) {
        resultList.add(info);
        }
    }

    // 其次,调用父类的方法实现
    super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
  1. 在合适时机对获取数据解析输出打印或文件中
替换方法
public class HelloWorld {

    public void test(int a, int b){
        add(a , b); //调用非静态方法
        getDesc("HelloWorld"); //调用静态方法
    }
    private int add(int a , int b){
        return a + b;
    }
    public static String getDesc(String clazzName){
        return "current class " + clazzName;
    }
}

//输出字节码如下
public void test(int, int);
    descriptor: (II)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=3  //局部变量表中位置: 0:this; 1:a ; 2:b
         0: aload_0   //加载this到操作数栈
         1: iload_1   //加载a到操作数栈
         2: iload_2   //加载b到操作数栈
         3: invokespecial #2                  // Method add:(II)I  调用非静态方法add
         6: pop
         7: ldc           #3                  // String HelloWorld
         9: invokestatic  #4                  // Method getDesc:(Ljava/lang/String;)Ljava/lang/String;
        12: pop
        13: return
        
//asm代码如下
{
  methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "test", "(II)V", null, null);
  methodVisitor.visitCode();
  methodVisitor.visitVarInsn(ALOAD, 0);
  methodVisitor.visitVarInsn(ILOAD, 1);
  methodVisitor.visitVarInsn(ILOAD, 2);
  methodVisitor.visitMethodInsn(INVOKESPECIAL, "sample/HelloWorld", "add", "(II)I", false); //调用non-static add方法,上面三个visitVarInsn方法是方法所需参数
  methodVisitor.visitInsn(POP);
  
  methodVisitor.visitLdcInsn("HelloWorld");
  methodVisitor.visitMethodInsn(INVOKESTATIC, "sample/HelloWorld", "getDesc", "(Ljava/lang/String;)Ljava/lang/String;", false); //调用static getDesc需要一个参数
  methodVisitor.visitInsn(POP);
  methodVisitor.visitInsn(RETURN);
  methodVisitor.visitMaxs(3, 3);
  methodVisitor.visitEnd();
}
test:(II)V                                         局部变量表         | 操作数栈
                               // {this, int, int} | {}
0000: aload_0                  // {this, int, int} | {this}
0001: iload_1                  // {this, int, int} | {this, int}
0002: iload_2                  // {this, int, int} | {this, int, int} 
0003: invokespecial   #2       // {this, int, int} | {int} //将操作数栈上数据消耗掉后存储返回值
0006: pop                      // {this, int, int} | {}
0007: ldc             #3       // {this, int, int} | {String}
0009: invokestatic    #4       // {this, int, int} | {String} //消耗掉String后存储返回值
0012: pop                      // {this, int, int} | {}
0013: return                   // {} | {}
//自定义MethodReplaceInvokeAdapter extends MethodVisitor替换visitMethodInsn方法
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
    if (oldOwner.equals(owner) && oldMethodName.equals(name) && oldMethodDesc.equals(desc)){
        //这里是我们自定义替换的方法
        super.visitMethodInsn(newOpcode , newOwner , newMethodName , newMethodDesc , false);
    }else{
        super.visitMethodInsn(opcode, owner, name, desc, itf);
    }
}

/**
* 调用区别newMethodDesc方法描述符是否需要消耗掉this
*/
//静态方法替换,描述符中不添加this
 ClassVisitor cv = new MethodReplaceInvokeVisitor(Opcodes.ASM9, cw,
                "sample/HelloWorld", "getDesc", "(Ljava/lang/String;)Ljava/lang/String;",
                Opcodes.INVOKESTATIC, "com/asm/method/ReplaceMethodManager", "getDesc", "(Ljava/lang/String;)Ljava/lang/String;"); 
//非静态方法替换
ClassVisitor cv = new MethodReplaceInvokeVisitor(Opcodes.ASM9, cw, "sample/HelloWorld", "add", "(II)I", Opcodes.INVOKESTATIC, "com/asm/method/ReplaceMethodManager", "add", "(Lsample/HelloWorld;II)I");

有状态转换

  1. 删除指令: 移除ICONST_0 IADD。例如,int d = c + 0;与int d = c;两者效果是一样的,所以+ 0的部分可以删除掉
  2. 删除指令: 移除ALOAD_0 ALOAD_0 GETFIELD PUTFIELD。例如,this.val = this.val;,将字段的值赋值给字段本身,无实质意义
state machine
  1. 创建MethodPatternAdapter抽象类,visitXxxInsn方法中调用visitInsn
public abstract class MethodPatternAdapter extends MethodVisitor {
    protected final static int SEEN_NOTHING = 0; //初始状态
    protected int state; //记录状态变化

    public MethodPatternAdapter(int api, MethodVisitor methodVisitor) {
        super(api, methodVisitor);
    }

        @Override
    public void visitLdcInsn(Object value) {
        visitInsn();
        super.visitLdcInsn(value);
    }
    
    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
        visitInsn();
        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
    }
    ......
    
     protected abstract void visitInsn();
  1. 将问题转换成Instruciton指令,然后对多个指令组合的特征或遵循的模式进行总结:如下Hybrid添加action示例中如何获取action列表
Map<String, Class<? extends RegisteredActionCtrl>> actions = new HashMap<>();
actions.put(CarPublishBackParser.ACTION, CarPublishBackActionCtrl.class);
actions.put(CarPublishGuideParser.ACTION, CarPublishGuideActionCtrl.class);
Hybrid.add(actions);

//转换为asm代码如下
methodVisitor.visitCode();
methodVisitor.visitTypeInsn(NEW, "java/util/HashMap");
methodVisitor.visitInsn(DUP);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "<init>", "()V", false);
methodVisitor.visitVarInsn(ASTORE, 0);
methodVisitor.visitVarInsn(ALOAD, 0);

//添加第一个
methodVisitor.visitLdcInsn("publish_car_go_back");
methodVisitor.visitLdcInsn(Type.getType("Lcom/wuba/carLib/manager/CarPublishBackActionCtrl;"));
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);

methodVisitor.visitInsn(POP);
methodVisitor.visitVarInsn(ALOAD, 0);

//添加第二个
methodVisitor.visitLdcInsn("show_publish_leadingpage");
methodVisitor.visitLdcInsn(Type.getType("Lcom/wuba/carLib/manager/CarPublishGuideActionCtrl;"));
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);

methodVisitor.visitInsn(POP);

a. 通过以上观察,对于actions.put方法调用实际上是三个instruction:visitLdcInsn(key), visitLdcInsn(value),visitMethodInsn

b. 总共调用三个方法,我们只需要在添加两个状态即可满足,状态转换如下图所示

//调用visitLdcInsn(key)后的状态
private static final int SEEN_LDCTYPE = 1
//调用visitLdcInsn(value)后的状态
private static final int SEEN_LDC_METHOD = 2
state_transforme.png
  1. 状态转移清楚后,写代码就很轻松了
public class ReadActionMapMethod extends MethodPatternAdapter {

    /**
     * 需要校验的字节码顺序:Map.put()/HashMap.put()
     */
    private static final int SEEN_LDCTYPE = 1

    private static final int SEEN_LDC_METHOD = 2
    //当前存储业务线
    private String businessLine
    /**
     * 当前第一步缓存数据
     */
    private String mMapKey
    /**
     * 当前第二步缓存数据
     */
    private Type mMapValue

    protected ReadActionMapMethod(int api, MethodVisitor methodVisitor, String businessLine) {
        super(api, methodVisitor)
        this.businessLine = businessLine
    }

    @Override
    void visitLdcInsn(Object value) {

        switch (state) {
            case SEEN_NOTHING:
                if (value instanceof String) {
                    state = SEEN_LDCTYPE
                    mMapKey = value
                    mv.visitLdcInsn(value)
                    return
                }
                break
            case SEEN_LDCTYPE:
                if (value instanceof Type) {
                    state = SEEN_LDC_METHOD
                    mMapValue = value
                    mv.visitLdcInsn(value)
                    return
                }
                break

        }
        super.visitLdcInsn(value)
    }

    @Override
    protected void visitInsn() {
//        switch (state) {
//
//            case SEEN_LDCTYPE:
//                //将拦截的数据发送出去
//                mv.visitLdcInsn(mMapKey)
//                break
//
//            case SEEN_LDC_METHOD:
//                //将拦截的数据发送出去
//                mv.visitLdcInsn(mMapKey)
//                mv.visitLdcInsn(mMapValue)
//                break
//        }
        state = SEEN_NOTHING

    }

    @Override
    void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
        switch (state) {
            case SEEN_LDC_METHOD:
                boolean flag = ((opcode == Opcodes.INVOKEVIRTUAL && owner == "java/util/HashMap" && name == "put")
                        || (opcode == Opcodes.INVOKEINTERFACE && owner == "java/util/Map" && name == "put"))
                if (flag) { //需要存储数据啦
                    HybridActionManager.install.addAction(businessLine, mMapKey)
                }
                break
        }
        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)

    }

思考:

  1. 当前只是检查三个状态,如何提升准确性?
@Override
protected List<ModuleSpec> createWubaNativeModules(final ReactApplicationContextWrapper reactApplicationContext) {
       List<ModuleSpec> moduleSpecList = new ArrayList<ModuleSpec>();
       moduleSpecList.add(new ModuleSpec(new Provider<NativeModule>() {
           @Override
           public NativeModule get() {
               return new WBSingleSelector(reactApplicationContext);
           }
       },WBSingleSelector.class.getName()));
return moduleSpecList;

//解析字节码指令:校验状态共计四步
methodVisitor.visitLdcInsn(Type.getType("Lcom/wuba/wubaaction/rn/selector/WBMultiUnlinkSelector;"));
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;", false);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "com/facebook/react/bridge/ModuleSpec", "<init>", "(Ljavax/inject/Provider;Ljava/lang/String;)V", false);
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true);

//viewModule注入
@Override
  protected List<WubaViewManager> createWubaViewManagers(ReactApplicationContextWrapper reactApplicationContext) {
          List<WubaViewManager> list = new ArrayList<WubaViewManager>();
          list.add(new WBPublishLoadingView());
          list.add(new WBErrorView());
          return list;
      }
      
  //解析字节码指令:校验状态共计四步
  methodVisitor.visitTypeInsn(NEW, "com/wuba/wubaaction/rn/selector/view/WBPublishLoadingView");
  methodVisitor.visitInsn(DUP);
  methodVisitor.visitMethodInsn(INVOKESPECIAL, "com/wuba/wubaaction/rn/selector/view/WBPublishLoadingView", "<init>", "()V", false);
  methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true);
  1. 当引用一些业务库中对异常只简单捕获并未输出堆栈信息导致问题查找困难时,也可以尝试使用ASM对catch()函数校验后增加自定义逻辑:输出堆栈信息或调用自定义方法等; catch.png

总结

参考资料

  1. 《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》周志明
  2. ASM4使用中英文手册 中文版 英文版
  3. jvm字节码指令集
  4. Oracle: The Java Virtual Machine Specification, Java SE 8 Edition
上一篇下一篇

猜你喜欢

热点阅读