ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java ASM系列:(007)ClassWriter介绍

2021-06-21 14:06:31  阅读:204  来源: 互联网

标签:COMPUTE int max u2 007 ASM Java final 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`类。

标签:COMPUTE,int,max,u2,007,ASM,Java,final,ClassWriter
来源: https://blog.51cto.com/lsieun/2930465

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有