项目地址: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虚拟机》