在拜读邓凡平老师的《深入理解Android Java虚拟机ART》后,试着结合openjdk 11的代码对class文件的格式进行分析。因为详细的介绍在《深入理解》中已经有说明,所以结合代码分析的时候可能个别部分不会进行很详细的说明
准备工作,此处我们进行分析的代码可以从github上下载:https://github.com/openjdk/jdk,对应tag为jdk-11+28
。准备工作完毕,下面直接开始进行分析
1. 总体流程
首先将class文件的格式贴出来,如下图所示,看class文件的总体格式相对比较简单,只要按照这个ClassFile格式将内容写入即可
下面我们查看write_class_file_format方法的代码,可以看到代码原有的注释中已经有比较详细的说明了,其中部分方法我们会在后面继续进行说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
// root/jdk/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp // ------------------------------------------------------------------------------------- void JvmtiClassFileReconstituter::write_class_file_format() { ReallocMark(); // JVMSpec| ClassFile { // JVMSpec| u4 magic; write_u4(0xCAFEBABE); // JVMSpec| u2 minor_version; // JVMSpec| u2 major_version; write_u2(ik()->minor_version()); u2 major = ik()->major_version(); write_u2(major); // JVMSpec| u2 constant_pool_count; // JVMSpec| cp_info constant_pool[constant_pool_count-1]; write_u2(cpool()->length()); // ## 2. 写入constant_pool的内容,下面我们将copy_cpool_bytes展开分析 copy_cpool_bytes(writeable_address(cpool_size())); // JVMSpec| u2 access_flags; // JVM_RECOGNIZED_CLASS_MODIFIERS是class类访问权限的集合,access_flag只能是其中进行挑选 write_u2(ik()->access_flags().get_flags() & JVM_RECOGNIZED_CLASS_MODIFIERS); // JVMSpec| u2 this_class; // JVMSpec| u2 super_class; // this_class和super_class都是指向常量池的索引 write_u2(class_symbol_to_cpool_index(ik()->name())); Klass* super_class = ik()->super(); write_u2(super_class == NULL? 0 : // zero for java.lang.Object class_symbol_to_cpool_index(super_class->name())); // JVMSpec| u2 interfaces_count; // JVMSpec| u2 interfaces[interfaces_count]; // interfaces[] 同样保存着指向常量池的索引 Array<Klass*>* interfaces = ik()->local_interfaces(); int num_interfaces = interfaces->length(); write_u2(num_interfaces); for (int index = 0; index < num_interfaces; index++) { HandleMark hm(thread()); InstanceKlass* iik = InstanceKlass::cast(interfaces->at(index)); write_u2(class_symbol_to_cpool_index(iik->name())); } // JVMSpec| u2 fields_count; // JVMSpec| field_info fields[fields_count]; // ## 3. 写入类包含的成员变量的数量和信息 write_field_infos(); // JVMSpec| u2 methods_count; // JVMSpec| method_info methods[methods_count]; // ## 4. 写入类的成员函数的数量和信息 write_method_infos(); // JVMSpec| u2 attributes_count; // JVMSpec| attribute_info attributes[attributes_count]; // JVMSpec| } /* end ClassFile 8? // ## 5. 写入类包含的属性信息 write_class_attributes(); } |
写入class文件的总体流程如上,对比着java虚拟机规范第四章“class文件格式”看还是比较简单的。上面给的代码只是流程,细节的地方我们接着看,首先我们看写入cp_info的流程。
2. 写入常量池流程
在开始copy_cpool_bytes分析前需要首先说明的是writeable_address这个方法,这个方法主要是进行内存判断的,因为在写入class文件的前是会先分配一部分内存,这里判断之前分配的内存是否足够使用,如果不足以存在常量池的内容的话就需要对之前分配的内存进行扩展,扩展为之前的两倍以上,并且会进行边界对齐。OK,我们进入主题,分析copy_cpool_bytes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
// root/jdk/src/hotspot/share/oops/constantPool.cpp // ------------------------------------------------------------------------- int ConstantPool::copy_cpool_bytes(int cpool_size, SymbolHashMap* tbl, unsigned char *bytes) { u2 idx1, idx2; jint size = 0; jint cnt = length(); unsigned char *start_bytes = bytes; // 进行轮循常量池,分别进行写入 for (jint idx = 1; idx < cnt; idx++) { u1 tag = tag_at(idx).value(); jint ent_size = cpool_entry_size(idx); assert(size + ent_size <= cpool_size, "Size mismatch"); // 不管是哪种常量池类型,首先写入的都是tag // 这个tag用来指示接下来写入的是什么常量池类型 *bytes = tag; DBG(printf("#%03hd tag=%03hd, ", (short)idx, (short)tag)); // 按照不同常量池的结构定义分别进行写入 // 各种常量池的结构定义可以查看如下链接 // https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.4 switch(tag) { case JVM_CONSTANT_Invalid: { DBG(printf("JVM_CONSTANT_Invalid")); break; } case JVM_CONSTANT_Unicode: { assert(false, "Wrong constant pool tag: JVM_CONSTANT_Unicode"); DBG(printf("JVM_CONSTANT_Unicode")); break; } case JVM_CONSTANT_Utf8: { Symbol* sym = symbol_at(idx); char* str = sym->as_utf8(); // Warning! It's crashing on x86 with len = sym->utf8_length() int len = (int) strlen(str); Bytes::put_Java_u2((address) (bytes+1), (u2) len); for (int i = 0; i < len; i++) { bytes[3+i] = (u1) str[i]; } DBG(printf("JVM_CONSTANT_Utf8: %s ", str)); break; } ……………… case JVM_CONSTANT_Fieldref: case JVM_CONSTANT_Methodref: case JVM_CONSTANT_InterfaceMethodref: { idx1 = uncached_klass_ref_index_at(idx); idx2 = uncached_name_and_type_ref_index_at(idx); Bytes::put_Java_u2((address) (bytes+1), idx1); Bytes::put_Java_u2((address) (bytes+3), idx2); DBG(printf("JVM_CONSTANT_Methodref: %hd %hd", idx1, idx2)); break; } ……………… } DBG(printf("\n")); bytes += ent_size; size += ent_size; } assert(size == cpool_size, "Size mismatch"); // Keep temorarily for debugging until it's stable. DBG(print_cpool_bytes(cnt, start_bytes)); return (int)(bytes - start_bytes); } /* end copy_cpool_bytes */ |
这个方法的后面部分都是按照tag结构的不同分别写入常量池,因为代码太长,所以省略了部分代码。这里我们可以看个简单例子,首先我们看JVM_CONSTANT_Fieldref、JVM_CONSTANT_Methodref、JVM_CONSTANT_InterfaceMethodref的类型定义如下,首先是写入tag,这个代码部分在switch前就已经写入了,其次是两个字节的class_index以及两个字节的name_and_type_index,上述代码中则是通过两次调用Bytes::put_Java_u2
将index写入到 bytes+1
以及 bytes+3
处
写入常量池的过程就到这里,后面我们接着看成员变量的写入
3. 写入类包含的成员变量的数量和信息
写入成员变量会调用 write_field_infos
方法进行, filed_info在java虚拟机规范中的定义如下所示,因此我们照着虚拟机中的规范进行查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
void JvmtiClassFileReconstituter::write_field_infos() { HandleMark hm(thread()); Array<AnnotationArray*>* fields_anno = ik()->fields_annotations(); Array<AnnotationArray*>* fields_type_anno = ik()->fields_type_annotations(); // Compute the real number of Java fields int java_fields = ik()->java_fields_count(); // 这个将fields的数量写入到class文件中,这个在前面将ClassFile的时候有提及 // ClassFile组成中是先写入fields[]数组的数量随后才是fields[]数组 write_u2(java_fields); // 轮循开始写入fields_info数组 for (JavaFieldStream fs(ik()); !fs.done(); fs.next()) { AccessFlags access_flags = fs.access_flags(); int name_index = fs.name_index(); int signature_index = fs.signature_index(); int initial_value_index = fs.initval_index(); guarantee(name_index != 0 && signature_index != 0, "bad constant pool index for field"); // int offset = ik()->field_offset( index ); int generic_signature_index = fs.generic_signature_index(); AnnotationArray* anno = fields_anno == NULL ? NULL : fields_anno->at(fs.index()); AnnotationArray* type_anno = fields_type_anno == NULL ? NULL : fields_type_anno->at(fs.index()); // 写入access_flags,name_index和descriptor_index,各两个字节 write_u2(access_flags.as_int() & JVM_RECOGNIZED_FIELD_MODIFIERS); write_u2(name_index); write_u2(signature_index); int attr_count = 0; // 计算attributes_count数量 if (initial_value_index != 0) { ++attr_count; } if (access_flags.is_synthetic()) { // ++attr_count; } if (generic_signature_index != 0) { ++attr_count; } if (anno != NULL) { ++attr_count; // has RuntimeVisibleAnnotations attribute } if (type_anno != NULL) { ++attr_count; // has RuntimeVisibleTypeAnnotations attribute } // 写入attributes_count write_u2(attr_count); if (initial_value_index != 0) { write_attribute_name_index("ConstantValue"); write_u4(2); //length always 2 write_u2(initial_value_index); } if (access_flags.is_synthetic()) { // write_synthetic_attribute(); } // 写入attribute数组信息 if (generic_signature_index != 0) { write_signature_attribute(generic_signature_index); } if (anno != NULL) { write_annotations_attribute("RuntimeVisibleAnnotations", anno); } if (type_anno != NULL) { write_annotations_attribute("RuntimeVisibleTypeAnnotations", type_anno); } } } |
这里最后需要处理类成员变量相关的attribute信息,这里我们用write_signature_attribute
来进行展示说明,java虚拟机规范中 Signature_attribute
定义如下所示:
1 2 3 4 5 6 7 8 |
// root/jdk/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp // -------------------------------------------------------------------------------------------------------------------- void JvmtiClassFileReconstituter::write_signature_attribute(u2 generic_signature_index) { write_attribute_name_index("Signature"); write_u4(2); // always length 2 write_u2(generic_signature_index); } |
对照这java虚拟机规范中的定义,再查看代码的实现方式就比较容易理解了。其余的attribute也可以按照这种方式进行分析,其余attribute这里就不多做赘述。
4. 写入类的成员函数的数量和信息
写入成员函数是在 write_method_infos
方法中进行的,我们首先来分析这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
void JvmtiClassFileReconstituter::write_method_infos() { HandleMark hm(thread()); Array<Method*>* methods = ik()->methods(); int num_methods = methods->length(); int num_overpass = 0; // 这里的意思是需要将默认生成的overpass接口方法排除在外 for (int index = 0; index < num_methods; index++) { Method* method = methods->at(index); if (method->is_overpass()) { num_overpass++; } } // 和写入类成员变量类似,在ClassFile中有定义 // 需要先写入类成员方法的数量,然后在写入methods[] 数组的内容 write_u2(num_methods - num_overpass); if (JvmtiExport::can_maintain_original_method_order()) { int index; int original_index; intArray method_order(num_methods, num_methods, 0); for (index = 0; index < num_methods; index++) { original_index = ik()->method_ordering()->at(index); assert(original_index >= 0 && original_index < num_methods, "invalid original method index"); method_order.at_put(original_index, index); } // 此处根据是否需要对方法进行排序,不影响后续分析 for (original_index = 0; original_index < num_methods; original_index++) { index = method_order.at(original_index); methodHandle method(thread(), methods->at(index)); write_method_info(method); } } else { for (int index = 0; index < num_methods; index++) { methodHandle method(thread(), methods->at(index)); write_method_info(method); } } } |
这里最后会轮循进行method的写入,调用的对应方法是 write_method_info
,这个方法就是实际的按照java虚拟机规范中method_info
的结构进行写入的地方了,我们首先将 method_info
的结构贴出来,然后就可以对着该结构进行代码分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
void JvmtiClassFileReconstituter::write_method_info(const methodHandle& method) { AccessFlags access_flags = method->access_flags(); ConstMethod* const_method = method->constMethod(); u2 generic_signature_index = const_method->generic_signature_index(); AnnotationArray* anno = method->annotations(); AnnotationArray* param_anno = method->parameter_annotations(); AnnotationArray* default_anno = method->annotation_default(); AnnotationArray* type_anno = method->type_annotations(); // 如果是overpass方法就跳过,那么什么是overpass方法呢?下面找到一个定义 // overpass methods in an interface will be assigned an itable index later // by an inheriting class if (method->is_overpass()) { return; } // 写入access_flags,name_index以及descriptor_index,各占两个字节 write_u2(access_flags.get_flags() & JVM_RECOGNIZED_METHOD_MODIFIERS); write_u2(const_method->name_index()); write_u2(const_method->signature_index()); // 统计attribute的数量 int attr_count = 0; if (const_method->code_size() != 0) { ++attr_count; // has Code attribute } if (const_method->has_checked_exceptions()) { ++attr_count; // has Exceptions attribute } ……………… // 写入attribute的数量 write_u2(attr_count); if (const_method->code_size() > 0) { write_code_attribute(method); } if (const_method->has_checked_exceptions()) { write_exceptions_attribute(const_method); } ……………… } |
对于 method_info
中,这里我们比较关注的属性就是code,里面有个字段就是存储的就是该方法编译后的字节码。除了code属性外,下面还有很多其他的属性需要记录和写入,例如exception、anotation和signature,这些属性都比较简单,各位同学可以按照需要跟踪学习。code属性在 write_code_attribute
中进行展现。老规矩了,我们先将 Code_attribute
结构贴出来,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
void JvmtiClassFileReconstituter::write_code_attribute(const methodHandle& method) { ConstMethod* const_method = method->constMethod(); u2 line_num_cnt = 0; int stackmap_len = 0; int local_variable_table_length = 0; int local_variable_type_table_length = 0; // compute number and length of attributes int attr_count = 0; int attr_size = 0; // Code_attribute中还包含其他的attribute,这里统计的是LineNumberTable属性 // LineNumberTable_attribute的具体定义我们在代码片段后面贴出 if (const_method->has_linenumber_table()) { line_num_cnt = line_number_table_entries(method); if (line_num_cnt != 0) { ++attr_count; // 统计LineNumberTable_attribute的具体大小,在后续统计Code_attribute的时候需要使用 attr_size += 2 + 4 + 2 + line_num_cnt * (2 + 2); } } // Code_attribute中还包含其他的attribute,这里统计的是StackMapTable属性 if (method->has_stackmap_table()) { stackmap_len = method->stackmap_data()->length(); if (stackmap_len != 0) { ++attr_count; attr_size += 2 + 4 + stackmap_len; } } // Code_attribute中还包含其他的attribute,这里统计的是LocalVariableTable属性 if (method->has_localvariable_table()) { local_variable_table_length = method->localvariable_table_length(); if (local_variable_table_length != 0) { ++attr_count; attr_size += 2 + 4 + 2 + local_variable_table_length * (2 + 2 + 2 + 2 + 2); // Local variables with generic signatures must have LVTT entries LocalVariableTableElement *elem = method->localvariable_table_start(); for (int idx = 0; idx < local_variable_table_length; idx++) { if (elem[idx].signature_cp_index != 0) { local_variable_type_table_length++; } } // Code_attribute中还包含其他的attribute,这里统计的是LocalVariableTypeTable属性 if (local_variable_type_table_length != 0) { ++attr_count; attr_size += 2 + 4 + 2 + local_variable_type_table_length * (2 + 2 + 2 + 2 + 2); } } } ExceptionTable exception_table(method()); int exception_table_length = exception_table.length(); int code_size = const_method->code_size(); // 计算Code_attribute属性的总长度,对应Code_attribute在的attribute_length字段 int size = 2+2+4 + // max_stack, max_locals, code_length code_size + // code 2 + // exception_table_length (2+2+2+2) * exception_table_length + // exception_table 2 + // attributes_count attr_size; // attributes // 开始按照Code_attribute的各字节定义写入class文件 write_attribute_name_index("Code"); write_u4(size); write_u2(method->verifier_max_stack()); write_u2(method->max_locals()); write_u4(code_size); copy_bytecodes(method, (unsigned char*)writeable_address(code_size)); // 写入exception_table的内容 write_u2(exception_table_length); for (int index = 0; index < exception_table_length; index++) { write_u2(exception_table.start_pc(index)); write_u2(exception_table.end_pc(index)); write_u2(exception_table.handler_pc(index)); write_u2(exception_table.catch_type_index(index)); } write_u2(attr_count); if (line_num_cnt != 0) { write_line_number_table_attribute(method, line_num_cnt); } if (stackmap_len != 0) { write_stackmap_table_attribute(method, stackmap_len); } if (local_variable_table_length != 0) { write_local_variable_table_attribute(method, local_variable_table_length); } if (local_variable_type_table_length != 0) { write_local_variable_type_table_attribute(method, local_variable_type_table_length); } } |
首先我们将上面Code_attribute中会用到的一些属性的定义贴出来:
这里只是简单展示其对应的流程,具体的代码细节,各位同学可自行查看分析。最后我们接下来分析
5. 写入类包含的属性信息
剩下最后一部分了,write_class_attributes
用来写入类包含的对应的属性,前两部分也有提到一些属性,之前提到的属性都只是对应字段或者方法中涉及到的属性。这里的属性则是类相关的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
void JvmtiClassFileReconstituter::write_class_attributes() { u2 inner_classes_length = inner_classes_attribute_length(); Symbol* generic_signature = ik()->generic_signature(); AnnotationArray* anno = ik()->class_annotations(); AnnotationArray* type_anno = ik()->class_type_annotations(); int attr_count = 0; if (generic_signature != NULL) { ++attr_count; } if (ik()->source_file_name() != NULL) { ++attr_count; } if (ik()->source_debug_extension() != NULL) { ++attr_count; } if (inner_classes_length > 0) { ++attr_count; } if (anno != NULL) { ++attr_count; // has RuntimeVisibleAnnotations attribute } if (type_anno != NULL) { ++attr_count; // has RuntimeVisibleTypeAnnotations attribute } if (cpool()->operands() != NULL) { ++attr_count; } if (ik()->nest_host_index() != 0) { ++attr_count; } if (ik()->nest_members() != Universe::the_empty_short_array()) { ++attr_count; } write_u2(attr_count); if (generic_signature != NULL) { write_signature_attribute(symbol_to_cpool_index(generic_signature)); } if (ik()->source_file_name() != NULL) { write_source_file_attribute(); } if (ik()->source_debug_extension() != NULL) { write_source_debug_extension_attribute(); } if (inner_classes_length > 0) { write_inner_classes_attribute(inner_classes_length); } if (anno != NULL) { write_annotations_attribute("RuntimeVisibleAnnotations", anno); } if (type_anno != NULL) { write_annotations_attribute("RuntimeVisibleTypeAnnotations", type_anno); } if (cpool()->operands() != NULL) { write_bootstrapmethod_attribute(); } if (ik()->nest_host_index() != 0) { write_nest_host_attribute(); } if (ik()->nest_members() != Universe::the_empty_short_array()) { write_nest_members_attribute(); } } |
上面方法的代码阅读起来不是特别的困难,这里就偷懒不做额外的解释了,有兴趣的同学可以自行查看。另外,这里的代码分析没有设计到Java指令码的介绍,因为Java指令码会在编译的时候生成,这里的流程只是简单的将编译好的Java指令码写入文件。下一篇,我们希望写一个class解析软件,对class文件进行解析。