当前位置: 移动技术网 > IT编程>脚本编程>Go语言 > go实现java虚拟机03

go实现java虚拟机03

2020年03月09日  | 移动技术网IT编程  | 我要评论
上一篇我们已经根据路径读取到了我们需要的字节码文件,就以java.lang.Object这个类为例,可以看到类似下面这种东西,那么这些数字是什么呢? 要了解这个,我们大概可以猜到这是十进制的,在线将十进制转为十六进制看看https://tool.oschina.net/hexconvert/,注意上 ...

  上一篇我们已经根据路径读取到了我们需要的字节码文件,就以java.lang.object这个类为例,可以看到类似下面这种东西,那么这些数字是什么呢?

 

  要了解这个,我们大概可以猜到这是十进制的,在线将十进制转为十六进制看看,注意上图中已经用空格隔开了每个数,我们将最前面的变成十六进制看看效果,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表示访问权限,占两个字节,等等

  接下来说说常量池中表的类型以及每个表的结构(每一种表都标识了自己占用的字节大小),如下所示,每一种表都有自己特有的结构,还要注意一点,下面这么多表中,某一个表中某一项可能会引用另外一张表的数据的;

 

 

   常量池之外每个部分表示的什么,我随便找了一篇博客,参考这篇说的比较仔细的:;这就不多说了,这也不是我们的重点;

 

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种,至于每一种是什么意思,看看这个博客:,在上面的目录中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:\java\jdk8\jre" 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:\java\jdk8\jre" java.lang.object
    cp := classpath.parse(cmd.xjreoption, cmd.cpoption)
    fmt.printf("classpath:%v class:%v args:%v\n", 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\n", classfile.minorversion(), classfile.majorversion())
    fmt.printf("constantpool count:%v\n", len(classfile.constantpool()))
    fmt.printf("access flags:0x%x\n", classfile.accessflags())
    fmt.printf("this class:%v\n", classfile.classname())
    fmt.printf("super class:%v\n", classfile.superclassname())
    fmt.printf("interface:%v\n", classfile.interfacesnames())
    fmt.printf("fields count:%v\n", len(classfile.fields()))
    for _, field := range classfile.fields() {
        fmt.printf("    %s\n", field.name())
    }
    fmt.printf("methods count:%v\n", len(classfile.methods()))
    for _, method := range classfile.methods() {
        fmt.printf("    %s\n", method.name())
    }
}

 

5.测试

  还是用前面说的方法生成ch03.exe可执行文件,测试一下官方的object.class方法是否能打印出信息:

 

  然后再测试一下我们第一篇自己写的在jar包中的helloworld.class字节码文件:

如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网