上一篇我们已经根据路径读取到了我们需要的字节码文件,就以java.lang.object这个类为例,可以看到类似下面这种东西,那么这些数字是什么呢?
要了解这个,我们大概可以猜到这是十进制的,在线将十进制转为十六进制看看,注意上图中已经用空格隔开了每个数,我们将最前面的变成十六进制看看效果,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表示访问权限,占两个字节,等等
接下来说说常量池中表的类型以及每个表的结构(每一种表都标识了自己占用的字节大小),如下所示,每一种表都有自己特有的结构,还要注意一点,下面这么多表中,某一个表中某一项可能会引用另外一张表的数据的;
常量池之外每个部分表示的什么,我随便找了一篇博客,参考这篇说的比较仔细的:;这就不多说了,这也不是我们的重点;
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种,至于每一种是什么意思,看看这个博客:,在上面的目录中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 = this.data[1:] return val } //读取两个字节 func (this *classreader) readuint16() uint16 { //u2 val := binary.bigendian.uint16(this.data) this.data = this.data[2:] return val } //读取四个字节 func (this *classreader) readuint32() uint32 { //u4 val := binary.bigendian.uint32(this.data) this.data = this.data[4:] return val } //读取8个字节 func (this *classreader) readuint64() uint64 { val := binary.bigendian.uint64(this.data) this.data = 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 = this.data[length:] return bytes }
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 }
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!") } }
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 )
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) }
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) }
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) }
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() }
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 }
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) }
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) }
再下面的都是属性表相关的内容(包括八个属性表):
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} } }
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) }
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) }
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) { }
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 }
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 }
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 }
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 }
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 }
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字节码文件:
您可能感兴趣的文章:
如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!
网友评论