用Java实现JVM第三章《解析class文件》

199 阅读10分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

简述

字节码文件

class文件本质上是一个以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在class文件中。jvm根据其特定的规则解析该二进制数据,从而得到相关信息。

Class文件采用一种伪结构来存储数据,它有两种类型:无符号数和表

Class文件的结构属性

类型名称数量
u4magic(魔数)(0xCAFEBABE)1
u2minor_version(次版本号)1
u2major_version(主版本号)1
u2constant_pool_count(常量计数器)1
cp_infoconstant_pool(常量池)constant_pool_count-1
u2access_flags(访问标志)1
u2this_class(类索引)1
u2super_class(父类索引)1
u2interfaces_count(接口计数器)1
u2interfacesinterfaces_count
u2fields_count1
field_infofieldsfields_count
u2methods_count1
method_infomethodsmethods_count
u2attributes_count1
attributes_infoattributesattributes_count

常量池

通用格式

//通用格式
 cp_info {
  u1 tag;
  u1 info[];
 }

tag信息

类型标志(tag 区分类型)描述
CONSTANT_Utf8_info1UTF-8 编码的字符串
CONSTANT_Integer_info3整性字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info5长整型字面量
CONSTANT_Double_info6双精度浮点型字面量
CONSTANT_Class_info7类或则接口的符号引用
CONSTANT_String_info8字符串类型字面量
CONSTANT_Fieldref_info9字段的符号引用
CONSTANT_Methodref_info10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中的方法符号引用
CONSTANT_NameAndType_info12字段或者方法的部分符号引用
CONSTANT_MethodHandle_info15表示方法句柄
CONSTANT_MethodType_info16标识方法类型
CONSTANT_InvokeDynamic_info18表示一个动态方法的调用点

访问标志

标志名称标志值含义
ACC_PUBLIC0x0001是否为public
ACC_FINAL0x0010是否被声明为final,只有类可设置
ACC_SUPER0x0020是否允许使用invokespecial 字节码指令得新语意,invokespecial指令得语意在JDK 1.0.2发生改变,为了区别这条指令使用哪种语意,在JDK1.0.2之后便宜出来的类这个标志是真
ACC_INTERFACE0x0200标识这是一个接口
ACC_ABSTRACT0x0400是否为abstract类型,对于接口或者抽象类来说,此标志值为真,其他类值为假
ACC_SYNTHETIC0x1000标识这个类并非由用户代码产生
ACC_ANNOTATION0x2000标识这是一个注解
ACC_ENUM0x4000标识这是一个枚举

字段表集合

类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count-1

access_flags

标志名称标志值含义
ACC_PUBLIC0x0001字段是否public
ACC_PRIVATE0x0002字段是否private
ACC_PROTECTED0x0004字段是否protected
ACC_STATIC0x0008字段是否static
ACC_FINAL0x0010字段是否final
ACC_VOLATILE0x0040字段是否volatile
ACC_TRABSIENT0x0080字段是否transient
ACC_SYNTHETIC0x1000字段是否由编译器自动产生
ACC_ENUM0x4000字段是否enum

name_index和descriptor_index

它们都是对常量 池的引用,分别代表着字段的简单名称以及字段和方法的描述符。

简单名称是指没有类型和参数修饰的方法或者字段名称,这个类 中的inc()方法和m字段的简单名称分别是“inc”和“m”。

描述符的作用是 用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描 述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表 无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示。

标识字符含义
B基本类型byte
C基本类型char
D基本类型double
F基本类型float
I基本类型int
J基本类型long
S基本类型short
Z基本类型boolean
V特殊类型void
L对象类型,如Ljava/lang/Object

