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

go实现java虚拟机03

 

  要了解这个,我们大概可以猜到这是十进制的,在线将十进制转为十六进制看看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文件的结构,右边的对应的十六进制;

go实现java虚拟机03

 

 1.简单说说class文件结构

  首先说说class字节码文件的结构,看有哪几部分组成,其实在上图左边已经差不多说明了,下图更清楚:其中u2表示两个字节,u4表示四个字节,这之外的比如cp_info表示的是一张表,然后表中每一个字段又对应着一张表(这么说肯定不好理解,见过多维数组没,表就看作数组就好,只不多数组每个位置又对应这一个数组,这就叫多维数组);

  至于下面这些代表什么意思,这里 就不多做赘述了,自己去看字节码文件的组成吧,不是我们的重点;

go实现java虚拟机03

 

  这里的结构有个很有意思的现象,就是在列出该项数据之前,会提前指明该数据有几个字节;比如constant_pool_count表示常量池中有n个表,占用2个字节;而紧接其后的constant_pool[constant_pool_count-1]存的就是各个表实际的数据,由于每个表第一个字节表示该表的类型,然后后面又会指定该表的大小,所以可以确定总共占用多少字节;access_flags表示访问权限,占两个字节,等等

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

go实现java虚拟机03

 go实现java虚拟机03

 go实现java虚拟机03

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

 

2.读取class字节码文件

  总的目录结构如下所示:

go实现java虚拟机03

   根据上面这个图我们将classfile中的文件分为几个部分理解一下,首先是class_reader.go这个文件里面是结构体,存了class文件的全部数据的字节切片,并且定义了一些方法一下子读取1字节,2字节,4字节和8字节等方法,方便于我们读取数据;

  然后class_file.go文件中一个结构体,存了字节码中所有结构,就是魔数,版本号,常量池,访问修饰符等等,然后定义了一些获取这些部分的方法,可想而知这些方法需要使用前面说的class_reader.go文件中结构体读取数据;

  再然后比较关键,就是class_file.go文件中定义的那些获取各个部分的方法,下图所示,其中最关键的就是读取常量池属性表

go实现java虚拟机03

 

  说道读取常量池数据,那么因为常量池中有很多不同类型的表,我们定义一个接口,所有的表都必须实现这个接口;至于总共有些什么类型的表,大致分为两种,一种是字符型,一种是引用型的;字符型的分为字符串和数字类的,分别是在上面的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中确定是什么类型的属性表;

go实现java虚拟机03

 

  属性表也有很多类型,我们这里只是列举其中的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

相关文章:

  • 2021-06-15
  • 2021-11-06
  • 2022-01-06
  • 2022-12-23
  • 2021-08-12
  • 2021-07-29
  • 2019-01-16
  • 2022-01-31
猜你喜欢
  • 2021-12-02
  • 2021-06-17
  • 2021-05-22
  • 2021-07-12
  • 2019-01-28
  • 2021-11-17
相关资源
相似解决方案