1. 程式人生 > >《自己動手寫java虛擬機器》學習筆記(五)-----解析class檔案(go)

《自己動手寫java虛擬機器》學習筆記(五)-----解析class檔案(go)

         專案地址:https://github.com/gongxianshengjiadexiaohuihui

  上一節,我們已經通過路徑找到了指定的class檔案,這一節,我們開始解析class檔案,我們知道class檔案裡面存放的是位元組碼,如果不清楚它的檔案結構,它對我們來說就是一堆亂碼,但是其實它是嚴格按照某種順序存放的,我們只要按照相對應的順序獲取並翻譯這些位元組碼,就能得到我們需要的資訊。

         建立新的資料夾classfile

         既然要讀取,首先來寫讀的工具

        class_reader.go

        

package classfile 

import "encoding/binary"

type ClassReader struct{
	data []byte
}

func (self *ClassReader) readUint8() uint8 {
	val := self.data[0]
	self.data = self.data[1:]
	return val
}
//java的位元組碼採用大端儲存的方式儲存,即低地址存放最高有效位元組,高地址存放最低有效位元組
func (self *ClassReader) readUint16() uint16 {
	val := binary.BigEndian.Uint16(self.data)
	self.data = self.data[2:]
	return val
}

func (self *ClassReader) readUint32() uint32 {
	val := binary.BigEndian.Uint32(self.data)
	self.data = self.data[4:]
	return val
}

func (self *ClassReader) readUint64() uint64 {
	val := binary.BigEndian.Uint64(self.data)
	self.data = self.data[8:]
	return val
}
//讀取uint16表,表的大小由開頭的uint16資料確定
func (self *ClassReader) readUint16s() []uint16 {
	n := self.readUint16() 
	s := make([]uint16,n)
	for i := range s{
		s[i] = self.readUint16()
	}
	return s
}
//讀取指定數量的位元組,注意前面讀取的是資料
func (self *ClassReader) readBytes(n uint32) []byte {
	bytes := self.data[:n]
	self.data = self.data[n:]
	return bytes
}

有了讀取工具,就可以開始解析了,首先是class檔案的結構體

class_file.go

package classfile 

import "fmt"

type ClassFile struct {
	//magic     uint32 魔數檢測下是不是0xCAFEBABE就可以了
	minorVersion          uint16 //小版本號
	majorVersion          uint16 //大版本號
	constantPool          ConstantPool //常量池,內部是陣列
	accessFlags           uint16 //類訪問標誌
	thisClass             uint16 //類名得常量池索引
	superClass            uint16 //超類名的常量池索引
	interfaces           []uint16 //介面索引表,存放的也是常量池索引
	fields               []*MemberInfo //欄位表
	methods              []*MemberInfo //方法表
	attributes           []AttributeInfo //屬性表
}
//將位元組碼解析位ClassFile格式
func Parse(classData []byte)(cf *ClassFile,err error){
	//GO語言沒有異常機制,只有一個panic-recover,panic用於丟擲異常;將recover()寫在defer中,且有可能發生在panic之前,此方法依然要呼叫,當程式遇到panic時,系統跳過後續的值,進入defer,而recover()就將捕獲panic的值
	defer func(){
		if r := recover(); r != nil {
			var ok bool
			err, ok = r.(error)
			if !ok {
				err = fmt.Errorf("%v",r)
			}
		}
		}()
		cr := &ClassReader{classData}
		cf = &ClassFile{}
		cf.read(cr)
		return 
	}
//功能相當於建構函式之類
	func (self *ClassFile)read(reader *ClassReader){
	//檢查魔數是不是0xCAFEBABE
		self.readAndCheckMagic(reader)
	//檢查版本號
		self.readAndCheckVersion(reader)
    //解析常量池
		self.constantPool = readConstantPool(reader)
    //16位的bitmask,指出class定義的是類還是介面,訪問級別是public還是private,這裡只進行初步解析,讀取訪問標誌以備後用
		self.accessFlags = reader.readUint16()
    //本類的常量池索引把全限定名.換成/
		self.thisClass = reader.readUint16()
    //父類
		self.superClass = reader.readUint16()
    //介面索引表
		self.interfaces = reader.readUint16s()
    //欄位表,這裡不是存的索引,而是從常量池中讀出來
		self.fields = readMembers(reader,self.constantPool)
    //方法表
		self.methods = readMembers(reader,self.constantPool)
    //屬性表
		self.attributes = readAttributes(reader,self.constantPool)
	}
