Go to realize java virtual machine 03

Time:2021-4-19

In the last article, we have read the bytecode file we need according to the path java.lang.Object Taking this class as an example, you can see something like the following. What are these numbers?

 

To understand this, we can probably guess that it’s decimal. Let’s turn decimal to hexadecimal online https://tool.oschina.net/hexconvert/ Note that each number has been separated by a space in the figure above. Let’s turn the front one into hexadecimal to see the effect. 202 corresponds to Ca, 254 corresponds to Fe, 186 corresponds to Ba, 190 corresponds to be. All in all, it’s cafebabe, If you are interested, you can check the coffee represented by this. All bytecode files conforming to the JVM specification begin with this, which is called magic number;

I don’t know if you have found that if we need to convert one by one when we analyze this, it’s too bad. But there are many tools that can help us better look at hexadecimal, such as vscode, EDITPLUS, WinHex, jclasslib (we can’t see hexadecimal, but we can see the structure of bytecode file), If you really don’t want to download other things, you can also see hexadecimal with vim; Here we strongly recommend a tool called classpy, which can look at the structure of hexadecimal and class bytecode files at the same time and is very comfortable to use;

Link: https://pan.baidu.com/s/1s_fqLxQjG0lVXMEB5z1mlg    Extraction code: gmyt   When using this classpy, but there is a premise, your computer must have gradle environment! ! ! First, unzip it, then enter the classpy master folder and run gradle from the command line  uberjar And finally, gradle  run  And then use gradle every time  run Just do it! After opening the UI interface, just drop the class manually, as shown in the figure below. The structure of the class file is on the left, and the corresponding hexadecimal is on the right;

 

1. Talk about the class file structure

First of all, let’s talk about the structure of the class bytecode file and see which parts it is composed of. In fact, it has been explained on the left side of the figure above. The figure below is more clear: U2 represents two bytes, U4 represents four bytes, and other parts such as CP_ Info represents a table, and then each field in the table corresponds to a table (this is not easy to understand. If you have seen multi-dimensional arrays, it’s better to regard the table as an array, but not many arrays. Each position corresponds to this array, which is called multi-dimensional array);

As for the meaning of the following, here   Let’s not go into details. Let’s look at the composition of bytecode files by ourselves, which is not our focus;

 

There is an interesting phenomenon in the structure here, that is, before listing the data, it will indicate how many bytes the data has in advance; for example, constant_ pool_ Count indicates that there are n tables in the constant pool, occupying 2 bytes, and the constant immediately after it_ pool[constant_ pool_ Count-1] stores the actual data of each table. Since the first byte of each table represents the type of the table, and then the size of the table is specified, the total number of bytes can be determined_ Flags represents access rights, two bytes, etc

Next, let’s talk about the types of tables in the constant pool and the structure of each table (each table identifies its own byte size). As shown below, each table has its own unique structure. It should also be noted that in so many tables below, an item in one table may refer to the data of another table;

 

 

What does each part outside the constant pool represent? I casually found a blog, referring to the more detailed description in this article: https://www.jianshu.com/p/247e2475fc3a That’s not much to say, and that’s not our point;

 

2. Read the class bytecode file

The general directory structure is as follows:

According to the figure above, we will divide the file in the classfile into several parts. The first is class_ reader.go This file is a structure body, which stores all the byte slices of the class file data, and defines some methods to read 1 byte, 2 bytes, 4 bytes and 8 bytes at a time, which is convenient for us to read data;

Then class_ file.go A structure in the file stores all the structures in the bytecode, such as magic number, version number, constant pool, access modifier, etc., and then defines some methods to obtain these parts. It can be imagined that these methods need to use the class mentioned above_ reader.go The structure in the file reads the data;

And then the key is class_ file.go The methods defined in the file to get each part are shown in the figure below, and the most important one is readingConstant poolandProperty sheet

 

When it comes to reading constant pool data, because there are many different types of tables in the constant pool, we define an interface, and all tables must implement this interface. As for the total types of tables, there are roughly two types, one is character type, the other is reference type. Character type is divided into string type and number type, which are in the above CP_ Utf8.go and CP_ numberic.go In, other tables starting with CP are reference type tables;

When reading a table in the constant pool, we first need to determine the type of the table being read. When reading the first byte, the byte indicates what type it represents, as shown below. Then, each table specifies the byte structure, which has been explained above;

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
)

 

 

