zoukankan      html  css  js  c++  java
  • 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字节码文件:

  • 相关阅读:
    「联赛模拟测试33」题解
    分享几个基于vue的移动端框架
    11-15
    test
    联赛模拟测试20 C. Weed
    联赛模拟测试20 D. Drink
    联赛模拟测试24 联合权值·改
    联赛模拟测试21 表格
    近期的一些考试题目
    shell脚本执行错误 $' ':command not found
  • 原文地址:https://www.cnblogs.com/wyq1995/p/12381052.html
Copyright © 2011-2022 走看看