//檢查魔數
	func (self *ClassFile)readAndCheckMagic(reader *ClassReader){
		magic := reader.readUint32()
		if magic != 0xCAFEBABE {
			panic("java.lang.ClassFormatError: magic")
		}
	}
//檢測版本號,jdk8只支援45.0-52.0的檔案
	func (self *ClassFile) readAndCheckVersion(reader *ClassReader) {
	self.minorVersion = reader.readUint16()
	self.majorVersion = reader.readUint16()
	switch self.majorVersion {
	case 45:
		return 
	case 46,47,48,49,50,51,52:
		if self.minorVersion == 0 {
			return 
		}
	}
	panic("java.lang.UnsupporttedClassVersionError!")
}
//getter
func (self *ClassFile)MinorVersion() uint16{
	return self.minorVersion
}
func (self *ClassFile)MajorVersion() uint16{
	return self.majorVersion
}
func (self *ClassFile)ConstantPool() ConstantPool{
	return self.constantPool
}
func (self *ClassFile)AccessFlags() uint16{
	return self.accessFlags
}
func (self *ClassFile)Fields() []*MemberInfo{
	return self.fields
}
func (self *ClassFile)Methods() []*MemberInfo{
	return self.methods
}
//從常量池中get
func (self *ClassFile)ClassName() string{
	return self.constantPool.getClassName(self.thisClass)
}
func (self *ClassFile)SuperClassName() string{
	return self.constantPool.getClassName(self.superClass)
}
func (self *ClassFile)InterfaceNames() []string{
	interfaceNames := make([]string,len(self.interfaces))
	for i,cpIndex := range self.interfaces {
		interfaceNames[i] =  self.constantPool.getClassName(cpIndex)
	}
	return interfaceNames
}
// func (self *ClassFile)SourceFileAttribute()*SourceFileAttribute{
// 	for _,attrInfo := range self.attributes {
// 		switch attrInfo.(type){
// 		case *SourceFileAttribute:
// 			return attrInfo.(*SourceFileAttribute)
// 		}
// 	}
// 	return nil 
// }

相比java語言,Go的訪問控制非常簡單:只有公開和私有兩種。所有首字母大寫的型別、結構體、欄位、變數、函式、方法等都是公開的,可供其它包使用。首字母小寫的則是私有的,只能在包內使用

通過ClassViewer開啟class檔案我們可以看到class檔案的結構,ClassViewer下載地址https://download.csdn.net/download/qq_33543634/10764870

常量池佔據了class檔案很大一部分資料,裡面存放著各種各樣的常量資訊,包括數字和字串常量、類和介面名、欄位和方法名。

常量池結構體

constant_pool.go

package classfile 

type ConstantPool []ConstantInfo 
//讀取常量池
func readConstantPool(reader *ClassReader) ConstantPool{
   cpCount := int(reader.readUint16())
   cp := make([]ConstantInfo,cpCount)
//常量池的大小比表頭給大小實際小1,且常量池的索引是1-n-1,CONSTANT_Long_info和CONSTANT_Double_info各佔兩個位置,
   for i := 1 ; i < cpCount ; i++ {
   	cp[i] = readConstantInfo(reader,cp)
   	switch cp[i].(type){
   	case *ConstantLongInfo , *ConstantDoubleInfo :
   		i++
   	}
   }
   return cp 
}
//按索引查詢常量
func (self ConstantPool) getConstantInfo(index uint16) ConstantInfo{
	if cpInfo := self[index]; cpInfo != nil {
		return cpInfo
	}
	panic("Invalid constant pool index!")
}
//從常量池查詢欄位或方法的名字和描述符
func (self ConstantPool) getNameAndType(index uint16) (string,string){
	ntInfo := self.getConstantInfo(index).(*ConstantNameAndTypeInfo)
	name := self.getUtf8(ntInfo.nameIndex)//名字
	_type := self.getUtf8(ntInfo.descriptorIndex)//描述符
	return name,_type
}
//從常量池查詢類名
func (self ConstantPool) getClassName(index uint16) string {
	classInfo := self.getConstantInfo(index).(*ConstantClassInfo)
	return self.getUtf8(classInfo.nameIndex)
}
//從常量池查詢UTF-8字串
func (self ConstantPool) getUtf8(index uint16) string{
	utf8Info := self.getConstantInfo(index).(*ConstantUtf8Info)
	return utf8Info.str
}

