0x00
参考:ASM教程
0x01 Core Api
生成新的类
核心接口是ClassVisitor,其中方法调用的顺序为
visit
[visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]
(
visitAnnotation |
visitTypeAnnotation |
visitAttribute
)*
(
visitNestMember |
visitInnerClass |
visitRecordComponent |
visitField |
visitMethod
)*
visitEnd
ClassWrite继承于ClassVisitor,ClassWrite最重要的是在构造方法时提供的参数
在创建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。
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
生成接口
public interface HelloWorld {
}
// (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();
生成接口+字段+方法
public interface HelloWorld extends Cloneable {
int LESS = -1;
int EQUAL = 0;
int GREATER = 1;
int compareTo(Object o);
}
// (1) 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// (2) 调用visitXxx()方法
cw.visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "sample/HelloWorld",
null, "java/lang/Object", new String[]{"java/lang/Cloneable"});
{
FieldVisitor fv1 = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I", null, -1);
fv1.visitEnd();
}
{
FieldVisitor fv2 = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I", null, 0);
fv2.visitEnd();
}
{
FieldVisitor fv3 = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I", null, 1);
fv3.visitEnd();
}
{
MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo", "(Ljava/lang/Object;)I", null, null);
mv1.visitEnd();
}
cw.visitEnd();
// (3) 调用toByteArray()方法
return cw.toByteArray();
生成类
public class HelloWorld {
}
// (1) 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// (2) 调用visitXxx()方法
cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",
null, "java/lang/Object", null);
// 创建默认的构造方法
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();
// (3) 调用toByteArray()方法
return cw.toByteArray();
生成含有注解的字段
// 希望生成HelloWorld接口
public interface HelloWorld {
@MyTag(name = "tomcat", age = 10)
int intValue = 100;
}
public @interface MyTag {
String name();
int age();
}
// (1) 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// (2) 调用visitXxx()方法
cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE, "sample/HelloWorld", null, "java/lang/Object", null);
{
FieldVisitor fv1 = cw.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "intValue", "I", null, 100);
{
AnnotationVisitor anno = fv1.visitAnnotation("Lsample/MyTag;", false);
anno.visit("name", "tomcat");
anno.visit("age", 10);
anno.visitEnd();
}
fv1.visitEnd();
}
cw.visitEnd();
// (3) 调用toByteArray()方法
return cw.toByteArray();
MethodVisitor是另一个重要的接口,其提供了众多的方法用于设置方法体中指令
其定义方法的调用顺序如下
(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()方法,是最后一个进行调用的方法。
对这些visitXxx()方法进行精简之后,内容如下:
[
visitCode
(
visitFrame |
visitXxxInsn |
visitLabel |
visitTryCatchBlock
)*
visitMaxs
]
visitEnd
这些方法的调用顺序,可以记忆如下:
- 第一步,调用visitCode()方法,调用一次。
- 第二步,调用visitXxxInsn()方法,可以调用多次。对这些方法的调用,就是在构建方法的“方法体”。
- 第三步,调用visitMaxs()方法,调用一次。
- 第四步,调用visitEnd()方法,调用一次。