《自己動手寫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虛擬機器規範定義了一種簡單的語法來描述欄位和方法,可以根據下面的規則生成描述符
- 基本型別byte,short,char,int,long,float和double的描述符是單個字母,分別對應B,S,CI,J,F和D,注意long的描述符是J而不是L。
- 引用型別的描述符是L+類的完全限定名+分號。
- 陣列型別的描述符是[ +陣列元素型別描述符。
- 欄位描述符就是欄位型別描述符
- 方法描述符是(分號分隔的引數型別描述符)+返回值型別描述符,其中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虛擬機器》