02-Class文件格式

nobility 发布于 2021-07-06 1660 次阅读


Class文件格式

使用VScode安装Hexdump插件后,右击选择Show Hexdump即可查看到Class文件的原始内容,使用JDK中自带的反编译工具javap可将Class文件反编译成Java汇编代码,使用-verbose参数可在控制台输出

  • 一组以8字节为单位的字节流,各个数据项按照顺序紧凑排列(无空格和换行)
  • 对于空间大于8字节的数据项,按照高位在前的方式分隔成多个8字节进行存储
  • 只有两种数据类型:无符号数和表
    • 无符号数:基本数据类型,以u1、u2、u4、u8来代表集合字节的无符号数
    • 表:由多个无符号数和其他表构成的复合数据类型,通常以_info结尾

每个Class文件对应如下结构:

数据类型 数据项 描述
u4 magic 魔数,表明该文件是否为一个能被虚拟机接收的Class文件,固定值0x CA FE BA BE不会改变
u2 minor_version 次版本号,与JDK版本的对应关系从这里可以查到
u2 major_version 主版本号,与JDK版本的对应关系从这里可以查到
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 interface[interface_count] 接口集合
u2 fields_count 属性数量
field_info fields[fields_count] 属性字段信息
u2 methods_count 方法数量
method_info methods[methods_count] 方法信息
u2 attributes_count 属性数量(元属性)
attribute_info attributes[attribtes_counnt] 属性信息(元属性)

阅读Class文件

对应上面Class文件格式,以及常量池、属性和方法,边查文档边对比阅读即可

常量池

常量池中每个常量对应如下结构:

数据类型 数据项 描述
u1 tag 常量标记可从这里查到
u1 info[] 常量信息,对应tag标记

属性

每个属性字段对应如下结构

数据类型 数据项 描述
u2 access_flags 字段修饰符可从这里查到
u2 name_index 字段名索引
u2 descriptor_index 描述索引
u2 attributes_count 属性数量(元属性)
attribute_info attributes[attributes_count] 属性信息(元属性)

方法

方法中出来操作指令外,也有预定义的attributes属性,具体有如下几个

  • stack:方法执行时,操作栈的深度
  • locals:局部辨变量所需空间,单位是slot,虚拟机分配最小内存空间
  • args_size:参数个数,实例方法默认第一个会传递this参数
  • LineNumberTable:行号

代码

java代码如下

public class Hello{
  private static String hello = "hello";
  
  public static void main(String[] args){
    System.out.println(hello + "world");
  }
}

原始字节码文件如下:

  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 	
