bboyjing's blog

自己动手写JVM九【解析class文件(七)】

本章节开始学习属性表相关信息,上一节找到的main()方法的字节码就出现在属性表中,属性表可谓是个大杂烩,里面存储了各式各样的信息。下面我们就来详细讨论讨论。

解析属性表

AttributeInfo接口

和常量池类似,各种属性表达的信息也各不相同,因此无法用同一的结构来定义。不同之处在于,常量是由Java虚拟机规范严格定义的,共有14种。但是属性是可以扩展的,不同的虚拟机实现可以定义自己的属性类型,所以Java虚拟机规范没有使用tag,而是使用属性名来区别不同的属性。属性的结构定义如下:

1
2
3
4
5
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}

attribute_name_index为属性名常量池索引,指向常量池中的CONSTANT_Utf8_info常量。attribute_length表示属性数据的字节数,info[attribute_length]表示属性数据。相关代码位于attribute_info.go文件中:

1
2
3
type AttributeInfo interface {
readInfo(reader *ClassReader)
}

像常量池一样,还是要先去实现各个属性结构体,再回过来写读取属性表入口。
Java虚拟机规范预订定义了23中属性,按照用途可以分为三组。第一组属性是实现Java虚拟机所必须的,共5种;第二组属性时Java类库所必须的,共有12种;第三组属性主要提供给工具使用,共有6种。第三组属性是可选的,也就是说可以不出现在class文件中。我们重点挑8个学习下,下面列举这八种属性属性出现的Java版本、分组以及他们在class文件中的位置:(完整的23种都列出来篇幅太长了,在本页源码注释部分可以查看到)

属性名 Java SE 分组 位置
Deprecated 1.1 3 ClassFile, field_info, method_info
Synthetic 1.1 2 ClassFile, field_info, method_info
SourceFile 1.0.2 3 ClassFile
ConstantValue 1.0.2 1 field_info
Code 1.0.2 1 method_info
Exceptions 1.0.2 1 method_info
LineNumberTable 1.0.2 3 Code
LocalVariableTable 1.0.2 3 Code

Deprecated和Synthetic

Deprecated和Synthetic是最简单的两种属性,仅起标记作用,不包含任何数据。这两种属性都是JDK1.1引入的,可以出现在ClassFile, field_info, method_info,这里说的ClassFile,其实意思就是出现在ClassFile的attribute_info中。它们的结构如下:

1
2
3
4
5
6
7
8
9
Deprecated_attribute {
u2 attribute_name_index;
u4 attribute_length;
}
Synthetic_attribute {
u2 attribute_name_index;
u4 attribute_length;
}

由于不包含任何数据,所以attribute_length的值必须为0。Deprecated属性用于指出类、接口、字段或方法已经不建议使用。Synthetic属性用来标记源文件中不存在、有编译器生成的类成员,引入Synthetic属性主要是为了支持嵌套类和嵌套接口。相关代码位于attr_marker.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 统一的结构体
type MarkerAttribute struct{}
type DeprecatedAttribute struct {
MarkerAttribute
}
type SyntheticAttribute struct {
MarkerAttribute
}
// attribute_length值为0,所以方法中什么都不做
func (self *MarkerAttribute) readInfo(reader *ClassReader) {
}

SourceFile属性

SourceFile是可选定长属性,只会出现在ClassFile结构中,用于指出源文件名。其结构定义如下:

1
2
3
4
5
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}

attribute_length的值必须是2,attribute_name_index和sourcefile_index都是常量池索引。相关代码位于attr_source_file.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type SourceFileAttribute struct {
cp ConstantPool
sourceFileIndex uint16
}
func (self *SourceFileAttribute) readInfo(reader *ClassReader) {
// attribute_length的值为2,所以固定读取2个字节
self.sourceFileIndex = reader.readUint16()
}
// 通过索引读取常量池中utf8字面值
func (self *SourceFileAttribute) FileName() string {
return self.cp.getUtf8(self.sourceFileIndex)
}

下面来看看class文件:
jvmgo_24
前两个字节attribute_name_index为0x002F,转换成十进制为47。查看常量池,第47个是CONSTANT_Utf8_info常量结构体,字面值正是SourceFile。虽然attribute_length占4个字节,但是SourceFile属性中该值固定为2,所以直接看最后2个字节0x0030,转换成十进制为48。查看常量池第48个:
jvmgo_25
图中可以看出是CONSTANT_Utf8_info常量结构体,字面值为ClassFile.java,正是源文件名。

ConstantValue

ConstantValue是定长属性,只会出现在field_info结构中,用于表示常量表达wenjian式的值,其结构如下:

1
2
3
4
5
ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index;
}

attribute_length的值必须为2,attribute_name_index和constantvalue_index都是常量池索引,但constantvalue_index具体指向哪种常量因字段类型而异。下表给出字段类型和常量类型的对应关系:

字段类型 常量类型
long CONSTANT_Long_info
float CONSTANT_Float_info
double CONSTANT_Double_info
int、short、char、byte、boolean CONSTANT_Integer_info
String CONSTANT_String_info

相关代码位于attr_constant_value.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type ConstantValueAttribute struct {
constantValueIndex uint16
}
// 读取ConstantValue_attribute
func (self *ConstantValueAttribute) readInfo(reader *ClassReader) {
// 读取读取2个字节的constantValueIndex
self.constantValueIndex = reader.readUint16()
}
// 读取constantValueIndex
func (self *ConstantValueAttribute) ConstantValueIndex() uint16 {
return self.constantValueIndex
}

下面看下FLAG字段的class文件:
jvmgo_26
可以清楚地看出,FLAG中有一个attribute,attribute_name_index为0x0009,常量池第9个正是ConstantValue。attribute_length值固定为2,那直接看最后两个字节0x000A,指向常量池第10个CONSTANT_Integer_info常量。

下节继续学习属性相关信息。。。