《自己动手写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虚拟机》

猜你喜欢

转载自blog.csdn.net/qq_33543634/article/details/83744442