Then there is the property table. In fact, it defines a top-level interface similar to the constant pool, except that the property table does not use this number to determine the type of the table, but uses the property name (that is, string) to distinguish it. So we can see the following structure. We can find the constant of the constant pool by reading the first two bytes of the property table_ Index of utf8 table, then get the string, and then go to the switch below to determine what type of attribute table it is;

 

There are also many types of property sheets. Here we just list eight of them. As for the meaning of each, take a look at this blog: https://www.cnblogs.com/lrh-xl/p/5351182.html In the directory above, attr_ The beginning of XXX is property sheet,

 

3. Documents

class_ reader.go : used to help us read data in byte slices:

package classfile

import "encoding/binary"

//This structure reads data from a byte array
type ClassReader struct {
    data []byte
}

//Read a byte and kill the first byte of data
func (this *ClassReader) readUint8() uint8 { //u1
    val := this.data[0]
    this.data = this.data[1:]
    return val
}

//Read two bytes
func (this *ClassReader) readUint16() uint16 { //u2
    val := binary.BigEndian.Uint16(this.data)
    this.data = this.data[2:]
    return val

}

//Read four bytes
func (this *ClassReader) readUint32() uint32 { //u4
    val := binary.BigEndian.Uint32(this.data)
    this.data = this.data[4:]
    return val

}

//Read 8 bytes
func (this *ClassReader) readUint64() uint64 {
    val := binary.BigEndian.Uint64(this.data)
    this.data = this.data[8:]
    return val

}

//Read the first two bytes, indicating the number
//Continue to read n uint16 bytes according to this number
func (this *ClassReader) readUint16s() []uint16 {
    n := this.readUint16()
    s := make([]uint16, n)
    for i := range s {
        s[i] = this.readUint16()
    }
    return s
}

//Gets the specified number of bytes
func (this *ClassReader) readBytes(length uint32) []byte {
    bytes := this.data[:length]
    this.data = this.data[length:]
    return bytes

}

View Code

 

class_ file.go : defines the structure of the bytecode file

package classfile

import "fmt"

//This structure represents the content of the class file
type ClassFile struct {
    Magic uint32 // magic number U4
    Minorversion uint16 // minor version number U2
    Majorversion uint16 // major version U2
    Constantpool constantpool // constant pool
    Accessflags uint16 // modifier
    Thisclass uint16 // current class
    Superclass uint16 // parent class
    Interfaces [] uint16 // interface, an array of interfaces
    Fields [] * memberinfo // fields
    Methods [] * memberinfo // method
    Attributes [] attributeinfo // attributes, such as the full class name, are stored here
}

//This method is to parse the byte array into the fileclass structure
func Parse(classData []byte) (cf *ClassFile, err error) {
    //The defer and recover modes are similar to finally in Java. Here, you can handle an exception without fire
    defer func() {
        if r := recover(); r != nil {
            var ok bool
            err, ok = r.(error)
            if !ok {
                err = fmt.Errorf("%v", r)
            }

        }
    }()
    //Instantiate a classfile instance to save the information of each part of the bytecode
    cf = &ClassFile{}
    //Instantiate a class file parser and pass in an array containing all the information of the bytecode file
    cr := &ClassReader{classData}
    //The read method starts to parse the data of each part of the class file
    cf.read(cr)
    return
}

//This method is in accordance with the order of the parts of the bytecode file to read
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)
}

//Get magic number, magic number occupies 4 bytes
func (this *ClassFile) readAndCheckMagic(reader *ClassReader) {
    magic := reader.readUint32()
    //Note that all magic numbers that conform to the JVM specification are cafebabe, and those that do not conform to the conditions are directly terminated by panics
    if magic != 0xCAFEBABE {
        panic("java.lang.ClassFormatError:magic")
    }
}

//The minor version number and the major version number are two bytes
//The minor version number has not been used since jdk1.2, which is 0
//The major version number is 45 from 1.2. Every time it passes a large version, it will be + 1. Now it is 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")
}

//Get major version number
func (this *ClassFile) MinorVersion() uint16 {
    return this.minorVersion
}

//Get sub version number
func (this *ClassFile) MajorVersion() uint16 {
    return this.majorVersion
}