欄位和方法表

member_info.go

package classfile 

type MemberInfo struct {
	cp              ConstantPool//常量池 
	accessFlags     uint16//訪問許可權
	nameIndex       uint16//欄位名或方法名的常量池索引
	descriptorIndex uint16//描述符的常量池索引
	attributes      []AttributeInfo//屬性表
}
//讀取欄位表或方法表
func readMembers(reader *ClassReader,cp ConstantPool) []*MemberInfo{
	memberCount := reader.readUint16() 
	members := make([]*MemberInfo,memberCount)
	for i := range members {
		members[i] = readMember(reader,cp)
	}
	return members
}
//讀取欄位或方法
func readMember(reader *ClassReader,cp ConstantPool) *MemberInfo{
	return &MemberInfo{
		cp:              cp,
		accessFlags:     reader.readUint16(),
		nameIndex:       reader.readUint16(),
		descriptorIndex: reader.readUint16(),
		attributes:      readAttributes(reader,cp),
	}
}
//getter
func (self *MemberInfo)Name() string{
	return self.cp.getUtf8(self.nameIndex)
}
func (self *MemberInfo)Descriptor() string{
	return self.cp.getUtf8(self.descriptorIndex)
}

常量池實際上是一個表,由constantInfo組成,每個constantInfo都有自己的序號,這序號其實就是陣列的下標,class檔案中好多方法、欄位等存放的其實就是常量池索引,也就是陣列下標,通過下標定位到檔案位置。

constantInfo介面

constant_info.go

package classfile 

const (
	//類名
	CONSTANT_Class                   = 7
	CONSTANT_Fieldref                = 9
	CONSTANT_Methodref               = 10
	CONSTANT_InterfaceMethodref      = 11
	CONSTANT_String                  = 8
	//4位元組,有符號,更小的int,boolean,short,byte,char也是它
	CONSTANT_Integer                 = 3
	//4位元組,有符號
	CONSTANT_Float                   = 4
    //8位元組,有符號
	CONSTANT_Long                    = 5
	//8位元組,有符號
	CONSTANT_Double                  = 6
	//名字和描述符
	CONSTANT_NameAndType             = 12
	//字串
	CONSTANT_Utf8                    = 1
	CONSTANT_MethodHandle            = 15
	CONSTANT_MethodType              = 16
	CONSTANT_InvokeDynamic           = 18
)

type ConstantInfo interface {
	readInfo(reader *ClassReader)
}
//讀取單個常量池常量
func readConstantInfo(reader *ClassReader,cp ConstantPool) ConstantInfo{
	//獲取常量格式,根據格式生成對應的檔案格式,然後呼叫對應的讀取方法
	tag := reader.readUint8()
	c := newConstantInfo(tag,cp)
	c.readInfo(reader)
	return c 
}
//根據常量格式生成對應檔案,類似於建構函式
func newConstantInfo(tag uint8,cp ConstantPool) ConstantInfo{
	switch tag{
		case CONSTANT_Integer: return &ConstantIntegerInfo{}
		case CONSTANT_Float:   return &ConstantFloatInfo{}
		case CONSTANT_Long:    return &ConstantLongInfo{}
		case CONSTANT_Double:  return &ConstantDoubleInfo{}
		case CONSTANT_Utf8:    return &ConstantUtf8Info{}
		case CONSTANT_String:  return &ConstantStringInfo{cp : cp}
		case CONSTANT_Class:   return &ConstantClassInfo{cp : cp}
		case CONSTANT_Fieldref: return &ConstantFieldrefInfo{ConstantMemberrefInfo{cp : cp}}
		case CONSTANT_Methodref: return &ConstantMethodrefInfo{ConstantMemberrefInfo{cp : cp}}
		case CONSTANT_InterfaceMethodref: return &ConstantInterfaceMethodrefInfo{ConstantMemberrefInfo{cp : cp}}
		case CONSTANT_NameAndType: return &ConstantNameAndTypeInfo{}
		//case CONSTANT_MethodType: return &ConstantMethodTypeInfo{}
		//case CONSTANT_MethodHandle: return &ConstantMethodHandleInfo{}
		//case CONSTANT_InvokeDynamic: return &ConstantInvokeDynamicInfo{}
		default: panic("java.lang.ClassFormatError: constant pool tag")
	}
}

