jvm-4 class

ddatsh

dev #java #jvm

Class 文件结构

类似于 C 结构体描述,统一用无符号整数作为基本数据类型

u1、u2、u4、u8 表示无符号单字节、2 、4 、8 字节,字符串用 u1 数组进行表示

ClassFile {
    u4 magic;
    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];
}

Magic Number

固定为 0xCAFEBABE

文件版本

major version jdk
51 1.7
52 1.8

常量池

Class 文件中内容最为丰富的区域之一,是整个 Class 文件的基石

对于 Class 文件中的字段和方法解析也有着至关重要的作用

常量池 0 保留项,不存实际内容


不同类型的常量项的内容内容结构各不相同,一般都是以 “类型-长度-内容” 或者 “类型-内容” 的格式依次排列

常量池类型 Tag 常量池类型 Tag
Utf8 1 Fieldref 9
Integer 3 Methodref 10
Float 4 InterfaceMethodref 11
Long 5 NameAndType 12
Double 6 MethodHandle 15
Class 7 MethodType 16
String 8 InvokeDynamic 18

常量池的描述方式总结一下就是:每一项要么直接包含了所要描述的内容,比如 utf8 字符串,要么以编号的形式引用其他项的内容

CONSTANT_String_info {
    u1 tag; // 类型编号 8
    u2 string_index; // String 对应的 utf8 常量池项的编号
}

CONSTANT_Float_info {  
   u1 tag;  // 类型编号 4
   u4 bytes;  // 值用 4 字节的无符号整形表示
}

CONSTANT_Double_info {  
   u1 tag;  // 类型编号 6
   u4 high_bytes;  // 高地址的值用 4 字节的无符号整形表示
   u4 low_bytes;  // 低地址的值用 4 字节的无符号整形表示
}

CONSTANT_MethodType_info {
    u1 tag; // 类型编号 16
    u2 descriptor_index; // 描述符对应的 utf8 常量池项编号
}

CONSTANT_Fieldref_info {
    u1 tag; // 类型编号 9
    u2 class_index; // 字段所属类对应的 class 常量池项编号
    u2 name_and_type_index; // 描述字段的对应的 name_and_type 常量池项编号
}

CONSTANT_InterfaceMethodref_info {
    u1 tag; // 类型编号 11
    u2 class_index; // 函数所属接口对应的 class 常量池项编号
    u2 name_and_type_index; // 描述函数的对应的 name_and_type 常量池项编号
}

CONSTANT_Integer_info {  
   u1 tag;  // 类型编号 3
   u4 bytes;  // 值用 4 字节的无符号整形表示
}

CONSTANT_Long_info {  
   u1 tag;  // 类型编号 5
   u4 high_bytes;  // 高地址的值用 4 字节的无符号整形表示
   u4 low_bytes;  // 低地址的值用 4 字节的无符号整形表示
}

CONSTANT_MethodHandle_info {  
   u1 tag;  // 类型编号 15
   u1 reference_kind;  // 方法句柄的类型用 1 字节的无符号整数表示
   u2 reference_index;  // 根据引用类型的不同可能会指向 Fieldref、Methodref、InterfaceMethodref 类型的常量池项
}

CONSTANT_InvokeDynamic_info {  
   u1 tag;  // 类型编号 18
   u2 bootstrap_method_attr_index;  // 为指向引导方法表中的索引, 即定位到一个引导方法。引导方法用于在动态调用时进行运行时函数查找和绑定。引导方法表属于类文件的属性(Attribute)
   u2 name_and_type_index;  // 表示方法名字和签名的 NameAndType 常量池项的编号
}

字节码技术

节码增强:

对现有字节码修改或动态生成全新字节码文件,一般用来实现AOP

字节码增强技术,可以选择的类库有很多

ASM

Cglib 就是基于 ASM

ASM 有两套 API

把整个类的结构读取到内存中

通过各种 Node 类来映射字节码的各个区域


package bbm.jvm;

public class Test {
    public void test() {
        System.out.println("test");
    }
}
package bbm.jvm;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;

public class TestASM {
    public static void main(String[] args) throws IOException {
        ClassReader classReader = new ClassReader("bbm.jvm.Test");
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        //处理
        ClassVisitor classVisitor = new MyClassVisitor(classWriter);
        classReader.accept(classVisitor, ClassReader.SKIP_DEBUG);
        byte[] data = classWriter.toByteArray();
        //输出
        File f = new File("/path/to/save/Test2.class");
        if (!f.exists()) {
            f.createNewFile();
        }
        FileOutputStream fout = new FileOutputStream(f);
        fout.write(data);
        fout.close();
    }

    public static class MyClassVisitor extends ClassVisitor implements Opcodes {
        public MyClassVisitor(ClassVisitor cv) {
            super(ASM5, cv);
        }
        @Override
        public void visit(int version, int access, String name, String signature,
            String superName, String[] interfaces) {
            cv.visit(version, access, name, signature, superName, interfaces);
        }
        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
            //Test 类中有两个方法:无参构造以及 test 方法,这里不增强构造方法
            if (!name.equals("<init>") && mv != null) {
                mv = new MyMethodVisitor(mv);
            }
            return mv;
        }
        class MyMethodVisitor extends MethodVisitor implements Opcodes {
            public MyMethodVisitor(MethodVisitor mv) {
                super(Opcodes.ASM5, mv);
            }

            @Override
            public void visitCode() {
                super.visitCode();
                mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv.visitLdcInsn("start");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            }
            @Override
            public void visitInsn(int opcode) {
                if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
                    mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                    mv.visitLdcInsn("end");
                    mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                }
                mv.visitInsn(opcode);
            }
        }
    }
}

新类反编译后

package bbm.jvm;

public class Test {
    public Test() {
    }

    public void test() {
        System.out.println("start");
        System.out.println("test");
        System.out.println("end");
    }
}

Javassist

ASM 在 指令层次上操作字节码,实现起来比较繁琐

Javassist 强调源代码层次操作字节码

直接使用 java 编码,不需解虚拟机指令,就能动态改变类的结构或者动态生成类

import java.io.IOException;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;

public class TestJavassist {
    public static void main(String[] args) throws Exception {
        ClassPool cp = ClassPool.getDefault();
        CtClass cc = cp.get("bbm.jvm.Test");
        CtMethod m = cc.getDeclaredMethod("test");
        m.insertBefore("{ System.out.println(\"start\"); }");
        m.insertAfter("{ System.out.println(\"end\"); }");
        Class c = cc.toClass();
        cc.writeFile("/path/to/save/");
        Test h = (Test)c.newInstance();
        h.test();
    }
}