//Get constant pool
func (this *ClassFile) ConstantPool() ConstantPool {
    return this.constantPool
}

//Get modifier
func (this *ClassFile) AccessFlags() uint16 {
    return this.accessFlags
}

//Gets the class name from the constant pool
func (this *ClassFile) ClassName() string {
    return this.constantPool.getClassName(this.thisClass)
}

//Get the superclass name from the constant pool. Note that you need to determine whether it is an object class
func (this *ClassFile) SuperClassName() string {
    if this.superClass > 0 {
        return this.constantPool.getClassName(this.superClass)
    }
    Return "// if the class is object, then self.superClass Is 0
}

//Get field
func (this *ClassFile) Fields() []*MemberInfo {
    return this.fields
}

//Acquisition method
func (this *ClassFile) Methods() []*MemberInfo {
    return this.methods
}

//Find all interface names implemented from the constant pool
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
}

View Code

  

constant_ pool.go : some methods are defined to help us get various tables according to the index

package classfile

//This interface represents each table in the constant pool
type ConstantInfo interface {
    readInfo(reader *ClassReader)
}

//A constant pool is actually a slice of all types of tables
type ConstantPool []ConstantInfo

//It is used to read the tables in the constant pool. After parsing each table in the constant pool, it is put into this slice, and then the table data can be obtained according to the index
//The first two bytes are the number of tables in the constant pool cpcount, followed by the actual data of various tables. The first field in each table indicates what type of table it is,
//And then you've set your own byte size
//Note the two tables, constantlonginfo and constantdoubleinfo. This representation occupies two places, and other types occupy one place
//Therefore, the actual number of tables in the constant pool must be less than cpcount
func readConstantPool(reader *ClassReader) ConstantPool {
    cpCount := int(reader.readUint16())
    cp := make([]ConstantInfo, cpCount)
    //Note that constant pool traversal starts at 1, and 0 means that it does not point to any constant pool data
    for i := 1; i < cpCount; i++ {
        cp[i] = readConstantInfo(reader, cp)
        switch cp[i].(type) {
        Case * constantlong, * constantdouble: // if these two types of tables are used, they occupy two places in the constant pool
            i++
        }
    }
    return cp

}

//Gets the table in the constant pool according to the index value
func (this ConstantPool) getConstantInfo(index uint16) ConstantInfo {
    if cpInfo := this[index]; cpInfo != nil {
        return cpInfo
    }
    panic("Invalid constant pool index!")

}

//Get a constant name and type info table from the constant pool according to the index, and then get the name and description of the table
//Note that the name and description correspond to the table in the constant pool, respectively
func (this ConstantPool) getNameAndType(index uint16) (string, string) {
    //An assertion is made here, because nil is not received here, so if it fails, it will be panic directly
    ntInfo := this.getConstantInfo(index).(*ConstantNameAndTypeInfo)
    name := this.getUtf8(ntInfo.nameIndex)
    _type := this.getUtf8(ntInfo.descriptorIndex)
    return name, _type
}

//Get the constant classInfo table in the constant pool according to the index, and get the name of the table
//This name corresponds to a constant utf8info table in the constant pool
func (this ConstantPool) getClassName(index uint16) string {
    classInfo := this.getConstantInfo(index).(*ConstantClassInfo)
    return this.getUtf8(classInfo.nameIndex)
}

//Get the constant utf8info table in the constant pool according to the index, and get the values saved in it
func (this ConstantPool) getUtf8(index uint16) string {
    utf8Info := this.getConstantInfo(index).(*ConstantUtf8Info)
    return utf8Info.str

}

//Read a table in the constant pool. Note that no matter what the table is, its first byte tag indicates the type of the table
// we first get the type of the table, then instantiate the corresponding table, and finally call the readInfo method implemented by the table to read the table data.
func readConstantInfo(reader *ClassReader, constantPool ConstantPool) ConstantInfo {
    tag := reader.readUint8()
    info := newConstantInfo(tag, constantPool)
    info.readInfo(reader)
    return info
}

//Here are all the types in the constant pool. The last three of them are annotated because they were added in JDK7,
//In order to support the new invokedynamic instruction
//And from the following, we roughly divide constant pools into two categories: literal quantity and symbolic reference;
//Literal quantity: string constant and numeric constant
//Symbolic reference: class name, interface name, field and method information. Why is it called symbolic reference? Because there is no actual data in these tables,
//All of them are indexes to the constant utf8info table in the constant pool
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!")
    }

}

View Code

 

constant_ info.go : the type of the table in the constant pool

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
)

View Code

  

  cp_utf8.go:

package classfile

type ConstantUtf8Info struct {
    str string
}

//CONSTANT_Utf8_info {
//u1 tag;
//u2 length;
//u1 bytes[length];
//}
//Note that in this table, the first byte represents the type of the table, and then the two bytes represent the length of the string stored in the table
//Finally, according to this length to read the third part of the data, return the byte slice, we simply converted to a string
func (this *ConstantUtf8Info) readInfo(reader *ClassReader) {
    length := uint32(reader.readUint16())
    bytes := reader.readBytes(length)
    this.str = string(bytes)
}

View Code

 

  cp_numberic.go:

package classfile

import (
    "math"
)

//This file contains four tables related to numbers
//First table
type ConstantIntegerInfo struct {
    val int32
}

//The constantinfo interface is implemented. The first byte of the table represents the type, and the last four bytes represent the stored data
//CONSTANT_Integer_info {
//u1 tag;
//u4 bytes;
//}
func (this *ConstantIntegerInfo) readInfo(reader *ClassReader) {
    readUint32 := reader.readUint32()
    this.val = int32(readUint32)
}

//The second table
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)
}

//The third table
type ConstantFloatInfo struct {
    val float32
}

//CONSTANT_Float_info {
//u1 tag;
//u4 bytes;
//}
func (this *ConstantFloatInfo) readInfo(reader *ClassReader) {
    readUint32 := reader.readUint32()
    //Convert uint32 to float32
    this.val = math.Float32frombits(readUint32)
}

//The fourth table
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)
}

View Code

   

  cp_string.go

package classfile

//CONSTANT_String_info {
//u1 tag;
//u2 string_index;
//}
//There is no data stored in the table. The first byte represents the type of the table, and the next two bytes represent the index
//This index refers to the constant utf8info table in the constant pool
type ConstantStringInfo struct {
    pool        ConstantPool
    stringIndex uint16
}

func (this *ConstantStringInfo) readInfo(reader *ClassReader) {
    this.stringIndex = reader.readUint16()
}

//Get the string corresponding to constantstringinfo
//Find the corresponding constant utf8info table in the constant pool according to the index
func (this *ConstantStringInfo) String() string {
    return this.pool.getUtf8(this.stringIndex)
}

View Code

  

  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()
}

View Code

   

  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
}

View Code

   

  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)
}

View Code

 

member_ info.go : the field tables of the method table are all the same, but there are some differences in the property tables, so it can be represented by the following structure:

package classfile

//field_info {
//u2 access_flags;
//u2 name_index;
//u2 descriptor_index;
//u2 attributes_count;
//attribute_info attributes[attributes_count];
//}
//The structure of the field table and the method table are almost the same, except that the property table is different, which is represented by this structure
type MemberInfo struct {
    constPool       ConstantPool
    Accessflags uint16 // access modifiers
    Nameindex uint16 // field name
    Descriptorindex uint16 // the type of the field
    Attributes [] attributeinfo // attribute table slice
}

//func (self *MemberInfo) AccessFlags() uint16 {...} // getter
//func (self *MemberInfo) Name() string {...}
//func (self *MemberInfo) Descriptor() string {...}

//Because there may be more than one field or method, it is read by traversal
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),
    }
}

//Get the literal quantity of the field or method stored in the constant utf8info table in the constant pool according to the index
func (this *MemberInfo) Name() string {
    return this.constPool.getUtf8(this.nameIndex)
}

//Get the description of the field or method in the constantnameandtypeinfo table in the constant pool according to the index
func (this *MemberInfo) Descriptor() string {
    return this.constPool.getUtf8(this.descriptorIndex)
}

View Code

   

The following contents are related to the property sheet (including eight property sheets)

attribute_ info.go : The top-level interface corresponding to the property sheet. All property sheets must implement this interface

package classfile

