go实现java虚拟机03

go实现java虚拟机03

  上一篇我们已经根据路径读取到了我们需要的字节码文件,就以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

}
View Code

 

  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
}
View Code

  

  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!")
    }

}
View Code

 

  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
)
View Code

  

  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)
}
View Code

 

  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)
}
View Code

   

  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)
}
View Code

  

  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()
}
View Code

   

  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
}
View Code

   

  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)
}
View Code

 

  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)
}
View Code

   

  再下面的都是属性表相关的内容(包括八个属性表):

  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}
    }
}
View Code

 

  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)
}
View Code

  

  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)
}
View Code

 

  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) {

}
View Code

 

  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
}
View Code

 

  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
}
View Code

 

  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
}
View Code

 

  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
}
View Code

 

  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
}
View Code

 

 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字节码文件: