Java ASM系列:(007)ClassWriter介绍

本文属于[Java ASM系列一:Core API](https://blog.51cto.com/lsieun/2924583)当中的一篇。 ## 1. ClassWriter类 ### 1.1 class info 第一个部分,就是`ClassWriter`的父类是`ClassVisitor`,因此`ClassWriter`类继承了`visit()`、`visitField()`、`visitMethod()`和`visitEnd()`等方法。 ```java public class ClassWriter extends ClassVisitor { } ``` ### 1.2 fields 第二个部分,就是`ClassWriter`定义的字段有哪些。 ```java 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结构密切相关: ```text 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]; } ``` ### 1.3 constructors 第三个部分,就是`ClassWriter`定义的构造方法。`ClassWriter`定义的构造方法有两个,这里只关注其中一个,也就是只接收一个`int`类型参数的构造方法。在使用`new`关键字创建`ClassWriter`对象时,推荐使用`COMPUTE_FRAMES`参数。 ```java 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); } } ``` ### 1.4 methods 第四个部分,就是`ClassWriter`提供了哪些方法。 #### 1.4.1 visitXxx()方法 在`ClassWriter`这个类当中,我们仍然是只关注其中的`visit()`方法、`visitField()`方法、`visitMethod()`方法和`visitEnd()`方法。这些`visitXxx()`方法的调用,就是在为构建ClassFile提供“原材料”的过程。 ```java 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(); // ...... } ``` #### 1.4.2 toByteArray()方法 在`ClassWriter`类当中,提供了一个`toByteArray()`方法。这个方法的作用是将“所有的努力”(对`visitXxx()`的调用)转换成`byte[]`,而这些`byte[]`的内容就遵循ClassFile结构。 在`toByteArray()`方法的代码当中,通过三个步骤来得到`byte[]`: - 第一步,计算`size`大小。这个`size`就是表示`byte[]`的最终的长度是多少。 - 第二步,将数据填充到`byte[]`当中。 - 第三步,将`byte[]`数据返回。 ```java 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; } } } ``` ## 2. 创建ClassWriter对象 ### 2.1 推荐使用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`对象,如下所示: ```text ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); ``` ### 2.2 为什么推荐使用COMPUTE_FRAMES 在创建`ClassWriter`对象的时候,使用`ClassWriter.COMPUTE_FRAMES`,ASM会自动计算max stacks和max locals,也会自动计算stack map frames。 首先,来看一下max stacks和max locals。在ClassFile结构中,每一个方法都用`method_info`来表示,而方法里定义的代码则使用`Code`属性来表示,其结构如下: ```text 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进行类型检查。 ```text 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`的内容。 ![maxstacksmaxlocalsstackmapframes.png](http://www.icode9.com/i/li/?n=2&i=images/20210621/1624254673117990.png?,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) 我们推荐使用`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。 ## 3. 如何使用ClassWriter类 使用`ClassWriter`生成一个Class文件,可以大致分成三个步骤: - 第一步,创建`ClassWriter`对象。 - 第二步,调用`ClassWriter`对象的`visitXxx()`方法。 - 第三步,调用`ClassWriter`对象的`toByteArray()`方法。 示例代码如下: ```java 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; } } ``` ## 4. 总结 本文主要对`ClassWriter`类进行介绍,内容总结如下: - 第一点,`ClassWriter`类有哪些部分的信息,以便于对`ClassWriter`类有所了解。 - 第二点,在创建`ClassWriter`对象时,推荐使用`ClassWriter.COMPUTE_FRAMES`选项。 - 第三点,如何使用`ClassWriter`类。
上一篇:python__007内置函数


下一篇:USB数据采集系统论文007