美文网首页
07 - ASM之ClassWriter

07 - ASM之ClassWriter

作者: 舍是境界 | 来源:发表于2022-01-16 07:47 被阅读0次

ClassWriter类

class info

ClassWriter的父类是ClassVisitor,因此ClassWriter类继承了visit()、visitField()、visitMethod()和visitEnd()等方法。

public class ClassWriter extends ClassVisitor {
}
fields

ClassWriter定义的字段有哪些。

public class ClassWriter extends ClassVisitor {
    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;

    //......
}

这些字段与ClassFile结构密切相关:

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
constructors

ClassWriter定义的构造方法有两个,这里只关注其中一个,也就是只接收一个int类型参数的构造方法。在使用new关键字创建ClassWriter对象时,推荐使用COMPUTE_FRAMES参数。

public class ClassWriter extends ClassVisitor {
    /* A flag to automatically compute the maximum stack size and the maximum number of local variables of methods. */
    public static final int COMPUTE_MAXS = 1;
    /* A flag to automatically compute the stack map frames of methods from scratch. */
    public static final int COMPUTE_FRAMES = 2;

    // flags option can be used to modify the default behavior of this class.
    // Must be zero or more of COMPUTE_MAXS and COMPUTE_FRAMES.
    public ClassWriter(final int flags) {
        this(null, flags);
    }
}
methods
  • visitXxx()方法

在ClassWriter这个类当中,我们仍然是只关注其中的visit()方法、visitField()方法、visitMethod()方法和visitEnd()方法。这些visitXxx()方法的调用,就是在为构建ClassFile提供“原材料”的过程。

public class ClassWriter extends ClassVisitor {
    public void visit(
        final int version,
        final int access,
        final String name,
        final String signature,
        final String superName,
        final String[] interfaces);
    public FieldVisitor visitField( // 访问字段
        final int access,
        final String name,
        final String descriptor,
        final String signature,
        final Object value);
    public MethodVisitor visitMethod( // 访问方法
        final int access,
        final String name,
        final String descriptor,
        final String signature,
        final String[] exceptions);
    public void visitEnd();
    // ......
}
  • toByteArray()方法

在ClassWriter类当中,提供了一个toByteArray()方法。这个方法的作用是将“所有的努力”(对visitXxx()的调用)转换成byte[],而这些byte[]的内容就遵循ClassFile结构。

在toByteArray()方法的代码当中,通过三个步骤来得到byte[]:

  • 第一步,计算size大小。这个size就是表示byte[]的最终的长度是多少。
  • 第二步,将数据填充到byte[]当中。
  • 第三步,将byte[]数据返回。
public class ClassWriter extends ClassVisitor {
    public byte[] toByteArray() {

        // First step: compute the size in bytes of the ClassFile structure.
        // The magic field uses 4 bytes, 10 mandatory fields (minor_version, major_version,
        // constant_pool_count, access_flags, this_class, super_class, interfaces_count, fields_count,
        // methods_count and attributes_count) use 2 bytes each, and each interface uses 2 bytes too.
        int size = 24 + 2 * interfaceCount;
        int fieldsCount = 0;
        FieldWriter fieldWriter = firstField;
        while (fieldWriter != null) {
            ++fieldsCount;
            size += fieldWriter.computeFieldInfoSize();
            fieldWriter = (FieldWriter) fieldWriter.fv;
        }
        int methodsCount = 0;
        MethodWriter methodWriter = firstMethod;
        while (methodWriter != null) {
            ++methodsCount;
            size += methodWriter.computeMethodInfoSize();
            methodWriter = (MethodWriter) methodWriter.mv;
        }

        // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
        int attributesCount = 0;

        // ......

        if (firstAttribute != null) {
            attributesCount += firstAttribute.getAttributeCount();
            size += firstAttribute.computeAttributesSize(symbolTable);
        }
        // IMPORTANT: this must be the last part of the ClassFile size computation, because the previous
        // statements can add attribute names to the constant pool, thereby changing its size!
        size += symbolTable.getConstantPoolLength();


        // Second step: allocate a ByteVector of the correct size (in order to avoid any array copy in
        // dynamic resizes) and fill it with the ClassFile content.
        ByteVector result = new ByteVector(size);
        result.putInt(0xCAFEBABE).putInt(version);
        symbolTable.putConstantPool(result);
        int mask = (version & 0xFFFF) < Opcodes.V1_5 ? Opcodes.ACC_SYNTHETIC : 0;
        result.putShort(accessFlags & ~mask).putShort(thisClass).putShort(superClass);
        result.putShort(interfaceCount);
        for (int i = 0; i < interfaceCount; ++i) {
            result.putShort(interfaces[i]);
        }
        result.putShort(fieldsCount);
        fieldWriter = firstField;
        while (fieldWriter != null) {
            fieldWriter.putFieldInfo(result);
            fieldWriter = (FieldWriter) fieldWriter.fv;
        }
        result.putShort(methodsCount);
        boolean hasFrames = false;
        boolean hasAsmInstructions = false;
        methodWriter = firstMethod;
        while (methodWriter != null) {
            hasFrames |= methodWriter.hasFrames();
            hasAsmInstructions |= methodWriter.hasAsmInstructions();
            methodWriter.putMethodInfo(result);
            methodWriter = (MethodWriter) methodWriter.mv;
        }
        // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
        result.putShort(attributesCount);

        // ......

        if (firstAttribute != null) {
            firstAttribute.putAttributes(symbolTable, result);
        }

        // Third step: replace the ASM specific instructions, if any.
        if (hasAsmInstructions) {
            return replaceAsmInstructions(result.data, hasFrames);
        } else {
            return result.data;
        }
    }
}

