上一篇我们已经根据路径读取到了我们需要的字节码文件,就以java.lang.Object这个类为例,可以看到类似下面这种东西,那么这些数字是什么呢?
要了解这个,我们大概可以猜到这是十进制的,在线将十进制转为十六进制看看https://tool.oschina.net/hexconvert/,注意上图中已经用空格隔开了每个数,我们将最前面的变成十六进制看看效果,202对应CA,254对应FE,186对应BA,190对应BE,合起来就是CAFEBABE,有兴趣的可以查查这代表的时一种咖啡,所有的符合jvm规范的字节码文件都是以这个开头,专业称呼 "魔数";
不知道大家有没有发现,如果我们分析这个的时候要自己一个一个的转换,简直太坑爹了,但是有很多工具可以帮助我们更好的看十六进制的,比如vscode,editplus,winhex,jclasslib(这个看不到十六进制,但是可以看字节码文件的结构),实在不想下载的其他东西话用vim也可以看十六进制;这里强烈推荐一款工具叫做classpy,这个工具可以同时看十六进制和class字节码文件的结构,用起来很舒服;
链接:https://pan.baidu.com/s/1s_fqLxQjG0lVXMEB5z1mlg 提取码:gmyt ,使用这个classpy的时候,但是有一个前提,你计算机必须要有gradle环境!!!首先解压,然后需要进入classpy-master文件夹,命令行运行gradle uberjar,最后就是gradle run ,以后每次的话直接使用gradle run就行了!打开ui界面之后,把class手动丢进去就行了,如下图,左边是class文件的结构,右边的对应的十六进制;
1.简单说说class文件结构
首先说说class字节码文件的结构,看有哪几部分组成,其实在上图左边已经差不多说明了,下图更清楚:其中u2表示两个字节,u4表示四个字节,这之外的比如cp_info表示的是一张表,然后表中每一个字段又对应着一张表(这么说肯定不好理解,见过多维数组没,表就看作数组就好,只不多数组每个位置又对应这一个数组,这就叫多维数组);
至于下面这些代表什么意思,这里 就不多做赘述了,自己去看字节码文件的组成吧,不是我们的重点;
这里的结构有个很有意思的现象,就是在列出该项数据之前,会提前指明该数据有几个字节;比如constant_pool_count表示常量池中有n个表,占用2个字节;而紧接其后的constant_pool[constant_pool_count-1]存的就是各个表实际的数据,由于每个表第一个字节表示该表的类型,然后后面又会指定该表的大小,所以可以确定总共占用多少字节;access_flags表示访问权限,占两个字节,等等
接下来说说常量池中表的类型以及每个表的结构(每一种表都标识了自己占用的字节大小),如下所示,每一种表都有自己特有的结构,还要注意一点,下面这么多表中,某一个表中某一项可能会引用另外一张表的数据的;
常量池之外每个部分表示的什么,我随便找了一篇博客,参考这篇说的比较仔细的:https://www.jianshu.com/p/247e2475fc3a;这就不多说了,这也不是我们的重点;
2.读取class字节码文件
总的目录结构如下所示:
根据上面这个图我们将classfile中的文件分为几个部分理解一下,首先是class_reader.go这个文件里面是结构体,存了class文件的全部数据的字节切片,并且定义了一些方法一下子读取1字节,2字节,4字节和8字节等方法,方便于我们读取数据;
然后class_file.go文件中一个结构体,存了字节码中所有结构,就是魔数,版本号,常量池,访问修饰符等等,然后定义了一些获取这些部分的方法,可想而知这些方法需要使用前面说的class_reader.go文件中结构体读取数据;
再然后比较关键,就是class_file.go文件中定义的那些获取各个部分的方法,下图所示,其中最关键的就是读取常量池和属性表;
说道读取常量池数据,那么因为常量池中有很多不同类型的表,我们定义一个接口,所有的表都必须实现这个接口;至于总共有些什么类型的表,大致分为两种,一种是字符型,一种是引用型的;字符型的分为字符串和数字类的,分别是在上面的cp_utf8.go和cp_numberic.go中,其他的以cp开头的都是引用类型的表;
在读取常量池中的表的时候,我们首先要确定正在读取表的类型,在读取第一个字节的时候,该字节就是说明该表示什么类型,如下所示,然后每一种表都规定了字节的结构,前面已经说明白了;
const ( CONSTANT_Utf8 = 1 CONSTANT_Integer = 3 CONSTANT_Float = 4 CONSTANT_Long = 5 CONSTANT_Double = 6 CONSTANT_Class = 7 CONSTANT_String = 8 CONSTANT_Fieldref = 9 CONSTANT_Methodref = 10 CONSTANT_InterfaceMethodref = 11 CONSTANT_NameAndType = 12 CONSTANT_MethodHandle = 15 CONSTANT_MethodType = 16 CONSTANT_InvokeDynamic = 18 )
然后就是属性表,其实和常量池差不多定义了一个顶层接口,只不过属性表这里不是用这种数字来决定表的类型,而是用属性名(也就是字符串来区分),所以我们可以看到下面这种结构,通过读取属性表前面两个字节找到常量池的Constant_Utf8表的索引,然后取到字符串,再到下面这个switch中确定是什么类型的属性表;
属性表也有很多类型,我们这里只是列举其中的8种,至于每一种是什么意思,看看这个博客:https://www.cnblogs.com/lrh-xl/p/5351182.html,在上面的目录中attr_xxx开头的都是属性表,
3.各个文件
class_reader.go:用于帮助我们读取字节切片中的数据:
package classfile import "encoding/binary" //这个结构体从字节数组中读取数据 type ClassReader struct { data []byte } //读取一个字节,而且data数据也要将第一个字节干掉 func (this *ClassReader) readUint8() uint8 { //u1 val := this.data[0] this.data = this.data[1:] return val } //读取两个字节 func (this *ClassReader) readUint16() uint16 { //u2 val := binary.BigEndian.Uint16(this.data) this.data = this.data[2:] return val } //读取四个字节 func (this *ClassReader) readUint32() uint32 { //u4 val := binary.BigEndian.Uint32(this.data) this.data = this.data[4:] return val } //读取8个字节 func (this *ClassReader) readUint64() uint64 { val := binary.BigEndian.Uint64(this.data) this.data = this.data[8:] return val } //读取最前面的两个字节,表示数量 //根据这个数量继续往后面读取n个uint16的字节 func (this *ClassReader) readUint16s() []uint16 { n := this.readUint16() s := make([]uint16, n) for i := range s { s[i] = this.readUint16() } return s } //获取指定数量的字节 func (this *ClassReader) readBytes(length uint32) []byte { bytes := this.data[:length] this.data = this.data[length:] return bytes }
class_file.go:定义了字节码文件的结构
package classfile import "fmt" //这个结构体就是体现了class文件的内容 type ClassFile struct { magic uint32 //魔数 u4 minorVersion uint16 //次版本号 u2 majorVersion uint16 //主版本号 u2 constantPool ConstantPool //常量池 accessFlags uint16 //修饰符 thisClass uint16 //当前类 superClass uint16 //父类 interfaces []uint16 //接口,木有接口的数组 fields []*MemberInfo //字段 methods []*MemberInfo //方法 attributes []AttributeInfo //属性,例如全类名就是保存在这里 } //这个方法就是将byte数组解析成FileClass结构体 func Parse(classData []byte) (cf *ClassFile, err error) { //defer和recover模式,类似于java中的finally,这里就是做一个异常不火再进行处理 defer func() { if r := recover(); r != nil { var ok bool err, ok = r.(error) if !ok { err = fmt.Errorf("%v", r) } } }() //实例化一个ClassFile实例,用于保存字节码各个部分信息 cf = &ClassFile{} //实例化一个class文件解析器,将存有字节码文件所有信息的数组传递进去 cr := &ClassReader{classData} //read方法开始解析class文件各个部分的数据 cf.read(cr) return } //这个方法就是按照字节码文件中各部分的顺序进行读取 func (this *ClassFile) read(reader *ClassReader) { this.readAndCheckMagic(reader) this.readAndCheckVersion(reader) this.constantPool = readConstantPool(reader) this.accessFlags = reader.readUint16() this.thisClass = reader.readUint16() this.superClass = reader.readUint16() this.interfaces = reader.readUint16s() this.fields = readMembers(reader, this.constantPool) this.methods = readMembers(reader, this.constantPool) this.attributes = readAttributes(reader, this.constantPool) } //获取魔数,魔数是占有4个字节 func (this *ClassFile) readAndCheckMagic(reader *ClassReader) { magic := reader.readUint32() //注意,所有符合jvm规范的魔数都是CAFEBABE,不符合条件的直接panic终止程序 if magic != 0xCAFEBABE { panic("java.lang.ClassFormatError:magic") } } //次版本号和主版本号都是两个字节 //次版本号在jdk1.2之后就没有用过了,都是0 //主版本号的版本从1.2开始是45,每次经过一个大的版本,就会+1,现在是52 func (this *ClassFile) readAndCheckVersion(reader *ClassReader) { this.minorVersion = reader.readUint16() this.majorVersion = reader.readUint16() switch this.majorVersion { case 45: return case 46, 47, 48, 49, 50, 51, 52: if this.minorVersion == 0 { return } } panic("java.lang.UnsupportedClassVersionError") } //获取主版本号 func (this *ClassFile) MinorVersion() uint16 { return this.minorVersion } //获取副版本号 func (this *ClassFile) MajorVersion() uint16 { return this.majorVersion } //获取常量池 func (this *ClassFile) ConstantPool() ConstantPool { return this.constantPool } //获取修饰符 func (this *ClassFile) AccessFlags() uint16 { return this.accessFlags } //从常量池中获取类名 func (this *ClassFile) ClassName() string { return this.constantPool.getClassName(this.thisClass) } //从常量池中获取超类名,注意,这里需要判断是不是Object类 func (this *ClassFile) SuperClassName() string { if this.superClass > 0 { return this.constantPool.getClassName(this.superClass) } return "" //这里当类是Object的时候,那么self.superClass为0 } //获取字段 func (this *ClassFile) Fields() []*MemberInfo { return this.fields } //获取方法 func (this *ClassFile) Methods() []*MemberInfo { return this.methods } //从常量池中找实现的所有接口名称 func (this *ClassFile) InterfacesNames() []string { interfaceNames := make([]string, len(this.interfaces)) for index, value := range this.interfaces { interfaceNames[index] = this.constantPool.getClassName(value) } return interfaceNames }
constant_pool.go:定义了一些方法帮助我们根据索引获取各种表
package classfile //这个接口表示常量池中每一张表 type ConstantInfo interface { readInfo(reader *ClassReader) } //常量池,其实就是所有类型表的切片 type ConstantPool []ConstantInfo //用于读取常量池中的表,将常量池中每张表解析之后放到这个切片中来,然后就可以根据索引获取表数据了 //首先两个字节是常量池中表的个数cpCount,紧接着就是各种表的实际数据,每个表中第一个字段表示了自己是什么类型的表, // 然后也已经规定好了自己所占字节大小 //注意两种表ConstantLongInfo和ConstantDoubleInfo,这种表示占两个位置,其他类型的占用一个位置 //所以常量池中表实际的数量肯定是要小于cpCount func readConstantPool(reader *ClassReader) ConstantPool { cpCount := int(reader.readUint16()) cp := make([]ConstantInfo, cpCount) //注意,常量池遍历从1开始,0表示不指向任何常量池数据 for i := 1; i < cpCount; i++ { cp[i] = readConstantInfo(reader, cp) switch cp[i].(type) { case *ConstantLong, *ConstantDouble: //如果是这两种类型的表,那么在常量池中就占两个位置 i++ } } return cp } //根据索引值获取常量池中表 func (this ConstantPool) getConstantInfo(index uint16) ConstantInfo { if cpInfo := this[index]; cpInfo != nil { return cpInfo } panic("Invalid constant pool index!") } //根据索引从常量池中获取某个ConstantNameAndTypeInfo表,然后获取这张表的名字和描述 //注意,这个名字和描述分别又对应着常量池中的表 func (this ConstantPool) getNameAndType(index uint16) (string, string) { //这里做了一个断言,因为这里没有接收nil,所以如果失败,直接panic ntInfo := this.getConstantInfo(index).(*ConstantNameAndTypeInfo) name := this.getUtf8(ntInfo.nameIndex) _type := this.getUtf8(ntInfo.descriptorIndex) return name, _type } //根据索引获取常量池中ConstantClassInfo表,获取该表的名字 //这个名字又对应常量池中一张ConstantUtf8Info表 func (this ConstantPool) getClassName(index uint16) string { classInfo := this.getConstantInfo(index).(*ConstantClassInfo) return this.getUtf8(classInfo.nameIndex) } //根据索引获取常量池中的ConstantUtf8Info表,获取其中保存的值 func (this ConstantPool) getUtf8(index uint16) string { utf8Info := this.getConstantInfo(index).(*ConstantUtf8Info) return utf8Info.str } //读取常量池中的一个表,注意,不管是什么表,它的第一个字节tag表示表的类型 //我们这里先获取表的类型,然后实例化相应的表,最后调用该表实现的readInfo方法读取表数据 func readConstantInfo(reader *ClassReader, constantPool ConstantPool) ConstantInfo { tag := reader.readUint8() info := newConstantInfo(tag, constantPool) info.readInfo(reader) return info } //下面就是常量池中的所有类型,其中最下面三种被注释了,是因为这是在jdk7才被添加的, // 为了支持新增的invokedynamic指令 //而且从下面我们大概将常量池分为两大类,字面量和符号引用; //字面量:字符串常量和数字常量 //符号引用:类名,接口名,以及字段和方法信息,为什么叫做符号引用呢?因为这几个表中没有存实际的数据, //存的都是指向常量池中ConstantUtf8Info表的索引 func newConstantInfo(tag uint8, constantPool ConstantPool) ConstantInfo { switch tag { case CONSTANT_Utf8: return &ConstantUtf8Info{} case CONSTANT_Integer: return &ConstantIntegerInfo{} case CONSTANT_Float: return &ConstantFloatInfo{} case CONSTANT_Long: return &ConstantLong{} case CONSTANT_Double: return &ConstantDouble{} case CONSTANT_Class: return &ConstantClassInfo{} case CONSTANT_String: return &ConstantStringInfo{} case CONSTANT_Fieldref: return &ConstantFieldrefInfo{} case CONSTANT_Methodref: return &ConstantMethodrefInfo{} case CONSTANT_InterfaceMethodref: return &ConstantInterfaceMethodrefInfo{} case CONSTANT_NameAndType: return &ConstantNameAndTypeInfo{} //case CONSTANT_MethodHandle: // return &ConstantMethodHandleInfo{} //case CONSTANT_MethodType: // return &ConstantMethodTypeInfo{} //case CONSTANT_InvokeDynamic: // return &ConstantInvokeDynamic{} default: panic("java.lang.ClassFormatError: constant pool tag!") } }
constant_info.go:常量池中表的类型
package classfile const ( CONSTANT_Utf8 = 1 CONSTANT_Integer = 3 CONSTANT_Float = 4 CONSTANT_Long = 5 CONSTANT_Double = 6 CONSTANT_Class = 7 CONSTANT_String = 8 CONSTANT_Fieldref = 9 CONSTANT_Methodref = 10 CONSTANT_InterfaceMethodref = 11 CONSTANT_NameAndType = 12 CONSTANT_MethodHandle = 15 CONSTANT_MethodType = 16 CONSTANT_InvokeDynamic = 18 )
cp_utf8.go:
package classfile type ConstantUtf8Info struct { str string } //CONSTANT_Utf8_info { //u1 tag; //u2 length; //u1 bytes[length]; //} //注意,这种表,第一个字节表示表的类型,然后两个字节表示该表存的字符串的长度 //最后根据这个长度去读取第三部分的数据,返回字节切片,我们简单的转为字符串 func (this *ConstantUtf8Info) readInfo(reader *ClassReader) { length := uint32(reader.readUint16()) bytes := reader.readBytes(length) this.str = string(bytes) }
cp_numberic.go:
package classfile import ( "math" ) //该文件放四种与数字相关的表 //第一种表 type ConstantIntegerInfo struct { val int32 } //实现了ConstantInfo接口,这种表第一个字节表示类型,后面4个字节表示存的数据 //CONSTANT_Integer_info { //u1 tag; //u4 bytes; //} func (this *ConstantIntegerInfo) readInfo(reader *ClassReader) { readUint32 := reader.readUint32() this.val = int32(readUint32) } //第二种表 type ConstantLong struct { val int64 } //CONSTANT_Long_info { //u1 tag; //u4 high_bytes; //u4 low_bytes; //} func (this *ConstantLong) readInfo(reader *ClassReader) { readUint64 := reader.readUint64() this.val = int64(readUint64) } //第三种表 type ConstantFloatInfo struct { val float32 } //CONSTANT_Float_info { //u1 tag; //u4 bytes; //} func (this *ConstantFloatInfo) readInfo(reader *ClassReader) { readUint32 := reader.readUint32() //将uint32类型的转为float32类型的 this.val = math.Float32frombits(readUint32) } //第四种表 type ConstantDouble struct { val float64 } //CONSTANT_Double_info { //u1 tag; //u4 high_bytes; //u4 low_bytes; //} func (this *ConstantDouble) readInfo(reader *ClassReader) { readUint64 := reader.readUint64() this.val = math.Float64frombits(readUint64) }
cp_string.go
package classfile //CONSTANT_String_info { //u1 tag; //u2 string_index; //} //这个表中没有存数据,第一个字节表示该表的类型,再之后的两个字节表示索引 // 这个索引表示指向常量池中ConstantUtf8Info表 type ConstantStringInfo struct { pool ConstantPool stringIndex uint16 } func (this *ConstantStringInfo) readInfo(reader *ClassReader) { this.stringIndex = reader.readUint16() } //获取ConstantStringInfo对应的字符串 //在常量池中根据索引找到对应的ConstantUtf8Info表 func (this *ConstantStringInfo) String() string { return this.pool.getUtf8(this.stringIndex) }
cp_name_and_type.go
package classfile //CONSTANT_NameAndType_info { //u1 tag; //u2 name_index; //u2 descriptor_index; //} type ConstantNameAndTypeInfo struct { nameIndex uint16 descriptorIndex uint16 } func (this *ConstantNameAndTypeInfo) readInfo(reader *ClassReader) { this.nameIndex = reader.readUint16() this.descriptorIndex = reader.readUint16() }
cp_member_ref.go
package classfile //CONSTANT_Fieldref_info { //u1 tag; //u2 class_index; //u2 name_and_type_index; //} type ConstantMemberrefInfo struct { pool ConstantPool classIndex uint16 nameAndTypeIndex uint16 } func (this *ConstantMemberrefInfo) readInfo(reader *ClassReader) { this.classIndex = reader.readUint16() this.nameAndTypeIndex = reader.readUint16() } func (this *ConstantMemberrefInfo) ClassName() string { return this.pool.getClassName(this.classIndex) } func (this *ConstantMemberrefInfo) NameAndDescriptor() (string, string) { return this.pool.getNameAndType(this.nameAndTypeIndex) } type ConstantFieldrefInfo struct { ConstantMemberrefInfo } type ConstantMethodrefInfo struct { ConstantMemberrefInfo } type ConstantInterfaceMethodrefInfo struct { ConstantMemberrefInfo }
cp_class.go
package classfile //CONSTANT_Class_info { //u1 tag; //u2 name_index; //} type ConstantClassInfo struct { pool ConstantPool nameIndex uint16 } func (this *ConstantClassInfo) readInfo(reader *ClassReader) { this.nameIndex = reader.readUint16() } func (this *ConstantClassInfo) Name() string { return this.pool.getUtf8(this.nameIndex) }
member_info.go:方法表的字段表都是一样的,只是其中属性表有点差异,所以可以用下面这个结构体表示:
package classfile //field_info { //u2 access_flags; //u2 name_index; //u2 descriptor_index; //u2 attributes_count; //attribute_info attributes[attributes_count]; //} //字段表和方法表的结构几乎是一样的,只是属性表不同,就用这个结构体表示 type MemberInfo struct { constPool ConstantPool accessFlags uint16 //访问修饰符 nameIndex uint16 //字段名 descriptorIndex uint16 //字段的类型 attributes []AttributeInfo //属性表切片 } //func (self *MemberInfo) AccessFlags() uint16 {...} // getter //func (self *MemberInfo) Name() string {...} //func (self *MemberInfo) Descriptor() string {...} //因为字段或者方法可能有多个,所以就遍历进行读取 func readMembers(reader *ClassReader, cp ConstantPool) []*MemberInfo { memberCount := reader.readUint16() infos := make([]*MemberInfo, memberCount) for index := range infos { infos[index] = readMember(reader, cp) } return infos } func readMember(reader *ClassReader, cp ConstantPool) *MemberInfo { return &MemberInfo{ constPool: cp, accessFlags: reader.readUint16(), nameIndex: reader.readUint16(), descriptorIndex: reader.readUint16(), attributes: readAttributes(reader, cp), } } //根据索引获取常量池中的ConstantUtf8Info表中存的字段或者方法的字面量 func (this *MemberInfo) Name() string { return this.constPool.getUtf8(this.nameIndex) } //根据索引获取常量池中的ConstantNameAndTypeInfo表中的字段或者方法的描述 func (this *MemberInfo) Descriptor() string { return this.constPool.getUtf8(this.descriptorIndex) }
再下面的都是属性表相关的内容(包括八个属性表):
attribute_info.go:属性表对应的顶层接口,所有的属性表必须实现该接口
package classfile //attribute_info { //u2 attribute_name_index; //u4 attribute_length; //u1 info[attribute_length]; //} //各个属性表表达的属性都不相同,所以不能用常量池中表的类型可以靠tag来区分 //这里是使用属性名来区分 //并且属性表中也没有存实际的数据,存的是指向常量池中ConstantUtf8Info表的索引 type AttributeInfo interface { readInfo(reader *ClassReader) } //这里的话获取class文件中属性表的数量,根据属性表的数量去常量池中读取属性表 func readAttributes(reader *ClassReader, pool ConstantPool) []AttributeInfo { attributesCount := reader.readUint16() attributes := make([]AttributeInfo, attributesCount) for i := range attributes { attributes[i] = readAttribute(reader, pool) } return attributes } //至于怎么读属性表呢?首先读前两个字节表示属性名称的索引 // 根据这个索引去常量池中获取ConstantUtf8Info表中的数据获取属性名称 //然后再读取4个字节表示属性表的长度,根据属性名称和长度去读取各种属性表的数据,保存到各个结构体中 func readAttribute(reader *ClassReader, pool ConstantPool) AttributeInfo { attrNameIndex := reader.readUint16() attrName := pool.getUtf8(attrNameIndex) attrLength := reader.readUint32() attrInfo := newAttributeInfo(attrName, attrLength, pool) attrInfo.readInfo(reader) return attrInfo } func newAttributeInfo(attrName string, attrLen uint32, pool ConstantPool) AttributeInfo { switch attrName { case "Deprecated": return &DeprecatedAttribute{} case "Synthetic": return &SyntheticAttribute{} case "SourceFile": return &SourceFileAttribute{pool: pool} case "ConstantValue": return &ConstantValueAttribute{} case "Code": return &CodeAttribute{pool: pool} case "Exceptions": return &ExceptionsAttribute{} case "LineNumberTable": return &LineNumberTableAttribute{} case "LocalVariableTable": return &LocalVariableTableAttribute{} default: return &UnparsedAttribute{attrName, attrLen, nil} } }
attr_unparsed.go
package classfile type UnparsedAttribute struct { name string length uint32 info []byte } func (this *UnparsedAttribute) readInfo(reader *ClassReader) { this.info = reader.readBytes(this.length) }
attr_source_file.go
package classfile //SourceFile_attribute { //u2 attribute_name_index; //u4 attribute_length; //u2 sourcefile_index; //} //这个属性表表示指出源文件名,其中attribute_length;必须是2,另外两个是常量池索引 type SourceFileAttribute struct { pool ConstantPool sourcefileIndex uint16 } func (this *SourceFileAttribute) readInfo(reader *ClassReader) { this.sourcefileIndex = reader.readUint16() } func (this *SourceFileAttribute) FileName() string { return this.pool.getUtf8(this.sourcefileIndex) }
attr_makers.go
package classfile //Deprecated_attribute { //u2 attribute_name_index; //u4 attribute_length; //} //Synthetic_attribute { //u2 attribute_name_index; //u4 attribute_length; //} //Deprecated_attribute属性表是在java类中使用了@Deprecated注解标识该类废弃了 //Synthetic_attribute属性表是标识java编译器自己生成的类 //由于这两个属性表都只是起到标识作用,所以attribute_length为0 //也因此,在下面的readInfo方法中啥也不干 type DeprecatedAttribute struct { MarkerAttribute } type SyntheticAttribute struct { MarkerAttribute } type MarkerAttribute struct { } func (this *MarkerAttribute) readInfo(reader *ClassReader) { }
attr_line_number_table.go
package classfile //LineNumberTable_attribute { //u2 attribute_name_index; //u4 attribute_length; //u2 line_number_table_length; //{ u2 start_pc; //u2 line_number; //} line_number_table[line_number_table_length]; //} type LineNumberTableAttribute struct { lineNumberTable []*LineNumberTableEntry } type LineNumberTableEntry struct { startPc uint16 lineNumber uint16 } func (this *LineNumberTableAttribute) readInfo(reader *ClassReader) { attributeLength := reader.readUint16() tableEntries := make([]*LineNumberTableEntry, attributeLength) for i := range tableEntries { tableEntries[i] = &LineNumberTableEntry{ startPc: reader.readUint16(), lineNumber: reader.readUint16(), } } this.lineNumberTable = tableEntries }
attr_exceptions.go
package classfile //Exceptions_attribute { //u2 attribute_name_index; //u4 attribute_length; //u2 number_of_exceptions; //u2 exception_index_table[number_of_exceptions]; //} type ExceptionsAttribute struct { exceptionIndexTable []uint16 } func (this *ExceptionsAttribute) readInfo(reader *ClassReader) { this.exceptionIndexTable = reader.readUint16s() } func (this *ExceptionsAttribute) ExceptionIndexTable() []uint16 { return this.exceptionIndexTable }
attr_constant_value.go
package classfile //ConstantValue_attribute { //u2 attribute_name_index; //u4 attribute_length; //u2 constantvalue_index; //} //用于表示常量表达式的值,其中attribute_length为确定值2, type ConstantValueAttribute struct { constantvalueIndex uint16 } func (this *ConstantValueAttribute) readInfo(reader *ClassReader) { this.constantvalueIndex = reader.readUint16() } func (this *ConstantValueAttribute) ConstantvalueIndex() uint16 { return this.constantvalueIndex }
attr_code.go
package classfile //Code_attribute { //u2 attribute_name_index; //u4 attribute_length; //u2 max_stack; //u2 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]; //} //这个属性表存放字节码中方法有关的信息,例如max_stack表示操作数栈的最大深度;max_locals表示局部变量表的大小 //然后就是异常处理表和属性表 type CodeAttribute struct { pool ConstantPool maxStack uint16 maxLocals uint16 code []byte exceptionTable []*ExceptionTableEntry attributes []AttributeInfo } type ExceptionTableEntry struct { startPc uint16 endPc uint16 handlerPc uint16 catchType uint16 } func (this *CodeAttribute) readInfo(reader *ClassReader) { this.maxStack = reader.readUint16() this.maxLocals = reader.readUint16() codeLength := reader.readUint32() this.code = reader.readBytes(codeLength) this.exceptionTable = readExceptionTable(reader) this.attributes = readAttributes(reader, this.pool) } func readExceptionTable(reader *ClassReader) []*ExceptionTableEntry { exceptionTableLength := reader.readUint16() exceptionTables := make([]*ExceptionTableEntry, exceptionTableLength) for i := range exceptionTables { exceptionTables[i] = &ExceptionTableEntry{ startPc: reader.readUint16(), endPc: reader.readUint16(), handlerPc: reader.readUint16(), catchType: reader.readUint16(), } } return exceptionTables }
attr_local_varible_table.go
package classfile //LocalVariableTable_attribute { //u2 attribute_name_index; //u4 attribute_length; //u2 local_variable_table_length; //{ u2 start_pc; //u2 length; //u2 name_index; //u2 descriptor_index; //u2 index; //} local_variable_table[local_variable_table_length]; //} type LocalVariableTableAttribute struct { localVariableTable []*LocalVariableTableEntry } type LocalVariableTableEntry struct { startPc uint16 length uint16 nameIndex uint16 descriptorIndex uint16 index uint16 } func (this *LocalVariableTableAttribute) readInfo(reader *ClassReader) { localVariableTableLength := reader.readUint16() variableTableEntries := make([]*LocalVariableTableEntry, localVariableTableLength) for i := range variableTableEntries { variableTableEntries[i] = &LocalVariableTableEntry{ startPc: reader.readUint16(), length: reader.readUint16(), nameIndex: reader.readUint16(), descriptorIndex: reader.readUint16(), index: reader.readUint16(), } } this.localVariableTable = variableTableEntries }
4.修改main.go
上面的文件基本上就是把class字节码文件中的所有字节都读取了,然后放到ClassFile这个结构体中保存起来,简单的修改了一下startJVM函数,逻辑还是很清楚的;
package main import ( "firstGoPrj0114/jvmgo/ch03/classfile" "firstGoPrj0114/jvmgo/ch03/classpath" "fmt" "strings" ) //命令行输入 .ch02.exe -Xjre "D:javajdk8jre" java.lang.Object func main() { cmd := parseCmd() if cmd.versionFlag { fmt.Println("version 1.0.0 by wangyouquan") } else if cmd.helpFlag || cmd.class == "" { printUsage() } else { startJVM(cmd) } } //找到jdk中的任意一个类 func startJVM(cmd *Cmd) { //传入jdk中的jre全路径和类名,就会去里面lib中去找或者lib/ext中去找对应的类 //命令行输入 .ch02.exe -Xjre "D:javajdk8jre" java.lang.Object cp := classpath.Parse(cmd.XjreOption, cmd.cpOption) fmt.Printf("classpath:%v class:%v args:%v ", cp, cmd.class, cmd.args) //将全类名中的.转为/,以目录的形式去读取class文件 className := strings.Replace(cmd.class, ".", "/", -1) //加载类 classFile := loadClass(className, cp) //简单的将类中的信息打印出来 printClassInfo(classFile) } //可以看到加载类的方法很容易,就是将读取到的class字节码数组放到Parse函数去解析, //将解析出来的数据放到ClassFile结构体中保存起来,这里面就有魔数,版本号,常量池等等信息 func loadClass(className string, cp *classpath.Classpath) *classfile.ClassFile { classData, _, err := cp.ReadClass(className) if err != nil { panic(err) } classFile, err := classfile.Parse(classData) if err != nil { panic(err) } return classFile } //对ClassFile结构体中的信息进行简单的打印出来 func printClassInfo(classFile *classfile.ClassFile) { fmt.Printf("version:%v,%v ", classFile.MinorVersion(), classFile.MajorVersion()) fmt.Printf("constantPool count:%v ", len(classFile.ConstantPool())) fmt.Printf("access flags:0x%x ", classFile.AccessFlags()) fmt.Printf("this class:%v ", classFile.ClassName()) fmt.Printf("super class:%v ", classFile.SuperClassName()) fmt.Printf("interface:%v ", classFile.InterfacesNames()) fmt.Printf("fields count:%v ", len(classFile.Fields())) for _, field := range classFile.Fields() { fmt.Printf(" %s ", field.Name()) } fmt.Printf("methods count:%v ", len(classFile.Methods())) for _, method := range classFile.Methods() { fmt.Printf(" %s ", method.Name()) } }
5.测试
还是用前面说的方法生成ch03.exe可执行文件,测试一下官方的Object.class方法是否能打印出信息:
然后再测试一下我们第一篇自己写的在jar包中的HelloWorld.class字节码文件: