上一篇我們已經根據路徑讀取到了我們需要的位元組碼檔案,就以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常用命令