這種方法,和我們通過路徑返回對應檔案的格式一樣,是一個介面,讓不同格式的constantInfo去實現,前面我們已經提及,go實現介面的方法就是實現介面對應的方法即可,實現這個介面,只需實現readInfo(reader *ClassReader)這個方法即可

下面是實現了上述介面的各種常量

cp_numeric.go

package classfile 

import "math"

//Integer型別常量
type ConstantIntegerInfo struct {
	val int32 //4位元組有符號
}
//實現ConstantInfo介面的方法
func (self *ConstantIntegerInfo) readInfo(reader *ClassReader){
	bytes := reader.readUint32()
	//將無符號uint32轉有符號int32
	self.val = int32(bytes)
}
//getter
func (self *ConstantIntegerInfo) Value() int32 {
	return self.val 
}

//Float型別常量

type ConstantFloatInfo struct {
	val float32
}
//實現ConstantInfo介面的方法
func (self *ConstantFloatInfo) readInfo(reader *ClassReader){
   bytes := reader.readUint32()
   self.val = math.Float32frombits(bytes)
}
//getter
func (self *ConstantFloatInfo) Value() float32{
	return self.val
}
//Long型別常量
type ConstantLongInfo struct {
	val int64
}
//實現ConstantInfo介面的方法
func (self *ConstantLongInfo) readInfo(reader *ClassReader){
	bytes := reader.readUint64()
	self.val = int64(bytes)
}
//getter
func (self *ConstantLongInfo) Value() int64{
	return self.val
}
//Double型別常量

type ConstantDoubleInfo struct {
	val float64 
}
//實現ConstantInfo介面的方法
func (self *ConstantDoubleInfo) readInfo(reader *ClassReader){
	bytes := reader.readUint64()
	self.val = math.Float64frombits(bytes)
}
func (self *ConstantDoubleInfo) Value() float64{
	return self.val
}

cp_utf8.go

package classfile 


type ConstantUtf8Info struct {
	str string
}
//實現ConstantInfo介面
func (self *ConstantUtf8Info) readInfo(reader *ClassReader){
	length := uint32(reader.readUint16())
	bytes := reader.readBytes(length)
	self.str = decodeMUTF8(bytes)
}
//因為Go語言字串使用UTF-8編碼,所以如果字串中不包含null字元或補充字元,下面的函式是可以正常工作的
func decodeMUTF8(bytes []byte) string {
     return string(bytes)
}

cp_string.go

package classfile 

//ConstantStringInfo本身不存放字串資料,只是存放了常量池索引指向ConstantUtf8Info

type ConstantStringInfo struct {
	cp     ConstantPool 
	stringIndex uint16
}
//實現ConstantInfo介面
func (self *ConstantStringInfo) readInfo(reader *ClassReader){
	self.stringIndex = reader.readUint16()
}
//從常量池中查詢字串
func (self *ConstantStringInfo) String() string {
	return self.cp.getUtf8(self.stringIndex)
}

cp_class.go         表示類 或介面的符號引用

package classfile 
//類或介面的引用,也是存的常量池索引,裡面是引用的全限定名
type ConstantClassInfo struct {
	cp              ConstantPool
	nameIndex       uint16
}
//實現ConstantInfo介面
func (self *ConstantClassInfo) readInfo(reader *ClassReader){
	self.nameIndex = reader.readUint16()

}
//返回引用的全限定名
func (self *ConstantClassInfo) Name() string{
	return self.cp.getUtf8(self.nameIndex)
}

cp_name_and_type.go