对于数组类型,每一维度将使用一个前置的“[”字符来描述,如一个定义 为“java.lang.String[][]”类型的二维数组,将被记录为:“[[Ljava/lang/String;”,一个整型数 组“int[]”将被记录为“[I”。

属性表集合

属性表通用结构

类型名称数量
u2attribute_name_index1
u4attribute_length1
u1infoattribute_length

虚拟机规范预定义的属性(具体相关属性请查阅相关文档)

属性名称使用位置含义
Code方法表Java代码编译成的字节码指令
ConstantValue字段表由final关键字定义的常量值
Deprecated类、方法表、字段被声明为deprecated的方法和字段
Exceptions方法表方法抛出的异常列表
EnclosingMethod类文件仅当一个类为局部类或者匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法
InnerClass类文件内部类列表
LineNumberTableCode属性Java源码的行号与字节码指令的对应关系
LocalVariableTableCode属性方法的局部变量描述
StackMapTableCode属性JDK1.6中新增的属性,供新的类型检查检验器检查和处理目标方法的局部变量和操作数有所需要的类是否匹配
Signature类、方法表、字段用于支持泛型情况下的方法签名
SourceFile类文件记录源文件名称
SourceDebugExtension类文件用于存储额外的调试信息
Synthetic类、方法表、字段标志方法或字段为编译器自动生成的
LocalVariableTypeTable使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
RuntimeVisibleAnnotations类、方法表、字段为动态注解提供支持
RuntimeInvisibleAnnotations类、方法表、字段用于指明哪些注解是运行时不可见的
RuntimeVisibleParameterAnnotations方法表作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法
RuntimeInvisibleParameterAnnotations方法表作用与RuntimeInvisibleAnnotations属性类似,作用对象哪个为方法参数
AnnotationDefault方法表用于记录注解类元素的默认值
BootstrapMethos类文件用于保存invokeddynamic指令引用的引导方式限定符
RuntimeVisibleTypeAnnotations类、方法表、字段、Code属性指明哪些注解运行时可见的
RuntimeInvisibleTypeAnnotations类、方法表、字段、Code属性指明哪些注解运行时不可见的
MethodParameters方法表用于支持(编译时加上-parameters参数)将方法名称编译进class文件,并在运行中获取
Module用于记录一个Module名称及相关信息
ModulePackages用于记录一个模块中被exports 或则opens的包
ModuleMainClass指定一个模块的指令
NestHost用于支持嵌套类的反射和访问控制API,一个内部类通过该属性得知自己的宿主类
NestMembers用于支持嵌套类的反射和访问控制API,一个宿主类通过该属性得知自己有哪些内部类

代码实现

ClassReader

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;

/**
 * @Author blackcat
 * @create 2021/8/12 8:20
 * @version: 1.0
 * @description:读取class数据
 *
 *  java虚拟机定义了u1、u2、u4三种数据类型来表示;1字节、2字节、4字节,无符号整数。
 */
public class ClassReader {


    private byte[] codes;
    private int pos;

    public ClassReader(byte[] codes) {
        this.codes = codes;
        this.pos = 0;
    }

    //u1
    public int nextU1toInt() {
        return ByteUtil.byteToInt(new byte[] { codes[pos++] });
    }

    //u2
    public int nextU2ToInt() {
        return ByteUtil.byteToInt(new byte[] { codes[pos++], codes[pos++] });
    }

    //u4
    public int nextU4ToInt() {
        return ByteUtil.byteToInt(new byte[] { codes[pos++], codes[pos++], codes[pos++], codes[pos++] });
    }

    //u4 转String
    public String nextU4ToHexString() {
        return ByteUtil.byteToHexString((new byte[] { codes[pos++], codes[pos++], codes[pos++], codes[pos++] }));
    }

    public long next2U4ToLong() {
        byte[] bytes = nextBytes(8);
        return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getLong();
    }

    public double next2U4Double() {
        byte[] bytes = nextBytes(8);
        return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getDouble();
    }

    public float nextU4ToFloat() {
        byte[] bytes = nextBytes(4);
        return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
    }


    public int[] nextUint16s() {
        int count = nextU2ToInt();
        int[] result = new int[count];
        for (int i = 0; i < count; i++) {
            result[i] = nextU2ToInt();
        }
        return result;
    }

    //读取长度为len的数据
    public byte[] nextBytes(int len) {
        if (pos + len >= codes.length) {
            throw new ArrayIndexOutOfBoundsException();
        }

        byte[] data = Arrays.copyOfRange(codes, pos, pos + len);
        pos += len;
        return data;
    }
}

ClassFile

import com.black.cat.jvm.classfile.attribute.AttributeInfo;
import com.black.cat.jvm.classfile.attribute.BaseAttributeInfo;
import com.black.cat.jvm.classfile.constantpool.ConstantPool;
import com.black.cat.jvm.common.CommonValue;
import lombok.Data;

/**
 * @Author blackcat
 * @create 2021/8/12 8:25
 * @version: 1.0
 * @description: class 文件
 *
 *ClassFile {
 * u4 magic; 魔数(0xCAFEBABE)
 * u2 minor_version;次版本号
 * u2 major_version;主版本号
 * u2 constant_pool_count;常量计数器
 * cp_info constant_pool[constant_pool_count-1];常量池
 * u2 access_flags;访问标志
 * u2 this_class;类索引
 * u2 super_class;父类索引
 * u2 interfaces_count;接口计数器
 * u2 interfaces[interfaces_count];接口表
 * u2 fields_count;字段计数器
 * field_info fields[fields_count];字段表
 * u2 methods_count;方法计数器
 * method_info methods[methods_count];方法表
 * u2 attributes_count;属性计数器
 * attribute_info attributes[attributes_count];属性表
 * }
 *
 */
@Data
public class ClassFile {


    private ClassReader reader;

    private String magic;
    private int minorVersion;
    private int majorVersion;
    //    private int constantPoolCount;
    private ConstantPool constantPool;
    private int accessFlag;
    private int classNameIndex;
    private int superClassNameIndex;
    //    private int interfaceCount;
    private int[] interfaceIndexes;
    //    private int fieldCount;
    private MemberInfo[] fieldInfos;
    //    private int methodCount;
    private MemberInfo[] methodInfos;
    //    private int attributeCount;
    private AttributeInfo[] attributeInfos;


    public ClassFile(byte[] bytes) {
        this.reader = new ClassReader(bytes);
        readAndCheckMagic();
        readAndCheckVersion();
        readConstantPool();
        readAccessFlag();
        readClassNameIndex();
        readSuperClassNameIndex();
        readInterfaceIndexes();
        readFields();
        readMethods();
        readAttributes();
    }

    private void readAndCheckMagic() {
        String magic = reader.nextU4ToHexString();
        this.magic = magic;
        if (!"cafebabe".equalsIgnoreCase(magic)) {
            throw new ClassFormatError("magic!");
        }
    }

    private void readAndCheckVersion() {
        this.minorVersion = this.reader.nextU2ToInt();
        this.majorVersion = this.reader.nextU2ToInt();
        // jdk11对应的major为55,支持jdk1.2-jdk11
        if (this.majorVersion >= CommonValue.JDK_VERSION_MAJOR_02
                && this.majorVersion <= CommonValue.JDK_VERSION_MAJOR_11
                && this.minorVersion == CommonValue.JDK_VERSION_MINOR_00) {
            return;
        }
        throw new UnsupportedClassVersionError();
    }

    private void readConstantPool() {
        this.constantPool = new ConstantPool(this.reader);
    }

    private void readAccessFlag() {
        this.accessFlag = this.reader.nextU2ToInt();
    }

    private void readClassNameIndex() {
        this.classNameIndex = this.reader.nextU2ToInt();
    }

    private void readSuperClassNameIndex() {
        this.superClassNameIndex = this.reader.nextU2ToInt();
    }

    private void readInterfaceIndexes() {
        this.interfaceIndexes = reader.nextUint16s();
    }


    private void readFields() {
        fieldInfos = MemberInfo.readMembers(constantPool, reader);
    }

    private void readMethods() {
        methodInfos = MemberInfo.readMembers(constantPool, reader);
    }

    private void readAttributes() {
        this.attributeInfos = BaseAttributeInfo.readAttributes(reader, constantPool);
    }
}

剩下请参考

gitee.com/feicc/jjvm/…

测试

public class Main {

    public static void main(String[] args) {
        String[] argv = {"-classpath", "D:\\develop\\code\\jjvm\\jvm-03\\target\\classes", "com.black.cat.jvm.MainTest"};
        Cmd cmd = Cmd.parse(argv);
        if (!cmd.ok || cmd.helpFlag) {
            System.out.println("Usage: <main class> [-options] class [args...]");
            return;
        }
        if (cmd.versionFlag) {
            System.out.println("java version \"1.8.0\"");
            return;
        }
        startJVM(cmd);
    }

    private static void startJVM(Cmd cmd) {
        System.out.printf("classpath:%s class:%s args:%s\n", cmd.classpath, cmd.getMainClass(), cmd.getAppArgs());

        Classpath classpath = new Classpath(null, cmd.classpath);

        String className = cmd.getMainClass().replace(".", "/");

        ClassFile classFile = loadClass(className, classpath);
        assert classFile != null;
        printClassInfo(classFile);
    }

    private static ClassFile loadClass(String className, Classpath classpath) {
        try {
            byte[] classData = classpath.readClass(className);
            return new ClassFile(classData);
        } catch (Exception e) {
            System.out.println("Could not find or load main class " + className);
            return null;
        }
    }

    //使用 javap -verbose  MainTest查看所有常量池进行比对
    private static void printClassInfo(ClassFile cf) {
        System.out.println("version: " + cf.getMajorVersion() + "." + cf.getMinorVersion());
        System.out.println("constants count:" + cf.getConstantPool().getSize());
        for (int i = 1; i < cf.getConstantPool().getSize(); i++) {
            System.out.println("constants" + i + ":" + cf.getConstantPool().getConstantInfos()[i]);
        }

        System.out.format("access flags:0x%x\n", cf.getAccessFlag());
        System.out.println("this classindex:" + cf.getClassNameIndex());
        System.out.println("this class:" + cf.getConstantPool().getUtfStringByIndex(cf.getClassNameIndex()));
        System.out.println("super classindex:" + cf.getSuperClassNameIndex());
        System.out.println("super class:" + cf.getConstantPool().getUtfStringByIndex(cf.getSuperClassNameIndex()));
        System.out.println("interfaceCount:" + cf.getInterfaceIndexes().length);

        for (int interfaceIndex : cf.getInterfaceIndexes()) {
            System.out.println("interfaces:" + cf.getConstantPool().getUtfStringByIndex(interfaceIndex));
        }

        System.out.println("fields count:" + cf.getFieldInfos().length);
        for (MemberInfo memberInfo : cf.getFieldInfos()) {
            System.out.format("  %s\n", memberInfo.toString());
        }
        System.out.println("methods count: " + cf.getMethodInfos().length);
        for (MemberInfo memberInfo : cf.getMethodInfos()) {
            System.out.format("  %s\n", memberInfo.toString());
        }

        System.out.println("attribute count: " + cf.getAttributeInfos().length);
        for (AttributeInfo attributeInfo : cf.getAttributeInfos()) {
            System.out.format("  %s\n", attributeInfo.toString());
        }
    }
}


public class MainTest {

    public static final boolean FLAG = true;
    public static final byte BYTE = 123;
    public static final char X = 'X';
    public static final short SHORT = 12345;
    public static final int INT = 123456789;
    public static final long LONG = 12345678901L;
    public static final float PI = 3.14f;
    public static final double E = 2.71828;

    public static void main(String[] args) throws IOException {
        System.out.println("Hello World");
        main2(args);

        int sum = 0;
        for (int i = 1; i <= 10; i++) {
            sum += i;
        }
        System.out.println(sum);
    }

    public static float main2(String[] args) {
        float pi = 3.14F;
        float r = 5.4F;
        float area = 2 * pi * r;
        return area;
    }
}


结果

可使用 javap -verbose  MainTest查看所有常量池进行比对
version: 52.0
constants count:78
constants1:10--------------java/lang/Object.<init>:()V
constants2:9--------------java/lang/System.out:Ljava/io/PrintStream;
constants3:8--------------Hello World
constants4:10--------------java/io/PrintStream.println:(Ljava/lang/String;)V
constants5:10--------------com/black/cat/jvm/MainTest.main2:([Ljava/lang/String;)F
constants6:10--------------java/io/PrintStream.println:(I)V
constants7:4--------------3.14
constants8:4--------------5.4
constants9:7--------------com/black/cat/jvm/MainTest
constants10:7--------------java/lang/Object
constants11:1--------------FLAG
constants12:1--------------Z
constants13:1--------------ConstantValue
constants14:3--------------1
constants15:1--------------BYTE
constants16:1--------------B
constants17:3--------------123
constants18:1--------------X
constants19:1--------------C
constants20:3--------------88
constants21:1--------------SHORT
constants22:1--------------S
constants23:3--------------12345
constants24:1--------------INT
constants25:1--------------I
constants26:3--------------123456789
constants27:1--------------LONG
constants28:1--------------J
constants29:5--------------12345678901
constants30:null
constants31:1--------------PI
constants32:1--------------F
constants33:1--------------E
constants34:1--------------D
constants35:6--------------2.71828
constants36:null
constants37:1--------------<init>
constants38:1--------------()V
constants39:1--------------Code
constants40:1--------------LineNumberTable
constants41:1--------------LocalVariableTable
constants42:1--------------this
constants43:1--------------Lcom/black/cat/jvm/MainTest;
constants44:1--------------main
constants45:1--------------([Ljava/lang/String;)V
constants46:1--------------i
constants47:1--------------args
constants48:1--------------[Ljava/lang/String;
constants49:1--------------sum
constants50:1--------------StackMapTable
constants51:1--------------Exceptions
constants52:7--------------java/io/IOException
constants53:1--------------main2
constants54:1--------------([Ljava/lang/String;)F
constants55:1--------------pi
constants56:1--------------r
constants57:1--------------area
constants58:1--------------SourceFile
constants59:1--------------MainTest.java
constants60:12--------------<init>:()V
constants61:7--------------java/lang/System
constants62:12--------------out:Ljava/io/PrintStream;
constants63:1--------------Hello World
constants64:7--------------java/io/PrintStream
constants65:12--------------println:(Ljava/lang/String;)V
constants66:12--------------main2:([Ljava/lang/String;)F
constants67:12--------------println:(I)V
constants68:1--------------com/black/cat/jvm/MainTest
constants69:1--------------java/lang/Object
constants70:1--------------java/io/IOException
constants71:1--------------java/lang/System
constants72:1--------------out
constants73:1--------------Ljava/io/PrintStream;
constants74:1--------------java/io/PrintStream
constants75:1--------------println
constants76:1--------------(Ljava/lang/String;)V
constants77:1--------------(I)V
access flags:0x21
this classindex:9
this class:com/black/cat/jvm/MainTest
super classindex:10
super class:java/lang/Object
interfaceCount:0
fields count:8
  MemberInfo{accessFlags=25, nameIndex=11, descriptorIndex=12, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=14}]}

  MemberInfo{accessFlags=25, nameIndex=15, descriptorIndex=16, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=17}]}

  MemberInfo{accessFlags=25, nameIndex=18, descriptorIndex=19, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=20}]}

  MemberInfo{accessFlags=25, nameIndex=21, descriptorIndex=22, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=23}]}

  MemberInfo{accessFlags=25, nameIndex=24, descriptorIndex=25, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=26}]}

  MemberInfo{accessFlags=25, nameIndex=27, descriptorIndex=28, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=29}]}

  MemberInfo{accessFlags=25, nameIndex=31, descriptorIndex=32, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=7}]}

  MemberInfo{accessFlags=25, nameIndex=33, descriptorIndex=34, attributes=[ConstantValueAttribute{attributeLength=2constantValueIndex=35}]}