创建ClassWriter对象

推荐使用COMPUTE_FRAMES

在创建ClassWriter对象的时候,要指定一个flags参数,它可以选择的值有三个:

  • 第一个,可以选取的值是0。ASM不会自动计算max stacks和max locals,也不会自动计算stack map frames。
  • 第二个,可以选取的值是ClassWriter.COMPUTE_MAXS。ASM会自动计算max stacks和max locals,但不会自动计算stack map frames。
  • 第三个,可以选取的值是ClassWriter.COMPUTE_FRAMES(推荐使用)。ASM会自动计算max stacks和max locals,也会自动计算stack map frames。
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
COMPUTE_FRAMES

在创建ClassWriter对象的时候,使用ClassWriter.COMPUTE_FRAMES,ASM会自动计算max stacks和max locals,也会自动计算stack map frames。

首先,来看一下max stacks和max locals。在ClassFile结构中,每一个方法都用method_info来表示,而方法里定义的代码则使用Code属性来表示,其结构如下:

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;     // 这里是max stacks
    u2 max_locals;    // 这里是max locals
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

如果我们在创建ClassWriter(flags)对象的时候,将flags参数设置为ClassWriter.COMPUTE_MAXS或ClassWriter.COMPUTE_FRAMES,那么ASM会自动帮助我们计算Code结构中max_stack和max_locals的值。

接着,来看一下stack map frames。在Code结构里,可能有多个attributes,其中一个可能就是StackMapTable_attribute。StackMapTable_attribute结构,就是stack map frame具体存储格式,它的主要作用是对ByteCode进行类型检查。

StackMapTable_attribute {
    u2              attribute_name_index;
    u4              attribute_length;
    u2              number_of_entries;
    stack_map_frame entries[number_of_entries];
}

如果我们在创建ClassWriter(flags)对象的时候,将flags参数设置为ClassWriter.COMPUTE_FRAMES,那么ASM会自动帮助我们计算StackMapTable_attribute的内容。

COMPUTE_FRAMES使用示意图

我们推荐使用ClassWriter.COMPUTE_FRAMES。因为ClassWriter.COMPUTE_FRAMES这个选项,能够让ASM帮助我们自动计算max stacks、max locals和stack map frame的具体内容。

  • 如果将flags参数的取值为0,那么我们就必须要提供正确的max stacks、max locals和stack map frame的值;
  • 如果将flags参数的取值为ClassWriter.COMPUTE_MAXS,那么ASM会自动帮助我们计算max stacks和max locals,而我们则需要提供正确的stack map frame的值。

那么,ASM为什么会提供0和ClassWriter.COMPUTE_MAXS这两个选项呢?因为ASM在计算这些值的时候,要考虑各种各样不同的情况,所以它的算法相对来说就比较复杂,因而执行速度也会相对较慢。同时,ASM也鼓励开发者去研究更好的算法;如果开发者有更好的算法,就可以不去使用ClassWriter.COMPUTE_FRAMES,这样就能让程序的执行效率更高效。

但是,不得不说,要想计算max stacks、max locals和stack map frames,也不是一件容易的事情。出于方便的目的,就推荐大家使用ClassWriter.COMPUTE_FRAMES。在大多数情况下,ClassWriter.COMPUTE_FRAMES都能帮我们计算出正确的值。在少数情况下,ClassWriter.COMPUTE_FRAMES也可能会出错,比如说,有些代码经过混淆(obfuscate)处理,它里面的stack map frame会变更非常复杂,使用ClassWriter.COMPUTE_FRAMES就会出现错误的情况。针对这种少数的情况,我们可以在不改变原有stack map frame的情况下,使用ClassWriter.COMPUTE_MAXS,让ASM只帮助我们计算max stacks和max locals。

使用ClassWriter类

使用ClassWriter生成一个Class文件,可以大致分成三个步骤:

  • 第一步,创建ClassWriter对象。
  • 第二步,调用ClassWriter对象的visitXxx()方法。
  • 第三步,调用ClassWriter对象的toByteArray()方法。
import org.objectweb.asm.ClassWriter;

import static org.objectweb.asm.Opcodes.*;

public class HelloWorldGenerateCore {
    public static byte[] dump () throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用visitXxx()方法
        cw.visit();
        cw.visitField();
        cw.visitMethod();
        cw.visitEnd();       // 注意,最后要调用visitEnd()方法

        // (3) 调用toByteArray()方法
        byte[] bytes = cw.toByteArray();
        return bytes;
    }
}

小结

本文对ClassWriter类进行了介绍,说明了该类的作用以及使用方式,希望对你能有所帮助

相关文章

网友评论

      本文标题:07 - ASM之ClassWriter

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