00000000: CA FE BA BE 00 00 00 3B 00 34 0A 00 02 00 03 07    J~:>...;.4......
00000010: 00 04 0C 00 05 00 06 01 00 10 6A 61 76 61 2F 6C    ..........java/l
00000020: 61 6E 67 2F 4F 62 6A 65 63 74 01 00 06 3C 69 6E    ang/Object...<in
00000030: 69 74 3E 01 00 03 28 29 56 09 00 08 00 09 07 00    it>...()V.......
00000040: 0A 0C 00 0B 00 0C 01 00 10 6A 61 76 61 2F 6C 61    .........java/la
00000050: 6E 67 2F 53 79 73 74 65 6D 01 00 03 6F 75 74 01    ng/System...out.
00000060: 00 15 4C 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74    ..Ljava/io/Print
00000070: 53 74 72 65 61 6D 3B 09 00 0E 00 0F 07 00 10 0C    Stream;.........
00000080: 00 11 00 12 01 00 05 48 65 6C 6C 6F 01 00 05 68    .......Hello...h
00000090: 65 6C 6C 6F 01 00 12 4C 6A 61 76 61 2F 6C 61 6E    ello...Ljava/lan
000000a0: 67 2F 53 74 72 69 6E 67 3B 12 00 00 00 14 0C 00    g/String;.......
000000b0: 15 00 16 01 00 17 6D 61 6B 65 43 6F 6E 63 61 74    ......makeConcat
000000c0: 57 69 74 68 43 6F 6E 73 74 61 6E 74 73 01 00 26    WithConstants..&
000000d0: 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69    (Ljava/lang/Stri
000000e0: 6E 67 3B 29 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53    ng;)Ljava/lang/S
000000f0: 74 72 69 6E 67 3B 0A 00 18 00 19 07 00 1A 0C 00    tring;..........
00000100: 1B 00 1C 01 00 13 6A 61 76 61 2F 69 6F 2F 50 72    ......java/io/Pr
00000110: 69 6E 74 53 74 72 65 61 6D 01 00 07 70 72 69 6E    intStream...prin
00000120: 74 6C 6E 01 00 15 28 4C 6A 61 76 61 2F 6C 61 6E    tln...(Ljava/lan
00000130: 67 2F 53 74 72 69 6E 67 3B 29 56 08 00 11 01 00    g/String;)V.....
00000140: 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D 62    .Code...LineNumb
00000150: 65 72 54 61 62 6C 65 01 00 04 6D 61 69 6E 01 00    erTable...main..
00000160: 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74    .([Ljava/lang/St
00000170: 72 69 6E 67 3B 29 56 01 00 08 3C 63 6C 69 6E 69    ring;)V...<clini
00000180: 74 3E 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01    t>...SourceFile.
00000190: 00 0A 48 65 6C 6C 6F 2E 6A 61 76 61 01 00 10 42    ..Hello.java...B
000001a0: 6F 6F 74 73 74 72 61 70 4D 65 74 68 6F 64 73 0F    ootstrapMethods.
000001b0: 06 00 27 0A 00 28 00 29 07 00 2A 0C 00 15 00 2B    ..'..(.)..*....+
000001c0: 01 00 24 6A 61 76 61 2F 6C 61 6E 67 2F 69 6E 76    ..$java/lang/inv
000001d0: 6F 6B 65 2F 53 74 72 69 6E 67 43 6F 6E 63 61 74    oke/StringConcat
000001e0: 46 61 63 74 6F 72 79 01 00 98 28 4C 6A 61 76 61    Factory...(Ljava
000001f0: 2F 6C 61 6E 67 2F 69 6E 76 6F 6B 65 2F 4D 65 74    /lang/invoke/Met
00000200: 68 6F 64 48 61 6E 64 6C 65 73 24 4C 6F 6F 6B 75    hodHandles$Looku
00000210: 70 3B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72    p;Ljava/lang/Str
00000220: 69 6E 67 3B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 69    ing;Ljava/lang/i
00000230: 6E 76 6F 6B 65 2F 4D 65 74 68 6F 64 54 79 70 65    nvoke/MethodType
00000240: 3B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69    ;Ljava/lang/Stri
00000250: 6E 67 3B 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 4F    ng;[Ljava/lang/O
00000260: 62 6A 65 63 74 3B 29 4C 6A 61 76 61 2F 6C 61 6E    bject;)Ljava/lan
00000270: 67 2F 69 6E 76 6F 6B 65 2F 43 61 6C 6C 53 69 74    g/invoke/CallSit
00000280: 65 3B 08 00 2D 01 00 06 01 77 6F 72 6C 64 01 00    e;..-....world..
00000290: 0C 49 6E 6E 65 72 43 6C 61 73 73 65 73 07 00 30    .InnerClasses..0
000002a0: 01 00 25 6A 61 76 61 2F 6C 61 6E 67 2F 69 6E 76    ..%java/lang/inv
000002b0: 6F 6B 65 2F 4D 65 74 68 6F 64 48 61 6E 64 6C 65    oke/MethodHandle
000002c0: 73 24 4C 6F 6F 6B 75 70 07 00 32 01 00 1E 6A 61    s$Lookup..2...ja
000002d0: 76 61 2F 6C 61 6E 67 2F 69 6E 76 6F 6B 65 2F 4D    va/lang/invoke/M
000002e0: 65 74 68 6F 64 48 61 6E 64 6C 65 73 01 00 06 4C    ethodHandles...L
000002f0: 6F 6F 6B 75 70 00 21 00 0E 00 02 00 00 00 01 00    ookup.!.........
00000300: 0A 00 11 00 12 00 00 00 03 00 01 00 05 00 06 00    ................
00000310: 01 00 1E 00 00 00 1D 00 01 00 01 00 00 00 05 2A    ...............*
00000320: B7 00 01 B1 00 00 00 01 00 1F 00 00 00 06 00 01    7..1............
00000330: 00 00 00 01 00 09 00 20 00 21 00 01 00 1E 00 00    .........!......
00000340: 00 2B 00 02 00 01 00 00 00 0F B2 00 07 B2 00 0D    .+........2..2..
00000350: BA 00 13 00 00 B6 00 17 B1 00 00 00 01 00 1F 00    :....6..1.......
00000360: 00 00 0A 00 02 00 00 00 04 00 0E 00 05 00 08 00    ................
00000370: 22 00 06 00 01 00 1E 00 00 00 1E 00 01 00 00 00    "...............
00000380: 00 00 06 12 1D B3 00 0D B1 00 00 00 01 00 1F 00    .....3..1.......
00000390: 00 00 06 00 01 00 00 00 02 00 03 00 23 00 00 00    ............#...
000003a0: 02 00 24 00 25 00 00 00 08 00 01 00 26 00 01 00    ..$.%.......&...
000003b0: 2C 00 2E 00 00 00 0A 00 01 00 2F 00 31 00 33 00    ,........./.1.3.
000003c0: 19                                                 .

javap反编译内容如下

  Last modified 1970年1月1日; size 961 bytes
  SHA-256 checksum 4c2c5625f17ff3294ff3375e7601f0e1a381196f1d66485595b4886191ac1041
  Compiled from "Hello.java"
public class Hello
  minor version: 0
  major version: 59
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #14                         // Hello
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 1, methods: 3, attributes: 3
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Fieldref           #8.#9          // java/lang/System.out:Ljava/io/PrintStream;
   #8 = Class              #10            // java/lang/System
   #9 = NameAndType        #11:#12        // out:Ljava/io/PrintStream;
  #10 = Utf8               java/lang/System
  #11 = Utf8               out
  #12 = Utf8               Ljava/io/PrintStream;
  #13 = Fieldref           #14.#15        // Hello.hello:Ljava/lang/String;
  #14 = Class              #16            // Hello
  #15 = NameAndType        #17:#18        // hello:Ljava/lang/String;
  #16 = Utf8               Hello
  #17 = Utf8               hello
  #18 = Utf8               Ljava/lang/String;
  #19 = InvokeDynamic      #0:#20         // #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
  #20 = NameAndType        #21:#22        // makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
  #21 = Utf8               makeConcatWithConstants
  #22 = Utf8               (Ljava/lang/String;)Ljava/lang/String;
  #23 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #24 = Class              #26            // java/io/PrintStream
  #25 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
  #29 = String             #17            // hello
  #30 = Utf8               Code
  #31 = Utf8               LineNumberTable
  #32 = Utf8               main
  #33 = Utf8               ([Ljava/lang/String;)V
  #34 = Utf8               <clinit>
  #35 = Utf8               SourceFile
  #36 = Utf8               Hello.java
  #37 = Utf8               BootstrapMethods
  #38 = MethodHandle       6:#39          // REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #39 = Methodref          #40.#41        // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #40 = Class              #42            // java/lang/invoke/StringConcatFactory
  #41 = NameAndType        #21:#43        // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #42 = Utf8               java/lang/invoke/StringConcatFactory
  #43 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #44 = String             #45            // \u0001world
  #45 = Utf8               \u0001world
  #46 = Utf8               InnerClasses
  #47 = Class              #48            // java/lang/invoke/MethodHandles$Lookup
  #48 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #49 = Class              #50            // java/lang/invoke/MethodHandles
  #50 = Utf8               java/lang/invoke/MethodHandles
  #51 = Utf8               Lookup
{
  public Hello();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: getstatic     #13                 // Field hello:Ljava/lang/String;
         6: invokedynamic #19,  0             // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
        11: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        14: return
      LineNumberTable:
        line 4: 0
        line 5: 14

  static {};
    descriptor: ()V
    flags: (0x0008) ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #29                 // String hello
         2: putstatic     #13                 // Field hello:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 2: 0
}
SourceFile: "Hello.java"
BootstrapMethods:
  0: #38 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #44 \u0001world
InnerClasses:
  public static final #51= #47 of #49;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

ASM开发

ASM是一个Java字节码操作框架,能动态生成类或增强已有类的功能,ASM可直接生成Class文件,也可以在类被加载之前动态改变类的行为,cglib、spring都直接或间接依赖ASM操作字节码

pom.xml文件中添加下面两个依赖

<dependency>
  <groupId>org.ow2.asm</groupId>
  <artifactId>asm-util</artifactId>
  <version>9.0</version>
</dependency>
<dependency>
  <groupId>org.ow2.asm</groupId>
  <artifactId>asm</artifactId>
  <version>9.0</version>
</dependency>

ASM编程模型

  • Core API:提供基于事件形式的编程模型,无需一次性将整个类的结构读取到内存,因此该方式快,内存消耗少,但是编程难度大
  • Tree API:提供基于属性的编程模型,需要一次性将类完整结构读取到内存中,因此该方式慢,内存消耗大,但是编程难度小

CoreAPI

基于ClassVisitor接口的三个实现类,来操作Class文件的生成与转化

  1. ClassReader:解析一个类的Class文件
  2. ClassAdapter:实现变化的功能
  3. ClassWriter:输出变化后的字节码

由于CoreAPI编程难度较大,ASM提供ASMifier工具来帮助开发,可使用该工具生成的ASM结构化代码对比修改,使用方式如下

import com.Service;
import org.objectweb.asm.util.ASMifier;

public class Main {
  public static void main(final String[] args) {
    try {
      ASMifier.main(new String[]{Service.class.getName()});  //调用main方法,传递参数为要分析的类的全类名,即可生成结构化代码
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

基于ASM的AOP实现

计算某个方法的执行时间的日志输出类

public class MyTimeLogger {
  private static long start = 0;

  public static void start() {
    start = System.currentTimeMillis();
  }

  public static void end() {
    long end = System.currentTimeMillis();
    System.out.println("runtime is " + (end - start));
  }
}

使用ASMifier工具对比调用该日志类输出多啦以下两处内容

//...
methodVisitor.visitMethodInsn(INVOKESTATIC, "MyTimeLogger", "start", "()V", false);
//...
methodVisitor.visitMethodInsn(INVOKESTATIC, "MyTimeLogger", "end", "()V", false);
//...

编写修改Class文件的类

import org.objectweb.asm.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class MyClassVisitor extends ClassVisitor {  //继承ClassVisitor类,复写访问方法
  static class MyMethodVisitor extends MethodVisitor {  //继承MethodVisitor类,复写访问方法
    MyMethodVisitor(MethodVisitor methodVisitor) {
      super(Opcodes.ASM9, methodVisitor); //构造方法中指定ASM版本
    }

    @Override
    public void visitCode() {  //执行方法前
      mv.visitCode();
      mv.visitMethodInsn(Opcodes.INVOKESTATIC, "MyTimeLogger", "start", "()V", false);  //增加ASMifier工具生成的代码
    }

    @Override
    public void visitInsn(int opcode) {  //执行方法后
      if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN || opcode == Opcodes.ATHROW) {
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "MyTimeLogger", "end", "()V", false);  //增加ASMifier工具生成的代码
      }
      mv.visitInsn(opcode);
    }
  }

  private MyClassVisitor(ClassVisitor classVisitor) {
    super(Opcodes.ASM9, classVisitor);  //构造方法中指定ASM版本
  }

  @Override
  public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
    MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
    if (!"<init>".equals(name) && mv != null) {
      mv = new MyMethodVisitor(mv);  //替换为自己增强的方法访问并返回
    }
    return mv;
  }

  static void generate() {  //生成修改后的Class文件
    ClassReader cr;
    byte[] data;
    FileOutputStream fos = null;
    try {
      cr = new ClassReader("Service");  //指定读取的类路径,使用反斜杠分隔
      ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);  //输出页最大
      ClassVisitor cv = new MyClassVisitor(cw);
      cr.accept(cv, ClassReader.SKIP_DEBUG);  //跳过调试
      data = cw.toByteArray();  //获取返回的字节数组
      File f = new File("target/classes/Service.class");  //输出到Class文件中
      fos = new FileOutputStream(f);  //获取输出流
      fos.write(data);  //输入字节数组
      System.out.println("修改class文件成功");
    } catch (IOException e) {
      e.printStackTrace();
    } finally {  //关闭输出流
      if (fos != null) {
        try {
          fos.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }
}

使用下面代码进行测试

public class Main {
  public static void main(String[] args) {
    MyClassVisitor.generate();  //生成Class文件
    new Service().service();  //调用业务方法
  }
}

此作者没有提供个人介绍
最后更新于 2021-07-06