java虛擬機器規範定義了一種簡單的語法來描述欄位和方法,可以根據下面的規則生成描述符

  1. 基本型別byte,short,char,int,long,float和double的描述符是單個字母,分別對應B,S,CI,J,F和D,注意long的描述符是J而不是L。
  2. 引用型別的描述符是L+類的完全限定名+分號。
  3. 陣列型別的描述符是[ +陣列元素型別描述符。
  • 欄位描述符就是欄位型別描述符
  • 方法描述符是(分號分隔的引數型別描述符)+返回值型別描述符,其中void返回值由單個字母V表示。

示例

欄位描述符 欄位型別 方法描述符 方法
S short ()V void run()
Ljava.lang.Object; java.lang.Object ()Ljava.lang.String; String toString()
[I int []  ([Ljava.lang.String;)V void main(String[] args)
[[D double [] [] (FF)I int max(float x,float y)
[Ljava.lang.String java.lang.Object[] ([JJ)I int binarySearch(long[] a,long key)

我們都知道,java語言支援方法過載,不同的方法可以有相同的名字,只要引數列表不同即可,這就是為什麼CONSTANT_NameAndType_info結構為什麼要同時包含名稱和描述符的原因。

package classfile 

//欄位或方法的名稱和描述符 ConstantClassInfo+ConstantNameAndTypeInfo可以唯一確定一個欄位或方法
type ConstantNameAndTypeInfo struct{
	nameIndex        uint16 
    descriptorIndex  uint16
}
//實現ConstantInfo介面
func (self *ConstantNameAndTypeInfo) readInfo(reader *ClassReader){
	self.nameIndex = reader.readUint16()
	self.descriptorIndex = reader.readUint16()
}

cp_member_ref.go

常量引用,Go語言並沒有繼承這個概念,但是可以通過結構體巢狀來模擬

package classfile 
//常量引用,ConstantClassInfo+ConstantNameAndTypeInfo可以唯一確定一個欄位或方法
type ConstantMemberrefInfo struct {
	cp           ConstantPool
	classIndex   uint16
	nameAndTypeIndex uint16
}
//實現ConstantInfo介面
func (self *ConstantMemberrefInfo) readInfo(reader *ClassReader){
	self.classIndex = reader.readUint16()
	self.nameAndTypeIndex = reader.readUint16()
}
func (self *ConstantMemberrefInfo) ClassName() string{
	return self.cp.getClassName(self.classIndex)
}
func (self *ConstantMemberrefInfo) NameAndDescriptor()(string,string){
	return self.cp.getNameAndType(self.nameAndTypeIndex)
}
//定義三個結構體去繼承ConstantMemberrefInfo,go語言沒有繼承這個概念,但可以通過結構體巢狀來模擬

type ConstantFieldrefInfo  struct{ ConstantMemberrefInfo}
type ConstantMethodrefInfo struct{ ConstantMemberrefInfo}
type ConstantInterfaceMethodrefInfo struct{ ConstantMemberrefInfo}

常量池小結

可以把常量分為兩類:字面量和符號引用。字面量包括數字常量和字串常量,符號引用包括類和介面名、欄位和方法資訊等,除了字面量,其它常量都是通過索引直接或間接指向CONSTATN_Utf8_info常量。

屬性表

除了常量池的一些資訊,還有其它資訊並未展示,如位元組碼等,這部分資訊儲存在屬性表中,和常量池類似,各種屬性表達的資訊也各不相同,因此無法用統一的結構來定義。不同之處在於,常量是由java虛擬機器的規範嚴格定義的,共有14種。但屬性是可以擴充套件的,不同的虛擬機器可以定義自己的屬性型別

attribute_info.go

package classfile 

type AttributeInfo interface {
	readInfo(reader *ClassReader)
}
//讀取屬性表
func readAttributes(reader *ClassReader, cp ConstantPool) []AttributeInfo{
	//讀取屬性數量
	attributesCount := reader.readUint16()
	attributes := make([]AttributeInfo,attributesCount)
	for i := range attributes {
		attributes[i] = readAttribute(reader,cp)
	}
	return attributes
}
//讀取單個屬性
func readAttribute(reader *ClassReader,cp ConstantPool) AttributeInfo {
	//讀取屬性名索引
	attrNameIndex := reader.readUint16()
	attrName := cp.getUtf8(attrNameIndex)
	//屬性長度
	attrLen := reader.readUint32()
    attrInfo := newAttributeInfo(attrName,attrLen,cp)
    attrInfo.readInfo(reader)
    return attrInfo
}
//根據屬性名建立不同的例項
func newAttributeInfo(attrName string,attrLen uint32,cp ConstantPool) AttributeInfo{
	switch attrName{
		case "Code" : return &CodeAttribute{cp : cp}
		case "ConstantValue" : return &ConstantValueAttribute{}
		case "Deprecated" : return &DeprecatedAttribute{}
		case "Exceptions" : return &ExceptionsAttribute{}
		case "LineNumberTable" : return &LineNumberTableAttribute{}
		//case "LocalVariableTable": return &LocalVariableTableAttribute{}
		case "SourceFile" : return &SourceFileAttribute{cp : cp}
		case "Synthetic" : return &SyntheticAttribute{}
		default : return &UnparsedAttribute{attrName ,attrLen,nil}
	}
}

java虛擬機器規範預定義了23種屬性,先解析其中的8種

attr_unparsed.go

package classfile 
//暫不支援的屬性
type UnparsedAttribute struct {
	name string 
	length uint32
	info   []byte 
}
//實現AttributeInfo介面
func (self *UnparsedAttribute) readInfo(reader *ClassReader){
	self.info = reader.readBytes(self.length)
}

attr_makers.go

package classfile 

//Deprecated 和 Syntheic 屬性不包含任何資料,僅起標記作用
//Deprecated屬性用於指出類,介面,欄位,方法已經不建議使用,編譯器可以根據這個屬性輸出報錯資訊
//Synatheic 用於標記原始檔中不存在,由編譯器生成的類成員,主要用於支援巢狀類和巢狀介面

//go語言可以通過結構體巢狀模仿繼承功能
type DeprecatedAttribute struct { MarkerAttribute }
type SyntheticAttribute struct{ MarkerAttribute }

type MarkerAttribute struct {}

//實現AttributeInfo介面
func (self *MarkerAttribute) readInfo(reader *ClassReader){
	//nothing 因為這兩個屬性沒有資料
}

attr_source_file.go

package classfile 
//SourceFile是定長屬性,info是存放的檔案索引,所以長度是2個位元組
type SourceFileAttribute struct {
	cp          ConstantPool
	sourceFileIndex     uint16 //檔名索引
}
//實現AttributeInfo介面
func (self *SourceFileAttribute) readInfo(reader *ClassReader){
	self.sourceFileIndex  = reader.readUint16()
}
//返回檔名
func (self *SourceFileAttribute) FileName() string {
	return self.cp.getUtf8(self.sourceFileIndex)
}

attr_constant_value.go

package classfile 
//ConstantValue是定長屬性,只會出現在field_info結構中,用於表示表示式的值
type ConstantValueAttribute struct {
	constantValueIndex      uint16  //常量池索引,具體因欄位型別而定
}
//實現AttributeInfo介面
func (self *ConstantValueAttribute) readInfo(reader *ClassReader){
	self.constantValueIndex = reader.readUint16()
}
//getter
func (self *ConstantValueAttribute) ConstantValueIndex() uint16 {
	return self.constantValueIndex
}

attr_code.go

package classfile 
//Code屬性只存在於method_info中,是不定長屬性

type CodeAttribute struct {
	cp              ConstantPool 
	maxStack        uint16   //運算元棧最大深度
	maxLocals       uint16   //區域性變量表大小
	code            []byte   //位元組碼
    exceptionTable  []*ExceptionTableEntry //異常處理表
    attributes      []AttributeInfo //屬性表
}
//getter
func (self *CodeAttribute) MaxStack() uint {
	return uint(self.maxStack)
}
func (self *CodeAttribute) MaxLocals() uint {
	return uint(self.maxLocals)
}
func (self *CodeAttribute) Code() []byte {
    return self.code
}
func (self *CodeAttribute) ExceptionTable() []*ExceptionTableEntry{
	return self.exceptionTable
}


type ExceptionTableEntry struct {
	startPc        uint16
	endPc          uint16
	handlerPc      uint16
	catchType      uint16
}
//getter
func (self *ExceptionTableEntry) StartPc() uint16 {
	return self.startPc
}
func (self *ExceptionTableEntry) EndPc() uint16 {
	return self.endPc
}
func (self *ExceptionTableEntry) HandlerPc() uint16{
	return self.handlerPc
}
func (self *ExceptionTableEntry) CatchType() uint16{
	return self.catchType
}
//實現AttributeInfo介面
func (self *CodeAttribute) readInfo(reader *ClassReader){
	self.maxStack = reader.readUint16()
	self.maxLocals = reader.readUint16()
	codeLength := reader.readUint32()
	self.code = reader.readBytes(codeLength)
	self.exceptionTable = readExceptionTable(reader)
	self.attributes = readAttributes(reader,self.cp)
}

func readExceptionTable(reader *ClassReader) []*ExceptionTableEntry{
	exceptionTableLength := reader.readUint16()
	exceptionTable := make([]*ExceptionTableEntry,exceptionTableLength)

	for i := range exceptionTable {
		exceptionTable[i] = &ExceptionTableEntry{
			startPc:         reader.readUint16(),
			endPc:           reader.readUint16(),
			handlerPc:       reader.readUint16(),
			catchType:       reader.readUint16(),
		}
	}
	return exceptionTable
}

attr_exceptions.go

package classfile 

//丟擲的異常表

type ExceptionsAttribute struct {
	exceptionIndexTable     []uint16    //裡面存放的是常量池索引,指向異常的class
}

//實現AttributeInfo介面
func (self *ExceptionsAttribute) readInfo(reader *ClassReader){
	self.exceptionIndexTable = reader.readUint16s()
}
//getter
func (self *ExceptionsAttribute) ExceptionIndexTable() []uint16 {
	return self.exceptionIndexTable 
}

attr_line_number_table.go

package classfile 

//存放方法的行號資訊
type LineNumberTableAttribute struct {

		lineNumberTable  []*LineNumberTableEntry
}

type LineNumberTableEntry struct{
	startPc    uint16    //指令
	lineNumber uint16    //指令對應的行號
}
//實現AttributeInfo介面
func (self *LineNumberTableAttribute) readInfo(reader *ClassReader){
	lineNumberTableLength := reader.readUint16()
	self.lineNumberTable = make([]*LineNumberTableEntry,lineNumberTableLength)

	for i := range self.lineNumberTable {
		self.lineNumberTable[i] = &LineNumberTableEntry{
			startPc :           reader.readUint16(),
			lineNumber :        reader.readUint16(),  
		}
	}
}

 

LineNumberTable 屬性表存放方法得行號資訊,LocalVariableTable屬性表中存放方法得區域性變數資訊,這兩種屬性和前面介紹得SourceFile屬性都屬於除錯資訊,都不是執行時必要得,在使用javac編譯器編譯java程式時,預設會在class檔案中生成這些資訊,可以使用javac提供的 -g:none選項來關閉這些資訊的生成。

測試程式碼

修改main.go

package main

import "fmt"
import "strings"
import "jvmgo/classpath"
import "jvmgo/classfile"

func main(){
	//呼叫解析命令列的行數,接受解析結果
    cmd:=parseCmd()
    if cmd.versionFlag{
    	fmt.Println("version 0.0.1")
    }else if cmd.helpFlag||cmd.class==""{
    	printUsage()
    }else{
    	startJVM(cmd)
    }
}
//搜尋class檔案
func startJVM(cmd *Cmd){
	//解析類路徑
	cp := classpath.Parse(cmd.XjreOption,cmd.cpOption)
    //func Replace(s, old, new string, n int) string
    //返回將s中前n個不重疊old子串都替換為new的新字串,如果n<0會替換所有old子串。
	className := strings.Replace(cmd.class,".","/",-1)
	cf := loadClass(className,cp)
	fmt.Println(cmd.class)
	printClassInfo(cf)
}
//解析位元組碼
func loadClass(className string,cp *classpath.Classpath) *classfile.ClassFile {
    classData, _, err := cp.ReadClass(className)
	if err != nil {
		panic(err)
	}
	cf,err := classfile.Parse(classData)
	if err != nil {
		panic(err)
	}
	return cf
}
//列印
func printClassInfo(cf *classfile.ClassFile) {
	fmt.Printf("version: %v.%v\n",cf.MajorVersion(),cf.MinorVersion()) //版本號
	fmt.Printf("ConstantCounts: %v\n",len(cf.ConstantPool())) //常量池數量
	fmt.Printf("access flags:0x%x\n",cf.AccessFlags())//類訪問標誌
	fmt.Printf("this class:%v\n",cf.ClassName())//類名
	fmt.Printf("super class:%v\n",cf.SuperClassName())//父類名
	fmt.Printf("interfaces:%v\n",cf.InterfaceNames())//介面名
	fmt.Printf("fields count:%v\n",len(cf.Fields()))//欄位數量
	for _, f := range cf.Fields() {
		fmt.Printf("  %s\n",f.Name())
	}
	fmt.Printf("methods count:%v\n",len(cf.Methods()))//方法數量
	for _, m := range cf.Methods() {
		fmt.Printf(" %s\n",m.Name())
	}

}

執行結果

參考資料:《自己動手寫java虛擬機器》