//attribute_info {
//u2 attribute_name_index;
//u4 attribute_length;
//u1 info[attribute_length];
//}
//The attributes expressed by each attribute table are different, so the types of tables in constant pool can not be distinguished by tag
//Here we use the property name to distinguish
//In addition, there is no actual data in the attribute table, but the index to the constant utf8info table in the constant pool
type AttributeInfo interface {
    readInfo(reader *ClassReader)
}

//Here, get the number of property sheets in the class file, and read the property sheets from the constant pool according to the number of property sheets
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
}

//How to read the property sheet? First read the first two bytes to represent the index of the property name
//According to this index, get the data in the constant utf8info table from the constant pool to get the attribute name
//Then read 4 bytes to indicate the length of the attribute table. According to the attribute name and length, read the data of various attribute tables and save them to each structure
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}
    }
}

View Code

 

  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)
}

View Code

  

  attr_source_file.go

package classfile

//SourceFile_attribute {
//u2 attribute_name_index;
//u4 attribute_length;
//u2 sourcefile_index;
//}
//This attribute table indicates the source file name, where attribute_ Length; must be 2, and the other two are constant pool indexes
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)
}

View Code

 

  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 The attribute table uses @ retired annotation in Java class to identify that the class is abandoned
//Synthetic_ The attribute table identifies the classes generated by the java compiler itself
//Since these two attribute tables are only used for identification, attribute_ Length is 0
//As a result, nothing is done in the readinfo method below
type DeprecatedAttribute struct {
    MarkerAttribute
}

type SyntheticAttribute struct {
    MarkerAttribute
}

type MarkerAttribute struct {
}

func (this *MarkerAttribute) readInfo(reader *ClassReader) {

}

View Code

 

  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
}

View Code

 

  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
}

View Code

 

  attr_constant_value.go

package classfile

//ConstantValue_attribute {
//u2 attribute_name_index;
//u4 attribute_length;
//u2 constantvalue_index;
//}
//Used to represent the value of a constant expression, where attribute_ Length is determined as 2,
type ConstantValueAttribute struct {
    constantvalueIndex uint16
}

func (this *ConstantValueAttribute) readInfo(reader *ClassReader) {
    this.constantvalueIndex = reader.readUint16()
}

func (this *ConstantValueAttribute) ConstantvalueIndex() uint16 {
    return this.constantvalueIndex
}

View Code

 

  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];
//}
//This property table stores information about methods in bytecode, such as Max? U stack Represents the maximum depth of the stack of operands Represents the size of the local variable table
//Then there are exception handling tables and property tables
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
}

View Code

 

  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
}

View Code

 

4. Modification main.go

The above file basically reads all the bytes in the class bytecode file, and then saves them in the classfile structure. After a simple modification of the startjvm function, the logic is still very clear;

package main

import (
    "firstGoPrj0114/jvmgo/ch03/classfile"
    "firstGoPrj0114/jvmgo/ch03/classpath"
    "fmt"
    "strings"
)

//Command line input \\\\\\\\\ 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)
    }
}

//Find any class in JDK
func startJVM(cmd *Cmd) {
    //If you pass in the full path and class name of JRE in JDK, you will find the corresponding class in lib or lib / ext
    //Command line input \\\\\\\\\ java.lang.Object
    cp := classpath.Parse(cmd.XjreOption, cmd.cpOption)
    fmt.Printf("classpath:%v class:%v args:%v\n", cp, cmd.class, cmd.args)
    //Change. In the whole class name to /, and read the class file in the form of directory
    className := strings.Replace(cmd.class, ".", "/", -1)
    //Load class
    classFile := loadClass(className, cp)
    //Simply print out the information in the class
    printClassInfo(classFile)
}

//You can see that the method to load a class is very easy, that is, to put the read class bytecode array into the parse function,
//Save the parsed data in the classfile structure, which contains magic number, version number, constant pool and other information
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
}

//Simply print out the information in the classfile structure
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. Testing

Or use the above method to generate CH03. Exe executable file, test the official Object.class Whether the method can print out information:

 

Then test the first article we wrote in the jar package HelloWorld.class Bytecode file:

Recommended Today

Envoy announced alpha version of native support for windows

Author: sunjay Bhatia Since 2016, porting envoy to the windows platform has been an important part of the projectOne of the goalsToday, we are excited to announce the alpha version of envoy’s windows native support. The contributor community has been working hard to bring the rich features of envoy to windows, which is another step […]