ASM使用小结01

IAST前置知识,使用ASM进行字节码增强

ClassFile结构

  • ASM对Class文件的操作都是基于ClassFile的文件结构,了解ClassFile文件结构有助于更好的理解ASM。

  • u1、u2分别代表几个字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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];
}

描述符

对ASM中常用的字段描述符的举例:

  • boolean flag: Z
  • byte byteValue: B
  • int intValue: I
  • float floatValue: F
  • double doubleValue: D
  • String strValue: Ljava/lang/String;
  • Object objValue: Ljava/lang/Object;
  • byte[] bytes: [B
  • String[] array: [Ljava/lang/String;
  • Object[][] twoDimArray: [[Ljava/lang/Object;

对方法描述符的举例:

  • int add(int a, int b): (II)I
  • void test(int a, int b): (II)V
  • boolean compare(Object obj): (Ljava/lang/Object;)Z
  • void main(String[] args): ([Ljava/lang/String;)V

ASM作用

  • .java文件经过javac编译为.class,ASM可直接对.class文件进行操作
  • ASM用来操作字节码文件,不止java语言,只要符合java字节码规范即可

运行方式

  • 基于事件-核心API?
  • 基于对象-树API?

体系结构

ASM生成类

ClassVisitor类

  • 一个抽象类,常见实现类->ClassWrite、ClassNode

  • 访问者模式

  • 有调用顺序

    • []: 表示最多调用一次,可以不调用,但最多调用一次。
    • ()|: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。
    • *: 表示方法可以调用0次或多次。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    visit
    [visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]
    (
    visitAnnotation |
    visitTypeAnnotation |
    visitAttribute
    )*
    (
    visitNestMember |
    visitInnerClass |
    visitRecordComponent |
    visitField |
    visitMethod
    )*
    visitEnd
  • visit方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public void visit(
    final int version, // jdk版本
    final int access, // 访问标识
    final String name, // 类名
    final String signature, // 当前类的泛型信息
    final String superName, // 当前类的父类信息
    final String[] interfaces) { // 实现接口
    if (api < Opcodes.ASM8 && (access & Opcodes.ACC_RECORD) != 0) {
    throw new UnsupportedOperationException("Records requires ASM8");
    }
    if (cv != null) {
    cv.visit(version, access, name, signature, superName, interfaces);
    }
    }
  • visitField方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public FieldVisitor visitField(
    final int access, // 访问表示
    final String name, // 字段名
    final String descriptor, // 字段描述符
    final String signature, // 泛型类型
    final Object value) { // 字段值
    if (cv != null) {
    return cv.visitField(access, name, descriptor, signature, value);
    }
    return null;
    }
  • visitMethod方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public MethodVisitor visitMethod(
    final int access, // 访问标识
    final String name, // 方法名
    final String descriptor, // 方法描述符
    final String signature, // 泛型类型
    final String[] exceptions) { // 异常
    if (cv != null) {
    return cv.visitMethod(access, name, descriptor, signature, exceptions);
    }
    return null;
    }

FieldVisitor类

  • 一个抽象类

  • FieldVisitor类内定义的多个visitXxx()方法,也需要遵循一定的调用顺序,如下所示:

    • []: 表示最多调用一次,可以不调用,但最多调用一次。
    • ()|: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。
    • *: 表示方法可以调用0次或多次。
    1
    2
    3
    4
    5
    6
    (
    visitAnnotation |
    visitTypeAnnotation |
    visitAttribute
    )*
    visitEnd

MethodVisitor类

  • 一个抽象方法

  • 遵循一定的调用顺序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    (visitParameter)*
    [visitAnnotationDefault]
    (visitAnnotation | visitAnnotableParameterCount | visitParameterAnnotation | visitTypeAnnotation | visitAttribute)*
    [
    visitCode
    (
    visitFrame |
    visitXxxInsn |
    visitLabel |
    visitInsnAnnotation |
    visitTryCatchBlock |
    visitTryCatchAnnotation |
    visitLocalVariable |
    visitLocalVariableAnnotation |
    visitLineNumber
    )*
    visitMaxs
    ]
    visitEnd
  • visitXxx()方法分成三组:

    • 第一组,在visitCode()方法之前的方法。这一组的方法,主要负责parameter、annotation和attributes等内容,这些内容并不是方法当中“必不可少”的一部分;暂时不去考虑这些内容,可以忽略这一组方法。
    • 第二组,在visitCode()方法和visitMaxs()方法之间的方法。这一组的方法,主要负责当前方法的“方法体”内的opcode内容。其中,visitCode()方法,标志着方法体的开始,而visitMaxs()方法,标志着方法体的结束。
    • 第三组,是visitEnd()方法。这个visitEnd()方法,是最后一个进行调用的方法。

ClassWriter类

ClassVistor的实现类,在创建ClassWriter对象的时候,要指定一个flags参数,它可以选择的值有三个:

  • 第一个,可以选取的值是0。ASM不会自动计算max stacks和max locals,也不会自动计算stack map frames。
  • 第二个,可以选取的值是ClassWriter.COMPUTE_MAXS。ASM会自动计算max stacks和max locals,但不会自动计算stack map frames。
  • 第三个,可以选取的值是ClassWriter.COMPUTE_FRAMES(推荐使用)。ASM会自动计算max stacks和max locals,也会自动计算stack map frames。

FieldWriter类

FieldVisitor的具体实现类,FieldWriter类并不带有public修饰,因此它的有效访问范围只局限于它所处的package当中,不能像其它的public类一样被外部所使用。

MethodWriter类

MethodWriter类的父类是MethodVisitor类。在ClassWriter类里,visitMethod()方法的实现就是通过MethodWriter类来实现的。

MethodWriter类并不带有public修饰,因此它的有效访问范围只局限于它所处的package当中,不能像其它的public类一样被外部所使用。

生成一个类

生成一个HelloWorld接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package run;

import static org.objectweb.asm.Opcodes.*;
import org.objectweb.asm.ClassWriter;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.math.BigInteger;

public class HelloWorldGenerateCore {
public static void main(String[] args) throws Exception {

byte[] bytes = dump();

// 字节码打印
BigInteger bigInteger = new BigInteger(1, bytes);
System.out.println(bigInteger.toString(16));

// 通过反射defineClass0方法将字节码转对象
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Method method = Proxy.class.getDeclaredMethod("defineClass0",ClassLoader.class, String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
Object obj = method.invoke(null, classLoader, "sample.HelloWorld", bytes, 0, bytes.length);
System.out.println(obj);
}

public static byte[] dump() throws Exception {
// (1) 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

// (2) 调用visitXxx()方法
cw.visit(
V1_8, // version
ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, // access
"sample/HelloWorld", // name
null, // signature
"java/lang/Object", // superName
null // interfaces
);

cw.visitEnd();

// (3) 调用toByteArray()方法
return cw.toByteArray();
}
}

生成一个HelloWorld类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package run;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.math.BigInteger;

public class HelloWorldClassGenerate implements Opcodes {

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

byte[] bytes = dump();

// 字节码打印
BigInteger bigInteger = new BigInteger(1, bytes);
System.out.println(bigInteger.toString(16));

// 通过反射defineClass0方法将字节码转对象
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Method method = Proxy.class.getDeclaredMethod("defineClass0",ClassLoader.class, String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
Object obj = method.invoke(null, classLoader, "sample.HelloWorld", bytes, 0, bytes.length);
System.out.println(obj);
}

public static byte[] dump(){
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

cw.visit(V1_8, ACC_PUBLIC+ACC_SUPER, "sample/HelloWorld", null, "java/lang/Object", null);
// 无参构造函数<init>
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
cw.visitEnd();
return cw.toByteArray();
}
}

ASM读取类

ClassReader类

  • 用于读取.class文件,区别于ClassWriter

  • 一个公共类,不继承ClassVisitor

  • ClassReader类当中定义了5个构造方法。但是,从本质上来说,这5个构造方法本质上是同一个构造方法的不同表现形式。其中,最常用的构造方法有两个

    • 第一个是ClassReader cr = new ClassReader("sample.HelloWorld");
    • 第二个是ClassReader cr = new ClassReader(bytes);
  • accept方法,这个方法接收一个ClassVisitor类型的参数,将ClassReaderClassVisitor进行连接。

    1
    2
    3
    public void accept(final ClassVisitor classVisitor, final int parsingOptions) {
    accept(classVisitor, new Attribute[0], parsingOptions);
    }
  • parsingOptions参数可以选取的值有以下5个:

    • 0:会生成所有的ASM代码,包括调试信息、frame信息和代码信息。
    • ClassReader.SKIP_CODE:会忽略代码信息,例如,会忽略对于MethodVisitor.visitXxxInsn()方法的调用。
    • ClassReader.SKIP_DEBUG:会忽略调试信息,例如,会忽略对于MethodVisitor.visitParameter()MethodVisitor.visitLineNumber()MethodVisitor.visitLocalVariable()等方法的调用。
    • ClassReader.SKIP_FRAMES:会忽略frame信息,例如,会忽略对于MethodVisitor.visitFrame()方法的调用。
    • ClassReader.EXPAND_FRAMES:会对frame信息进行扩展,例如,会对MethodVisitor.visitFrame()方法的参数有影响。

读取一个类

编译HelloWorld类

  • 编译HelloWorld类为Class文件,然后通过ClassReader读取Class文件

    1
    2
    3
    4
    5
    6
    package sample;
    import java.io.Serializable;

    public class HelloWorld extends Exception implements Serializable, Cloneable {

    }

读取HelloWorld.class

  • 使用ClassReader读取Class文件信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    package run;

    import org.objectweb.asm.ClassReader;

    import java.io.*;
    import java.util.Arrays;

    public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
    // 获取Class绝对路径
    String relativePath = "sample/HelloWorld.class";
    String absolutePath = HelloWorldRun.class.getResource("/").getPath() + relativePath;

    // 读取字节
    File file = new File(absolutePath);
    InputStream inputStream = new FileInputStream(file);
    inputStream = new BufferedInputStream(inputStream);
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    copy(inputStream, byteArrayOutputStream);
    byte[] bytes = byteArrayOutputStream.toByteArray();

    //(1)构建ClassReader
    ClassReader cr = new ClassReader(bytes);

    // (2) 调用getXxx()方法
    int access = cr.getAccess();
    System.out.println("access: " + access);

    String className = cr.getClassName();
    System.out.println("className: " + className);

    String superName = cr.getSuperName();
    System.out.println("superName: " + superName);

    String[] interfaces = cr.getInterfaces();
    System.out.println("interfaces: " + Arrays.toString(interfaces));
    }

    public static void copy(InputStream source, OutputStream target) throws IOException {
    byte[] buf = new byte[8192];
    int length;
    while ((length = source.read(buf)) > 0) {
    target.write(buf, 0, length);
    }
    }
    }
  • 输出结果

    1
    2
    3
    4
    access: 33
    className: sample/HelloWorld
    superName: java/lang/Exception
    interfaces: [java/io/Serializable, java/lang/Cloneable]

转换一个类

  • ClassReader类,是ASM提供的一个类,可以直接拿来使用。
  • ClassWriter类,是ASM提供的一个类,可以直接拿来使用。
  • ClassVisitor类,是ASM提供的一个抽象类,因此需要写代码提供一个ClassVisitor的子类,在这个子类当中可以实现对.class文件进行各种处理操作。

为HelloWorld类添加接口

  • 编写HelloWorld.java类如下:

    1
    2
    3
    4
    5
    package sample;

    public class HelloWorld {

    }
  • 初始的HelloWorld.class文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    //

    package sample;

    public class HelloWorld {
    public HelloWorld() {
    }
    }
  • 通过ASM的ClassReaderClassWriterClassVisitor类对HelloWorld类进行转换,为其添加一个实现接口

  • 编写HelloWorldCloneVisitor类继承ClassVisitor类来实现具体功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package run;

    import org.objectweb.asm.ClassVisitor;

    public class HelloWorldCloneVisitor extends ClassVisitor {
    public HelloWorldCloneVisitor(int api, ClassVisitor classVisitor) {
    super(api, classVisitor);
    }

    // 重写visit方法
    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
    super.visit(version, access, name, signature, superName, new String[]{"java/lang/Cloneable"});
    }
    }
  • 编写HelloWorldTransform转换已有的class文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    package run;

    import org.objectweb.asm.ClassReader;
    import org.objectweb.asm.ClassVisitor;
    import org.objectweb.asm.ClassWriter;
    import org.objectweb.asm.Opcodes;

    import java.io.*;

    public class HelloWorldTransform {
    public static void main(String[] args) throws IOException {
    // 获取Class绝对路径
    String relativePath = "sample/HelloWorld.class";
    String absolutePath = HelloWorldRun.class.getResource("/").getPath() + relativePath;

    // 读取字节
    File file = new File(absolutePath);
    InputStream inputStream = new FileInputStream(file);
    inputStream = new BufferedInputStream(inputStream);
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    copy(inputStream, byteArrayOutputStream);
    byte[] bytes1 = byteArrayOutputStream.toByteArray();

    //(1)构建ClassReader
    ClassReader cr = new ClassReader(bytes1);

    //(2)构建ClassWriter
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

    //(3)串连ClassVisitor
    int api = Opcodes.ASM9;
    ClassVisitor cv = new HelloWorldCloneVisitor(api, cw);

    //(4)结合ClassReader和ClassVisitor
    int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
    cr.accept(cv, parsingOptions);

    //(5)生成byte[]
    byte[] bytes2 = cw.toByteArray();

    // (6) 重新写入class文件
    OutputStream outputStream = new FileOutputStream(absolutePath);
    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
    bufferedOutputStream.write(bytes2);
    bufferedOutputStream.flush();
    }

    public static void copy(InputStream source, OutputStream target) throws IOException {
    byte[] buf = new byte[8192];
    int length;
    while ((length = source.read(buf)) > 0) {
    target.write(buf, 0, length);
    }
    }
    }
  • 转换后的HelloWorld.class文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    //

    package sample;

    public class HelloWorld implements Cloneable {
    public HelloWorld() {
    }
    }

为HelloWorld类添加字段

  • 编写HelloWorld.java类如下:

    1
    2
    3
    4
    5
    package sample;

    public class HelloWorld {

    }
  • 初始的HelloWorld.class文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    //

    package sample;

    public class HelloWorld {
    public HelloWorld() {
    }
    }
  • 编写HelloWorldAddFieldVisitor类继承ClassVisitor类来实现具体功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    package run;

    import org.objectweb.asm.ClassVisitor;
    import org.objectweb.asm.FieldVisitor;

    public class HelloWorldAddFieldVisitor extends ClassVisitor {
    private final int fieldAccess;
    private final String fieldName;
    private final String fieldDesc;
    private boolean isFieldPresent;

    public HelloWorldAddFieldVisitor(int api, ClassVisitor classVisitor, int fieldAccess, String fieldName, String fieldDesc) {
    super(api, classVisitor);
    // 字段访问标识
    this.fieldAccess = fieldAccess;
    // 字段名称
    this.fieldName = fieldName;
    // 字段描述符
    this.fieldDesc = fieldDesc;
    }

    @Override
    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
    if (name.equals(fieldName)) {
    isFieldPresent = true;
    }
    return super.visitField(access, name, descriptor, signature, value);
    }

    @Override
    public void visitEnd() {
    if (!isFieldPresent) {
    FieldVisitor fv = super.visitField(fieldAccess, fieldName, fieldDesc, null, null);
    if (fv != null) {
    fv.visitEnd();
    }
    }
    super.visitEnd();
    }
    }
  • 编写HelloWorldTransform转换已有的class文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    package run;

    import org.objectweb.asm.ClassReader;
    import org.objectweb.asm.ClassVisitor;
    import org.objectweb.asm.ClassWriter;
    import org.objectweb.asm.Opcodes;

    import java.io.*;

    public class HelloWorldTransform {
    public static void main(String[] args) throws IOException {
    // 获取Class绝对路径
    String relativePath = "sample/HelloWorld.class";
    String absolutePath = HelloWorldRun.class.getResource("/").getPath() + relativePath;

    // 读取字节
    File file = new File(absolutePath);
    InputStream inputStream = new FileInputStream(file);
    inputStream = new BufferedInputStream(inputStream);
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    copy(inputStream, byteArrayOutputStream);
    byte[] bytes1 = byteArrayOutputStream.toByteArray();

    //(1)构建ClassReader
    ClassReader cr = new ClassReader(bytes1);

    //(2)构建ClassWriter
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

    //(3)串连ClassVisitor
    int api = Opcodes.ASM9;
    ClassVisitor cv = new HelloWorldAddFieldVisitor(api, cw, Opcodes.ACC_PUBLIC, "objValue", "Ljava/lang/Object;");

    //(4)结合ClassReader和ClassVisitor
    int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
    cr.accept(cv, parsingOptions);

    //(5)生成byte[]
    byte[] bytes2 = cw.toByteArray();

    // (6) 重新写入class文件
    OutputStream outputStream = new FileOutputStream(absolutePath);
    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
    bufferedOutputStream.write(bytes2);
    bufferedOutputStream.flush();
    }

    public static void copy(InputStream source, OutputStream target) throws IOException {
    byte[] buf = new byte[8192];
    int length;
    while ((length = source.read(buf)) > 0) {
    target.write(buf, 0, length);
    }
    }
    }
  • 转换后的HelloWorld.class文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    //

    package sample;

    public class HelloWorld {
    public Object objValue;

    public HelloWorld() {
    }
    }
Author: Sys71m
Link: https://www.sys71m.top/2022/03/23/ASM使用小结01/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.