jvm-4 class
ddatsh
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
-
Core API
基于 访问者模式
不需要把这个类的整个结构读取进来,就可以用流式的方法来处理字节码文件
MethodVisitor、FieldVisitor、AnnotationVisitor 等
AOP,重点要使用的是 MethodVisitor
-
Tree API
将字节码中的各个结构抽象成一个树形结构,通过 Tree 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 编码,不需解虚拟机指令,就能动态改变类的结构或者动态生成类
-
CtClass(compile-time class):
编译时类信息,它是一个 class 文件在代码中的抽象表现形式,可以通过一个类的全限定名来获取一个 CtClass 对象,用来表示这个类文件
-
ClassPool:
从开发视角来看,ClassPool 是一张保存 CtClass 信息的 HashTable,key 为类名,value 为类名对应的 CtClass 对象
pool.getCtClass(“className”)从 pool 中获取到相应的 CtClass
-
CtMethod、CtField:
对应类中的方法和属性
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();
}
}