主頁 > 後端開發 > go實作java虛擬機03

go實作java虛擬機03

2020-09-14 20:34:18 後端開發

  上一篇我們已經根據路徑讀取到了我們需要的位元組碼檔案,就以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 = https://www.cnblogs.com/wyq1995/p/this.data[1:]
    return val
}

//讀取兩個位元組
func (this *ClassReader) readUint16() uint16 { //u2
    val := binary.BigEndian.Uint16(this.data)
    this.data = https://www.cnblogs.com/wyq1995/p/this.data[2:]
    return val

}

//讀取四個位元組
func (this *ClassReader) readUint32() uint32 { //u4
    val := binary.BigEndian.Uint32(this.data)
    this.data = https://www.cnblogs.com/wyq1995/p/this.data[4:]
    return val

}

//讀取8個位元組
func (this *ClassReader) readUint64() uint64 {
    val := binary.BigEndian.Uint64(this.data)
    this.data = https://www.cnblogs.com/wyq1995/p/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 = https://www.cnblogs.com/wyq1995/p/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:\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位元組碼檔案:

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/38583.html

標籤:Go

上一篇:docker常用命令

下一篇:GoLang 自學系列(二)—— defer

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more