methods count: 3
  MemberInfo{accessFlags=1, nameIndex=37, descriptorIndex=38, attributes=[CodeAttribute{attributeLength=47maxStack=1, maxLocals=1, code=[42, -73, 0, 1, -79], attributes=[LineNumberTableAttribute{lineNumberTable=[LineNumberTableEntry{,startPc=0, lineNumber=11}]}, LocalVariableTableAttribute{localVariableTable=[LineNumberTableEntry{,startPc=0, length=5,nameIndex=42, descriptorIndex=43, index=0}]}], exceptionTable=[]}]}

  MemberInfo{accessFlags=9, nameIndex=44, descriptorIndex=45, attributes=[CodeAttribute{attributeLength=147maxStack=2, maxLocals=3, code=[-78, 0, 2, 18, 3, -74, 0, 4, 42, -72, 0, 5, 87, 3, 60, 4, 61, 28, 16, 10, -93, 0, 13, 27, 28, 96, 60, -124, 2, 1, -89, -1, -13, -78, 0, 2, 27, -74, 0, 6, -79], attributes=[LineNumberTableAttribute{lineNumberTable=[LineNumberTableEntry{,startPc=0, lineNumber=23}, LineNumberTableEntry{,startPc=8, lineNumber=24}, LineNumberTableEntry{,startPc=13, lineNumber=26}, LineNumberTableEntry{,startPc=15, lineNumber=27}, LineNumberTableEntry{,startPc=23, lineNumber=28}, LineNumberTableEntry{,startPc=27, lineNumber=27}, LineNumberTableEntry{,startPc=33, lineNumber=30}, LineNumberTableEntry{,startPc=40, lineNumber=31}]}, LocalVariableTableAttribute{localVariableTable=[LineNumberTableEntry{,startPc=17, length=16,nameIndex=46, descriptorIndex=25, index=2}, LineNumberTableEntry{,startPc=0, length=41,nameIndex=47, descriptorIndex=48, index=0}, LineNumberTableEntry{,startPc=15, length=26,nameIndex=49, descriptorIndex=25, index=1}]}, com.black.cat.jvm.classfile.attribute.UnparsedAttribute@1dd02175], exceptionTable=[]}, com.black.cat.jvm.classfile.attribute.ExceptionsAttribute@31206beb]}

  MemberInfo{accessFlags=9, nameIndex=53, descriptorIndex=54, attributes=[CodeAttribute{attributeLength=98maxStack=2, maxLocals=4, code=[18, 7, 68, 18, 8, 69, 13, 35, 106, 36, 106, 70, 37, -82], attributes=[LineNumberTableAttribute{lineNumberTable=[LineNumberTableEntry{,startPc=0, lineNumber=34}, LineNumberTableEntry{,startPc=3, lineNumber=35}, LineNumberTableEntry{,startPc=6, lineNumber=36}, LineNumberTableEntry{,startPc=12, lineNumber=37}]}, LocalVariableTableAttribute{localVariableTable=[LineNumberTableEntry{,startPc=0, length=14,nameIndex=47, descriptorIndex=48, index=0}, LineNumberTableEntry{,startPc=3, length=11,nameIndex=55, descriptorIndex=32, index=1}, LineNumberTableEntry{,startPc=6, length=8,nameIndex=56, descriptorIndex=32, index=2}, LineNumberTableEntry{,startPc=12, length=2,nameIndex=57, descriptorIndex=32, index=3}]}], exceptionTable=[]}]}

attribute count: 1
  SourceFileAttribute{attributeLength=2sourceFile=MainTest.java}