ASM学习笔记 前言 最近对IAST、RASP产品有兴趣,发现涉及到了ASM字节码编辑技术,于是学习一下。由于ASM内容比较多,且本人是第一次学习,内容大多为学习摘录。详细可以参考Java ASM系列 | lsieun
ASM 是什么? 简单来说,ASM 是一个操作 Java 字节码的类库。它操作的对象是字节码数据,通常以 .class 文件形式存在。ASM 处理字节码的方式是“拆分-修改-合并”,具体步骤如下:
将 .class 文件拆分成多个部分。
对某个部分的信息进行修改。
将多个部分重新组织成一个新的 .class 文件。
ASM 能够做什么 ASM 可以:
修改父类
添加或删除接口
添加或删除字段
添加或删除或修改方法
.class 文件的结构 ClassFile 结构
每个 .class 文件遵循 Java Virtual Machine Specification 中定义的 ClassFile 结构。其具体定义如下:
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]; }
u1 : 1 个字节
u2 : 2 个字节
u4 : 4 个字节
u8 : 8 个字节
其中,cp_info
、field_info
、method_info
和 attribute_info
结构由 u1、u2、u4 和 u8 组成。
field_info 结构 在 .class 文件中定义的字段遵循 field_info
结构:
1 2 3 4 5 6 7 field_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
method_info 结构 在 .class 文件中定义的方法遵循 method_info
结构:
1 2 3 4 5 6 7 method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
Code 属性结构 在 method_info
结构中,方法体的代码存储在 Code
属性中,其结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Code_attribute { u2 attribute_name_index; u4 attribute_length; u2 max_stack; u2 max_locals; u4 code_length; u1 code[code_length]; u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attributes_count]; }
ASMPrint 类 在刚开始学习 ASM 的时候,编写 ASM 代码是不太容易的。 ASMPrint
类能帮助我们将 .class
文件转换为 ASM 代码,这个功能非常实用。
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 package Study;import org.objectweb.asm.ClassReader;import org.objectweb.asm.util.ASMifier;import org.objectweb.asm.util.Printer;import org.objectweb.asm.util.Textifier;import org.objectweb.asm.util.TraceClassVisitor;import java.io.IOException;import java.io.PrintWriter;public class ASMPrint { public static void main (String[] args) throws IOException { String className = "Study.HelloWorld" ; int parsingOptions = ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG; boolean asmCode = true ; Printer printer = asmCode ? new ASMifier () : new Textifier (); PrintWriter printWriter = new PrintWriter (System.out, true ); TraceClassVisitor traceClassVisitor = new TraceClassVisitor (null , printer, printWriter); new ClassReader (className).accept(traceClassVisitor, parsingOptions); } }
1 2 3 4 5 6 7 8 package Study;public class HelloWorld { public void sayHello () { System.out.println("Hello, World!" ); } }
对于 ASMPrint
类来说,其中
className
值设置为类的全限定名,可以是我们自己写的类,例如 sample.HelloWorld
,也可以是 JDK 自带的类,例如 java.lang.Comparable
。
asmCode
值设置为 true
或 false
。如果是 true
,可以打印出对应的 ASM 代码;如果是 false
,可以打印出方法对应的 Instruction。
parsingOptions
值设置为 ClassReader.SKIP_CODE
、ClassReader.SKIP_DEBUG
、ClassReader.SKIP_FRAMES
、ClassReader.EXPAND_FRAMES
的组合值,也可以设置为 0
,可以打印出详细程度不同的信息。
ClassVisitor 类 class info ClassVisitor
类是一个 abstract
类,要想使用它,就必须有具体的子类来继承它。
比较常见的 ClassVisitor
子类是 ClassWriter
类
1 2 public class ClassWriter extends ClassVisitor { }
fields ClassVisitor
类定义的字段
1 2 3 4 public abstract class ClassVisitor { protected final int api; protected ClassVisitor cv; }
api
字段:它是一个 int
类型的数据,指出了当前使用的 ASM 版本,其可取值为 Opcodes.ASM4
~`Opcodes.ASM9。我们使用的 ASM 版本是 9.0,因此我们在给
api字段赋值的时候,选择
Opcodes.ASM9` 就可以了。
cv
字段:它是一个 ClassVisitor
类型的数据,它的作用是将多个 ClassVisitor
串连起来。
constructors ClassVisitor
类定义的构造方法
1 2 3 4 5 6 7 8 9 10 public abstract class ClassVisitor { public ClassVisitor (final int api) { this (api, null ); } public ClassVisitor (final int api, final ClassVisitor classVisitor) { this .api = api; this .cv = classVisitor; } }
methods ClassVisitor
类定义的方法。重点关注关注这 4 个方法:visit()
、visitField()
、visitMethod()
和 visitEnd()
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public abstract class ClassVisitor { public void visit ( final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) ; public FieldVisitor visitField ( // 访问字段 final int access, final String name, final String descriptor, final String signature, final Object value) ; public MethodVisitor visitMethod ( // 访问方法 final int access, final String name, final String descriptor, final String signature, final String[] exceptions) ; public void visitEnd () ; }
在 ClassVisitor
的 visit()
方法、visitField()
方法和 visitMethod()
方法中都带有 signature
参数。这个 signature
参数与“泛型”密切相关;换句话说,如果处理的是一个带有泛型信息的类、字段或方法,那么就需要给 signature
参数提供一定的值;如果处理的类、字段或方法不带有“泛型”信息,那么将 signature
参数设置为 null
就可以了。在本次课程当中,我们不去考虑“泛型”相关的内容,所以我们都将 signature
参数设置成 null
值。
如果大家对 signature
参数感兴趣,我们可以使用之前介绍的 PrintASMCodeCore
类去打印一下某个泛型类的 ASM 代码。例如,java.lang.Comparable
是一个泛型接口,我们就可以使用 PrintASMCodeCore
类来打印一下它的 ASM 代码,从来查看 signature
参数的值是什么。
方法的调用顺序 visitXxx()
方法,遵循一定的调用顺序
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
[]
: 表示最多调用一次,可以不调用,但最多调用一次。
()
和 |
: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。
*
: 表示方法可以调用 0 次或多次。
由于我们作为初学者,只关注 ClassVisitor
类当中的 visit()
方法、visitField()
方法、visitMethod()
方法和 visitEnd()
方法这 4 个方法,因此调用顺序是
1 2 3 4 5 6 visit ( visitField | visitMethod )* visitEnd
visit() 方法 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 public void visit ( final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces ) ; 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]; }
ClassVisitor 方法
参数
ClassFile
ClassVisitor.visit()
version
minor_version
和major_version
access
access_flags
name
this_class
signature
attributes
的一部分信息
superName
super_class
interfaces
interfaces_count
和interfaces
ClassVisitor.visitField()
field_info
ClassVisitor.visitMethod()
method_info
visitField() 方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public FieldVisitor visitField ( // 访问字段 final int access, final String name, final String descriptor, final String signature, final Object value ) ; field_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
ClassVisitor 方法
参数
field_info
ClassVisitor.visitField()
access
access_flags
name
name_index
descriptor
descriptor_index
signature
attributes
的一部分信息
value
attributes
的一部分信息
visitMethod() 方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public MethodVisitor visitMethod ( // 访问方法 final int access, final String name, final String descriptor, final String signature, final String[] exceptions ) ; method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
ClassVisitor 方法
参数
method_info
ClassVisitor.visitMethod()
access
access_flags
name
name_index
descriptor
descriptor_index
signature
attributes
的一部分信息
exceptions
attributes
的一部分信息
visitEnd() 方法 visitEnd()
方法,它是这些 visitXxx()
方法当中最后一个调用的方法。
为什么 visitEnd()
方法是“最后一个调用的方法”呢?是因为在 ClassVisitor
当中,定义了多个 visitXxx()
方法,这些个 visitXxx()
方法之间要遵循一个先后调用的顺序,而 visitEnd()
方法是最后才去调用的。
等到 visitEnd()
方法调用之后,就表示说再也不去调用其它的 visitXxx()
方法了,所有的“工作”已经做完了,到了要结束的时候了。
1 2 3 4 5 6 7 8 9 10 public void visitEnd () { if (cv != null ) { cv.visitEnd(); } }
ClassWriter 类 class info ClassWriter
的父类是 ClassVisitor
,因此 ClassWriter
类继承了 visit()
、visitField()
、visitMethod()
和 visitEnd()
等方法。
1 2 public class ClassWriter extends ClassVisitor { }
fields ClassWriter
定义的字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class ClassWriter extends ClassVisitor { private int version; private final SymbolTable symbolTable; private int accessFlags; private int thisClass; private int superClass; private int interfaceCount; private int [] interfaces; private FieldWriter firstField; private FieldWriter lastField; private MethodWriter firstMethod; private MethodWriter lastMethod; private Attribute firstAttribute; }
这些字段与 ClassFile 结构密切相关:
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]; }
constructors ClassWriter
定义的构造方法
ClassWriter
定义的构造方法有两个,这里只关注其中一个,也就是只接收一个 int
类型参数的构造方法。在使用 new
关键字创建 ClassWriter
对象时,推荐使用 COMPUTE_FRAMES
参数。
1 2 3 4 5 6 7 8 9 10 11 12 public class ClassWriter extends ClassVisitor { public static final int COMPUTE_MAXS = 1 ; public static final int COMPUTE_FRAMES = 2 ; public ClassWriter (final int flags) { this (null , flags); } }
创建 ClassWriter 对象 在创建ClassWriter
对象时,可以指定一个flags
参数来决定ASM框架在生成类文件时是否自动计算某些字节码相关的信息。
ClassWriter
的flags
参数有三个可选值:
0
:ASM不会自动计算最大栈大小(max stacks)和最大局部变量数量(max locals),也不会自动计算栈映射帧(stack map frames)。
ClassWriter.COMPUTE_MAXS
:ASM会自动计算最大栈大小和最大局部变量数量,但不会自动计算栈映射帧。
ClassWriter.COMPUTE_FRAMES
(推荐使用):ASM会自动计算最大栈大小、最大局部变量数量,并且会自动计算栈映射帧。
下表展示了不同flags
值对应的行为:
Flags
Max Stacks and Max Locals
Stack Map Frames
0
NO
NO
ClassWriter.COMPUTE_MAXS
YES
NO
ClassWriter.COMPUTE_FRAMES
YES
YES
示例代码ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
创建了一个ClassWriter
实例,并指定了COMPUTE_FRAMES
标志,这意味着ASM将自动计算最大栈大小、最大局部变量数量,并且会计算栈映射帧,从而简化了字节码生成过程,并减少了手动错误的可能性。
使用 ClassWriter 类 使用 ClassWriter
生成一个 Class 文件,可以大致分成三个步骤:
第一步,创建 ClassWriter
对象。
第二步,调用 ClassWriter
对象的 visitXxx()
方法。
第三步,调用 ClassWriter
对象的 toByteArray()
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class HelloWorldGenerateCore { public static byte [] dump () throws Exception { ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES); cw.visit(...); cw.visitField(...); cw.visitMethod(...); cw.visitEnd(...); byte [] bytes = cw.toByteArray(); return bytes; } }
示例一:生成接口 生成 HelloWorld
接口。
1 2 public interface 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 package Study.ClassWriter;import cmisl.utils.FileUtils;import org.objectweb.asm.ClassWriter;import static org.objectweb.asm.Opcodes.*;public class HelloWorldGenerateCore { public static void main (String[] args) throws Exception { String relative_path = "ClassFilePath/HelloWorld.class" ; String filepath = FileUtils.getFilePath(relative_path); byte [] bytes = dump(); FileUtils.writeBytes(filepath, bytes); } public static byte [] dump() throws Exception { ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES); cw.visit( V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "sample/HelloWorld" , null , "java/lang/Object" , null ); cw.visitEnd(); return cw.toByteArray(); } }
调用了 visit()
方法、visitEnd()
方法和 toByteArray()
方法。
由于 sample.HelloWorld
这个接口中,并没有定义任何的字段和方法,因此,在上述代码中没有调用 visitField()
方法和 visitMethod()
方法。
version
: 表示当前类的版本信息。在上述示例代码中,其取值为 Opcodes.V1_8
,表示使用 Java 8 版本。
access
: 表示当前类的访问标识(access flag)信息。在上面的示例中,access
的取值是 ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE
,也可以写成 ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE
。如果想进一步了解这些标识的含义,可以参考 Java Virtual Machine Specification 的 Chapter 4. The class File Format 部分。
name
: 表示当前类的名字,它采用的格式是 Internal Name 的形式。
signature
: 表示当前类的泛型信息。因为在这个接口当中不包含任何的泛型信息,因此它的值为 null
。
superName
: 表示当前类的父类信息,它采用的格式是 Internal Name 的形式。
interfaces
: 表示当前类实现了哪些接口信息。
示例二:生成接口 + 字段 + 方法 生成 HelloWorld
接口。
1 2 3 4 5 6 public interface HelloWorld extends Cloneable { int LESS = -1 ; int EQUAL = 0 ; int GREATER = 1 ; int compareTo (Object o) ; }
编码实现 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 56 package Study.ClassWriter;import cmisl.utils.FileUtils;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.FieldVisitor;import org.objectweb.asm.MethodVisitor;import static org.objectweb.asm.Opcodes.*;public class HelloWorldGenerateCore { public static void main (String[] args) throws Exception { String relative_path = "ClassFilePath/HelloWorld.class" ; String filepath = FileUtils.getFilePath(relative_path); byte [] bytes = dump(); FileUtils.writeBytes(filepath, bytes); } public static byte [] dump() throws Exception { ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES); 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(); return cw.toByteArray(); } }
visitField() 和 visitMethod() 方法
visitField (access, name, descriptor, signature, value)
visitMethod(access, name, descriptor, signature, exceptions)
这两个方法的前 4 个参数是相同的,第 5 个参数不同
access: 用于指定字段或方法的访问标识,如 ACC_PUBLIC(公共)、ACC_STATIC(静态)、ACC_FINAL(最终)等,这些标识符可以组合使用。
name: 字符串参数,表示字段或方法的名称。
descriptor: 字符串参数,表示字段或方法的描述符,这是一个特殊的字符串,用来描述字段或方法的类型和参数类型,它与Java语言中使用的类型表示法不同,例如,()V 表示无参无返回值的方法,I 表示 int 类型。
signature: 可以为 null 或具体的泛型签名字符串,用于表示字段或方法是否包含泛型信息。如果包含泛型,则需要提供具体的泛型签名;如果不包含,则提供 null。
value: 仅用于 visitField() 方法,表示字段的初始值。如果字段是一个常量,则需要提供这个常量的值;如果字段不是常量,则使用 null。
exceptions: 仅用于 visitMethod() 方法,表示方法可能抛出的异常列表。如果方法有 throws 声明,则提供异常类的完全限定名列表;如果没有,则使用 null。
描述符(descriptor)
对于字段(field)来说,描述符(descriptor)就是对字段本身的类型 进行简单化描述。
对于方法(method)来说,描述符(descriptor)就是对方法的接收参数的类型 和返回值的类型 进行简单化描述。
Java 类型
ClassFile 描述符
boolean
Z
(Z 表示 Zero,零表示 false
,非零表示 true
)
byte
B
char
C
double
D
float
F
int
I
long
J
short
S
void
V
non-array reference
L<InternalName>;
array reference
[
对字段描述符的举例:
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
示例三:生成类 生成 HelloWorld
类。
1 2 public class 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 package Study.ClassWriter;import cmisl.utils.FileUtils;import org.objectweb.asm.*;import static org.objectweb.asm.Opcodes.*;public class HelloWorldGenerateCore { public static void main (String[] args) throws Exception { String relative_path = "ClassFilePath/HelloWorld.class" ; String filepath = FileUtils.getFilePath(relative_path); byte [] bytes = dump(); FileUtils.writeBytes(filepath, bytes); } public static byte [] dump() throws Exception { ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES); 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(); return cw.toByteArray(); } }
<init>() 和 <clinit>() 方法 对于一个类(Class)来说,如果没有提供任何构造方法,Java 编译器会自动生成一个默认构造方法。在所有的 .class
文件中,构造方法的名字是 <init>()
。
另外,如果在 .class
文件中包含静态代码块,那么就会有一个 <clinit>()
方法。
1 2 3 4 5 6 7 package sample;public class HelloWorld { static { System.out.println("static code block" ); } }
上面的静态代码码,对应于 visitMethod(ACC_STATIC, "<clinit>", "()V", null, null)
的调用。
在 .class
文件中,构造方法的名字是 <init>()
,表示 instance initialization method;静态代码块的名字是 <clinit>()
,表示 class initialization method。
FieldVisitor 类 通过调用 ClassVisitor
类的 visitField()
方法,会返回一个 FieldVisitor
类型的对象。
1 public FieldVisitor visitField (int access, String name, String descriptor, String signature, Object value) ;
class info FieldVisitor
类是一个 abstract
类。
1 2 public abstract class FieldVisitor { }
fields FieldVisitor
类定义的字段。
1 2 3 4 public abstract class FieldVisitor { protected final int api; protected FieldVisitor fv; }
constructors FieldVisitor
类定义的构造方法。
1 2 3 4 5 6 7 8 9 10 public abstract class FieldVisitor { public FieldVisitor (final int api) { this (api, null ); } public FieldVisitor (final int api, final FieldVisitor fieldVisitor) { this .api = api; this .fv = fieldVisitor; } }
methods FieldVisitor
类定义的方法。
在 FieldVisitor
类当中,一共定义了 4 个 visitXxx()
方法,对于初学者的我们只需要关注其中的 visitEnd()
方法就可以了。
1 2 3 4 5 6 7 8 9 public abstract class FieldVisitor { public void visitEnd () { if (fv != null ) { fv.visitEnd(); } } }
多个 visitXxx()
方法遵循的调用顺序
1 2 3 4 5 6 ( visitAnnotation | visitTypeAnnotation | visitAttribute )* visitEnd
由于我们只关注 visitEnd()
方法,那么,这个调用顺序就变成如下这样:
示例一:字段常量 预期目标 1 2 3 4 public interface HelloWorld { int intValue = 100 ; String strValue = "ABC" ; }
编码实现 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 import cmisl.utils.FileUtils;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.FieldVisitor;import static org.objectweb.asm.Opcodes.*;public class HelloWorldGenerateCore { public static void main (String[] args) throws Exception { String relative_path = "ClassFilePath/HelloWorld.class" ; String filepath = FileUtils.getFilePath(relative_path); byte [] bytes = dump(); FileUtils.writeBytes(filepath, bytes); } public static byte [] dump() throws Exception { ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES); 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 ); fv1.visitEnd(); } { FieldVisitor fv2 = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "strValue" , "Ljava/lang/String;" , null , "ABC" ); fv2.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } }
FieldWriter 类 class info FieldWriter
类的父类是 FieldVisitor
类。FieldWriter
类并不带有 public
修饰,因此它的有效访问范围只局限于它所处的 package 当中,不能像其它的 public
类一样被外部所使用。
1 2 final class FieldWriter extends FieldVisitor { }
fields FieldWriter
类定义的字段。
1 2 3 4 5 6 final class FieldWriter extends FieldVisitor { private final int accessFlags; private final int nameIndex; private final int descriptorIndex; private Attribute firstAttribute; }
这些字段与 ClassFile 当中的 field_info
是对应的:
1 2 3 4 5 6 7 field_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
constructors FieldWriter
类定义的构造方法。在 FieldWriter
类当中,只定义了一个构造方法;同时,它也不带有 public
标识,只能在 package 内使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 final class FieldWriter extends FieldVisitor { FieldWriter(SymbolTable symbolTable, int access, String name, String descriptor, String signature, Object constantValue) { super (Opcodes.ASM9); this .symbolTable = symbolTable; this .accessFlags = access; this .nameIndex = symbolTable.addConstantUtf8(name); this .descriptorIndex = symbolTable.addConstantUtf8(descriptor); if (signature != null ) { this .signatureIndex = symbolTable.addConstantUtf8(signature); } if (constantValue != null ) { this .constantValueIndex = symbolTable.addConstant(constantValue).index; } } }
methods FieldWriter
类定义的方法。在 FieldWriter
类当中,有两个重要的方法:computeFieldInfoSize()
和 putFieldInfo()
方法。这两个方法会在 ClassWriter
类的 toByteArray()
方法内使用到。
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 final class FieldWriter extends FieldVisitor { int computeFieldInfoSize () { int size = 8 ; if (constantValueIndex != 0 ) { symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE); size += 8 ; } return size; } void putFieldInfo (final ByteVector output) { boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5; int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0 ; output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex); int attributesCount = 0 ; if (constantValueIndex != 0 ) { ++attributesCount; } output.putShort(attributesCount); if (constantValueIndex != 0 ) { output .putShort(symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE)) .putInt(2 ) .putShort(constantValueIndex); } } }
关于 FieldWriter
类的使用,它主要出现在 ClassWriter
类当中的 visitField()
和 toByteArray()
方法内。
关于Frame 1.static 方法 方法定义 1 2 3 4 5 public class HelloWorld { public static int add (int a, int b) { return a + b; } }
这是一个简单的静态方法add
,它接收两个整数参数并返回它们的和。
初始 Frame
这个表示method frame的初始状态,其中:
第一个[]
([int, int]
)表示局部变量表(locals),包含两个整数参数a
和b
。
第二个[]
表示操作数栈(operand stack),初始为空。
HelloWorldFrameTree
类输出的内容1 2 3 4 5 6 7 8 9 10 11 add:(II)I┌───────────────────────────────────────────────────────────┐ │ │ locals : 2 │ operand stack │ stacks : 0 │ ┌───────────────────────────┐ │ │ │ │ local variable │ 0 : I │ ├───────────────────────────┤ ┌─────┬─────┐ │ 1 : I │ │ │ │ 0 │ 1 │ │ │ └───────────────────────────┘ └─────┴─────┘ │ │ │ └───────────────────────────────────────────────────────────┘
这个图示表示当前方法的Frame结构:
locals: 2
:表示局部变量表包含2个变量。
stacks: 0
:表示操作数栈当前为空。
local variable
:显示局部变量表的内容,其中0
和1
对应于方法参数a
和b
。
字节码指令 使用命令 javap -c sample.HelloWorld
可以看到方法的字节码指令:
1 2 3 4 5 6 public static int add (int , int ) ; Code: 0 : iload_0 1 : iload_1 2 : iadd 3 : ireturn
这些指令分别表示:
0: iload_0
:将局部变量表中索引为0
的整数(即参数a
)加载到操作数栈上。
1: iload_1
:将局部变量表中索引为1
的整数(即参数b
)加载到操作数栈上。
2: iadd
:弹出操作数栈顶部的两个整数并将它们相加,然后将结果压回操作数栈。
3: ireturn
:从当前方法返回栈顶的整数值。
Frame 变化 1 2 3 4 5 6 add (II) I [int, int] [] [int, int] [int] [int, int] [int, int] [int, int] [int] [] []
这个表示方法执行过程中Frame的变化:
初始状态:[int, int] []
(局部变量表有a
和b
,操作数栈为空)。
iload_0
:[int, int] [int]
(a
被加载到操作数栈)。
iload_1
:[int, int] [int, int]
(b
被加载到操作数栈)。
iadd
:[int, int] [int]
(栈顶的两个整数相加,结果压回栈)。
ireturn
:[] []
(方法返回,Frame被清空)。
或者:
1 2 3 4 5 // {int , int } | {}0000 : iload_0 // {int , int } | {int }0001 : iload_1 // {int , int } | {int , int }0002 : iadd // {int , int } | {int }0003 : ireturn // {} | {}
这也是对Frame变化的另一种表示方式,注释中|
左侧表示局部变量表的内容,右侧表示操作数栈的内容。
2.non-static 方法 方法定义 1 2 3 4 5 public class HelloWorld { public int add (int a, int b) { return a + b; } }
这是一个非静态方法add
,它接收两个整数参数并返回它们的和。
初始 Frame 1 [sample /HelloWorld, int , int ] []
表示方法的初始Frame,其中:
第一个[]
([sample/HelloWorld, int, int]
)表示局部变量表(locals),包含:
this
引用(sample/HelloWorld
)
第一个int
参数(a
)
第二个int
参数(b
)
第二个[]
表示操作数栈(operand stack),初始为空。
HelloWorldFrameTree
类输出的内容1 2 3 4 5 6 7 8 9 10 11 add:(II)I┌─────────────────────────────────────────────────────────────┐ │ │ locals : 3 │ operand stack │ stacks : 0 │ ┌───────────────────────────┐ │ │ │ │ local variable │ 0 : HelloWorld │ ├───────────────────────────┤ ┌─────┬─────┬─────┐ │ 1 : I │ │ │ │ 0 │ 1 │ 2 │ │ 2 : I │ └───────────────────────────┘ └─────┴─────┴─────┘ │ │ │ └─────────────────────────────────────────────────────────────┘
这个图示表示方法的Frame结构:
locals: 3
:表示局部变量表包含3个位置。
stacks: 0
:表示操作数栈当前为空。
local variable
:显示局部变量表的内容,其中0
是this
引用,1
是第一个int
参数a
,2
是第二个int
参数b
。
字节码指令 方法的字节码指令如下:
1 2 3 4 5 6 public int add (int , int ) ; Code: 0 : iload_1 1 : iload_2 2 : iadd 3 : ireturn
这些指令分别表示:
0: iload_1
:将局部变量表中索引为1
的位置(即第一个int
参数a
)加载到操作数栈上。
1: iload_2
:将局部变量表中索引为2
的位置(即第二个int
参数b
)加载到操作数栈上。
2: iadd
:弹出操作数栈顶部的两个整数并将它们相加,然后将结果压回操作数栈。
3: ireturn
:从当前方法返回栈顶的整数值。
Frame 变化 1 2 3 4 5 6 add (II) I [sample/HelloWorld, int, int] [] [sample/HelloWorld, int, int] [int] [sample/HelloWorld, int, int] [int, int] [sample/HelloWorld, int, int] [int] [] []
这个表示方法执行过程中Frame的变化:
初始状态:[sample/HelloWorld, int, int] []
(局部变量表有this
、a
和b
,操作数栈为空)。
iload_1
:[sample/HelloWorld, int, int] [int]
(a
被加载到操作数栈)。
iload_2
:[sample/HelloWorld, int, int] [int, int]
(b
被加载到操作数栈)。
iadd
:[sample/HelloWorld, int, int] [int]
(栈顶的两个整数相加,结果压回栈)。
ireturn
:[] []
(方法返回,Frame被清空)。
或者:
1 2 3 4 5 // {this, int , int } | {}0000 : iload_1 // {this, int , int } | {int }0001 : iload_2 // {this, int , int } | {int , int }0002 : iadd // {this, int , int } | {int }0003 : ireturn // {} | {}
这也是对Frame变化的另一种表示方式,注释中|
左侧表示局部变量表的内容,右侧表示操作数栈的内容。
3.long 和 double 类型 这段内容描述了一个Java非静态方法add(long, long)
的执行过程及其字节码指令对应的局部变量表(locals)和操作数栈(operand stack)的变化。下面是对这段内容的详细解释:
方法定义 1 2 3 4 5 public class HelloWorld { public long add (long a, long b) { return a + b; } }
这是一个非静态方法add
,它接收两个长整型参数并返回它们的和。
初始 Frame 1 [sample/HelloWorld, long , top, long , top] []
表示方法的初始Frame,其中:
第一个[]
([sample/HelloWorld, long, top, long, top]
)表示局部变量表(locals),包含:
this
引用(sample/HelloWorld
)
第一个long
参数(占用两个位置,long
和top
)
第二个long
参数(占用两个位置,long
和top
)
第二个[]
表示操作数栈(operand stack),初始为空。
HelloWorldFrameTree
类输出的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 add:(JJ)J┌───────────────────────────────────────────────────────────────────────────┐ │ │ locals : 5 │ operand stack │ stacks : 0 │ ┌───────────────────────────┐ │ │ │ │ │ 0 : HelloWorld │ ├───────────────────────────┤ │ 1 : J │ │ │ │ 2 : . │ ├───────────────────────────┤ │ 3 : J │ │ │ local variable │ 4 : . │ ├───────────────────────────┤ ┌─────┬─────┬─────┬─────┬─────┐ │ │ │ │ │ 0 │ 1 │ 2 │ 3 │ 4 │ │ │ └───────────────────────────┘ └─────┴─────┴─────┴─────┴─────┘ │ │ │ └───────────────────────────────────────────────────────────────────────────┘
这个图示表示方法的Frame结构:
locals: 5
:表示局部变量表包含5个位置。
stacks: 0
:表示操作数栈当前为空。
local variable
:显示局部变量表的内容,其中0
是this
引用,1
和2
是第一个long
参数,3
和4
是第二个long
参数。
字节码指令 方法的字节码指令如下:
1 2 3 4 5 6 public long add (long , long ) ; Code: 0 : lload_1 1 : lload_3 2 : ladd 3 : lreturn
这些指令分别表示:
0: lload_1
:将局部变量表中索引为1
和2
的位置(即第一个long
参数)加载到操作数栈上。
1: lload_3
:将局部变量表中索引为3
和4
的位置(即第二个long
参数)加载到操作数栈上。
2: ladd
:弹出操作数栈顶部的两个long
值并将它们相加,然后将结果压回操作数栈。
3: lreturn
:从当前方法返回栈顶的long
值。
Frame 变化 1 2 3 4 5 6 add (JJ) J [sample/HelloWorld, long, top, long, top] [] [sample/HelloWorld, long, top, long, top] [long, top] [sample/HelloWorld, long, top, long, top] [long, top, long, top] [sample/HelloWorld, long, top, long, top] [long, top] [] []
这个表示方法执行过程中Frame的变化:
初始状态:[sample/HelloWorld, long, top, long, top] []
(局部变量表有this
、a
和b
,操作数栈为空)。
lload_1
:[sample/HelloWorld, long, top, long, top] [long, top]
(第一个long
参数被加载到操作数栈)。
lload_3
:[sample/HelloWorld, long, top, long, top] [long, top, long, top]
(第二个long
参数被加载到操作数栈)。
ladd
:[sample/HelloWorld, long, top, long, top] [long, top]
(栈顶的两个long
值相加,结果压回栈)。
lreturn
:[] []
(方法返回,Frame被清空)。
或者:
1 2 3 4 5 0000 : lload_1 // {this , long , top, long , top} | {long , top}0001 : lload_3 // {this , long , top, long , top} | {long , top, long , top}0002 : ladd // {this , long , top, long , top} | {long , top}0003 : lreturn // {} | {}
这也是对Frame变化的另一种表示方式,注释中|
左侧表示局部变量表的内容,右侧表示操作数栈的内容。
MethodVisitor 类 class info MethodVisitor
类是一个 abstract
类。
1 2 public abstract class MethodVisitor { }
fields MethodVisitor
类定义的字段。
1 2 3 4 public abstract class MethodVisitor { protected final int api; protected MethodVisitor mv; }
constructors MethodVisitor
类定义的构造方法。
1 2 3 4 5 6 7 8 9 10 public abstract class MethodVisitor { public MethodVisitor (final int api) { this (api, null ); } public MethodVisitor (final int api, final MethodVisitor methodVisitor) { this .api = api; this .mv = methodVisitor; } }
methods MethodVisitor
类定义的方法。
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 public abstract class MethodVisitor { public void visitCode () ; public void visitInsn (final int opcode) ; public void visitIntInsn (final int opcode, final int operand) ; public void visitVarInsn (final int opcode, final int var ) ; public void visitTypeInsn (final int opcode, final String type) ; public void visitFieldInsn (final int opcode, final String owner, final String name, final String descriptor) ; public void visitMethodInsn (final int opcode, final String owner, final String name, final String descriptor, final boolean isInterface) ; public void visitInvokeDynamicInsn (final String name, final String descriptor, final Handle bootstrapMethodHandle, final Object... bootstrapMethodArguments) ; public void visitJumpInsn (final int opcode, final Label label) ; public void visitLabel (final Label label) ; public void visitLdcInsn (final Object value) ; public void visitIincInsn (final int var , final int increment) ; public void visitTableSwitchInsn (final int min, final int max, final Label dflt, final Label... labels) ; public void visitLookupSwitchInsn (final Label dflt, final int [] keys, final Label[] labels) ; public void visitMultiANewArrayInsn (final String descriptor, final int numDimensions) ; public void visitTryCatchBlock (final Label start, final Label end, final Label handler, final String type) ; public void visitMaxs (final int maxStack, final int maxLocals) ; public void visitEnd () ; }
对于这些 visitXxx()
方法,可以从以下资料参阅:
在 MethodVisitor
类当中,定义了许多的 visitXxx()
方法,这些方法的调用,也要遵循一定的顺序。
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()
方法,是最后一个进行调用的方法。
对这些 visitXxx()
方法进行精简之后,内容如下:
1 2 3 4 5 6 7 8 9 10 11 [ visitCode ( visitFrame | visitXxxInsn | visitLabel | visitTryCatchBlock ) * visitMaxs ] visitEnd
这些方法的调用顺序,可以记忆如下:
第一步,调用 visitCode()
方法,调用一次。
第二步,调用 visitXxxInsn()
方法,可以调用多次。对这些方法的调用,就是在构建方法的“方法体”。
第三步,调用 visitMaxs()
方法,调用一次。
第四步,调用 visitEnd()
方法,调用一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 ┌─── visitCode () │ │ ┌─── visitXxxInsn () │ │ │ ├─── visitLabel () ├─── visitXxxInsn () ───┤ MethodVisitor ───┤ ├─── visitFrame () │ │ │ └─── visitTryCatchBlock () │ ├─── visitMaxs () │ └─── visitEnd ()
示例一:<init>() 方法 在 .class
文件中,构造方法的名字是 <init>
,它表示 instance init ialization method 的缩写。
编写代码实现以下.class文件
1 2 public class HelloWorld { }
或者:
1 2 3 4 5 public class HelloWorld { public HelloWorld () { super (); } }
编码实现 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 package Study.MethodVisitor;import cmisl.utils.FileUtils;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.MethodVisitor;import static org.objectweb.asm.Opcodes.*;public class HelloWorldGenerateCore { public static void main (String[] args) throws Exception { String relative_path = "ClassFilePath/HelloWorld.class" ; String filepath = FileUtils.getFilePath(relative_path); byte [] bytes = dump(); FileUtils.writeBytes(filepath, bytes); } public static byte [] dump() throws Exception { ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES); cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld" , null , "java/lang/Object" , null ); { MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>" , "()V" , null , null ); mv1.visitCode(); mv1.visitVarInsn(ALOAD, 0 ); mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object" , "<init>" , "()V" , false ); mv1.visitInsn(RETURN); mv1.visitMaxs(1 , 1 ); mv1.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } }
示例二:<clinit> 方法 在 .class
文件中,静态初始化方法的名字是 <clinit>
,它表示cl ass init ialization method 的缩写。它的方法描述符是 ()V
1 2 3 4 5 public class HelloWorld { static { System.out.println("class initialization method" ); } }
编码实现 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 package Study.MethodVisitor;import cmisl.utils.FileUtils;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.MethodVisitor;import static org.objectweb.asm.Opcodes.*;public class HelloWorldGenerateCore { public static void main (String[] args) throws Exception { String relative_path = "ClassFilePath/HelloWorld.class" ; String filepath = FileUtils.getFilePath(relative_path); byte [] bytes = dump(); FileUtils.writeBytes(filepath, bytes); } public static byte [] dump() throws Exception { ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES); cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld" , null , "java/lang/Object" , null ); { MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>" , "()V" , null , null ); mv1.visitCode(); mv1.visitVarInsn(ALOAD, 0 ); mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object" , "<init>" , "()V" , false ); mv1.visitInsn(RETURN); mv1.visitMaxs(1 , 1 ); mv1.visitEnd(); } { MethodVisitor mv2 = cw.visitMethod(ACC_STATIC, "<clinit>" , "()V" , null , null ); mv2.visitCode(); mv2.visitFieldInsn(GETSTATIC, "java/lang/System" , "out" , "Ljava/io/PrintStream;" ); mv2.visitLdcInsn("class initialization method" ); mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream" , "println" , "(Ljava/lang/String;)V" , false ); mv2.visitInsn(RETURN); mv2.visitMaxs(2 , 0 ); mv2.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } }
示例三:创建对象 假如有一个 GoodChild
类,内容如下:
1 2 3 4 5 6 7 8 9 public class GoodChild { public String name; public int age; public GoodChild (String name, int age) { this .name = name; this .age = age; } }
我们的预期目标是生成一个 HelloWorld
类:
1 2 3 4 5 public class HelloWorld { public void test () { GoodChild child = new GoodChild ("Lucy" , 8 ); } }
编码实现 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 56 57 package Study.MethodVisitor;import cmisl.utils.FileUtils;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.MethodVisitor;import static org.objectweb.asm.Opcodes.*;public class HelloWorldGenerateCore { public static void main (String[] args) throws Exception { String relative_path = "ClassFilePath/HelloWorld.class" ; String filepath = FileUtils.getFilePath(relative_path); byte [] bytes = dump(); FileUtils.writeBytes(filepath, bytes); } public static byte [] dump() throws Exception { ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES); cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld" , null , "java/lang/Object" , null ); { MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>" , "()V" , null , null ); mv1.visitCode(); mv1.visitVarInsn(ALOAD, 0 ); mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object" , "<init>" , "()V" , false ); mv1.visitInsn(RETURN); mv1.visitMaxs(1 , 1 ); mv1.visitEnd(); } { MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test" , "()V" , null , null ); mv2.visitCode(); mv2.visitTypeInsn(NEW, "sample/GoodChild" ); mv2.visitInsn(DUP); mv2.visitLdcInsn("Lucy" ); mv2.visitIntInsn(BIPUSH, 8 ); mv2.visitMethodInsn(INVOKESPECIAL, "sample/GoodChild" , "<init>" , "(Ljava/lang/String;I)V" , false ); mv2.visitVarInsn(ASTORE, 1 ); mv2.visitInsn(RETURN); mv2.visitMaxs(4 , 2 ); mv2.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } }
示例四:调用方法 1 2 3 4 5 6 public class HelloWorld { public void test (int a, int b) { int val = Math.max(a, b); System.out.println(val); } }
编码实现 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 56 57 58 package Study.MethodVisitor;import cmisl.utils.FileUtils;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.MethodVisitor;import static org.objectweb.asm.Opcodes.*;public class HelloWorldGenerateCore { public static void main (String[] args) throws Exception { String relative_path = "ClassFilePath/HelloWorld.class" ; String filepath = FileUtils.getFilePath(relative_path); byte [] bytes = dump(); FileUtils.writeBytes(filepath, bytes); } public static byte [] dump() throws Exception { ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES); cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld" , null , "java/lang/Object" , null ); { MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>" , "()V" , null , null ); mv1.visitCode(); mv1.visitVarInsn(ALOAD, 0 ); mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object" , "<init>" , "()V" , false ); mv1.visitInsn(RETURN); mv1.visitMaxs(1 , 1 ); mv1.visitEnd(); } { MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test" , "(II)V" , null , null ); mv2.visitCode(); mv2.visitVarInsn(ILOAD, 1 ); mv2.visitVarInsn(ILOAD, 2 ); mv2.visitMethodInsn(INVOKESTATIC, "java/lang/Math" , "max" , "(II)I" , false ); mv2.visitVarInsn(ISTORE, 3 ); mv2.visitFieldInsn(GETSTATIC, "java/lang/System" , "out" , "Ljava/io/PrintStream;" ); mv2.visitVarInsn(ILOAD, 3 ); mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream" , "println" , "(I)V" , false ); mv2.visitInsn(RETURN); mv2.visitMaxs(2 , 4 ); mv2.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } }
上面代码如果看不懂,以下是ai解释
这段代码利用ASM库生成了一个Java类,并为该类定义了两个方法:一个构造方法和一个名为test
的方法。下面我们逐步解释每一部分的代码。
1. 构造方法 1 2 3 4 5 6 7 8 9 >{ >MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>" , "()V" , null , null ); >mv1.visitCode(); >mv1.visitVarInsn(ALOAD, 0 ); >mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object" , "<init>" , "()V" , false ); >mv1.visitInsn(RETURN); >mv1.visitMaxs(1 , 1 ); >mv1.visitEnd(); >}
1.1 创建构造方法 1 >MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>" , "()V" , null , null );
创建一个方法访问器,用来生成新的构造方法。
ACC_PUBLIC
: 构造方法的访问权限是public
。
"<init>"
: 构造方法的名称。
"()V"
: 构造方法的描述符,表示无参数且返回类型为void
.
null, null
: 不设置方法的签名和异常。
1.2 生成方法代码
1.3 调用父类构造方法 1 2 >mv1.visitVarInsn(ALOAD, 0 ); >mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object" , "<init>" , "()V" , false );
ALOAD 0
: 将this
引用加载到操作数栈上(局部变量索引0)。
INVOKESPECIAL
: 调用父类的实例初始化方法。
"java/lang/Object"
: 类是java.lang.Object
。
"<init>"
: 方法名是<init>
。
"()V"
: 方法描述符,无参数且返回类型为void
。
false
: 不是接口方法。
1.4 返回
1.5 设置最大堆栈深度和局部变量表大小
1
: 方法执行期间操作数栈的最大深度是1。
1
: 方法的局部变量表大小是1(this
引用)。
1.6 结束方法生成
2. test
方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 >{ MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test" , "(II)V" , null , null ); mv2.visitCode(); mv2.visitVarInsn(ILOAD, 1 ); mv2.visitVarInsn(ILOAD, 2 ); mv2.visitMethodInsn(INVOKESTATIC, "java/lang/Math" , "max" , "(II)I" , false ); mv2.visitVarInsn(ISTORE, 3 ); mv2.visitFieldInsn(GETSTATIC, "java/lang/System" , "out" , "Ljava/io/PrintStream;" ); mv2.visitVarInsn(ILOAD, 3 ); mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream" , "println" , "(I)V" , false ); mv2.visitInsn(RETURN); mv2.visitMaxs(2 , 4 ); mv2.visitEnd(); >}
2.1 创建test
方法 1 >MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test" , "(II)V" , null , null );
创建一个方法访问器,用来生成新的test
方法。
ACC_PUBLIC
: 方法的访问权限是public
。
"test"
: 方法名是test
。
"(II)V"
: 方法描述符,表示接受两个int
参数且返回类型为void
。
null, null
: 不设置方法的签名和异常。
2.2 生成方法代码
2.3 加载方法参数 1 2 >mv2.visitVarInsn(ILOAD, 1 ); >mv2.visitVarInsn(ILOAD, 2 );
ILOAD 1
: 将第一个参数(局部变量索引1)加载到操作数栈。
ILOAD 2
: 将第二个参数(局部变量索引2)加载到操作数栈。
2.4 调用Math.max
方法 1 >mv2.visitMethodInsn(INVOKESTATIC, "java/lang/Math" , "max" , "(II)I" , false );
INVOKESTATIC
: 调用静态方法。
"java/lang/Math"
: 类是java.lang.Math
。
"max"
: 方法名是max
。
"(II)I"
: 方法描述符,接受两个int
参数且返回一个int
。
false
: 不是接口方法。
将两个整数从操作数栈中弹出,计算它们的最大值,并将结果压回操作数栈。
2.5 存储结果 1 >mv2.visitVarInsn(ISTORE, 3 );
ISTORE 3
: 将操作数栈顶部的值存储到局部变量表索引3的位置。
2.6 获取System.out
输出流 1 >mv2.visitFieldInsn(GETSTATIC, "java/lang/System" , "out" , "Ljava/io/PrintStream;" );
GETSTATIC
: 获取静态字段的值。
"java/lang/System"
: 类是java.lang.System
。
"out"
: 字段名是out
。
"Ljava/io/PrintStream;"
: 字段类型是java.io.PrintStream
。
将System.out
压入操作数栈。
2.7 加载结果 1 >mv2.visitVarInsn(ILOAD, 3 );
ILOAD 3
: 将局部变量表索引3的值加载到操作数栈。
2.8 调用println
方法 1 >mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream" , "println" , "(I)V" , false );
INVOKEVIRTUAL
: 调用实例方法。
"java/io/PrintStream"
: 类是java.io.PrintStream
。
"println"
: 方法名是println
。
"(I)V"
: 方法描述符,接受一个int
参数且返回void
。
false
: 不是接口方法。
从操作数栈中弹出值并调用println
方法,打印最大值。
2.9 返回
2.10 设置最大堆栈深度和局部变量表大小
2
: 方法执行期间操作数栈的最大深度是2。
4
: 方法的局部变量表大小是4(this
、两个参数和一个局部变量)。
2.11 结束方法生成
总结 这段代码利用ASM库生成了一个类,并为该类定义了以下两个方法:
构造方法 :
访问权限:public
名称:<init>
描述符:()V
(无参数且返回类型为void
)
功能:调用父类java.lang.Object
的构造方法。
test
方法 :
访问权限:public
名称:test
描述符:(II)V
(接受两个int
参数且返回类型为void
)
功能:计算两个整数中的最大值,并将结果打印到控制台。
通过设置最大操作数栈深度和局部变量表大小,确保生成的字节码能够被JVM正确加载和执行。
更细节的如下
当然可以!我们可以通过模拟test
方法的执行过程,来帮助你理解每一步指令的作用。这是一个很好的方式来深入理解如何通过ASM生成字节码。
test
方法回顾1 2 3 4 5 6 7 8 9 10 11 12 13 14 >{ MethodVisitor mv2 = cw.visitMethod(Opcodes.ACC_PUBLIC, "test" , "(II)V" , null , null ); mv2.visitCode(); mv2.visitVarInsn(Opcodes.ILOAD, 1 ); mv2.visitVarInsn(Opcodes.ILOAD, 2 ); mv2.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Math" , "max" , "(II)I" , false ); mv2.visitVarInsn(Opcodes.ISTORE, 3 ); mv2.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System" , "out" , "Ljava/io/PrintStream;" ); mv2.visitVarInsn(Opcodes.ILOAD, 3 ); mv2.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream" , "println" , "(I)V" , false ); mv2.visitInsn(Opcodes.RETURN); mv2.visitMaxs(2 , 4 ); mv2.visitEnd(); >}
我们将逐行分析这段方法,并模拟其执行过程。
初始状态 在方法调用开始时,局部变量表和操作数栈的状态如下:
局部变量表
索引
变量
0
this
1
int a
2
int b
3
未使用
操作数栈 为空。
步骤 1:加载第一个参数(a
) 1 >mv2.visitVarInsn(Opcodes.ILOAD, 1 );
局部变量表
索引
变量
0
this
1
int a
2
int b
3
未使用
操作数栈
步骤 2:加载第二个参数(b
) 1 >mv2.visitVarInsn(Opcodes.ILOAD, 2 );
局部变量表
索引
变量
0
this
1
int a
2
int b
3
未使用
操作数栈
步骤 3:调用Math.max
方法 1 >mv2.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Math" , "max" , "(II)I" , false );
该指令从操作数栈中弹出两个整数a
和b
。
计算它们的最大值。
将结果压回操作数栈。
假设a = 5
,b = 3
,则最大值是5
。
局部变量表
索引
变量
0
this
1
int a
2
int b
3
未使用
操作数栈
步骤 4:将结果存储到局部变量 1 >mv2.visitVarInsn(Opcodes.ISTORE, 3 );
局部变量表
索引
变量
0
this
1
int a
2
int b
3
int 5
操作数栈 为空。
步骤 5:获取System.out
输出流 1 >mv2.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System" , "out" , "Ljava/io/PrintStream;" );
获取System.out
的静态字段值,并将其压入操作数栈。
局部变量表
索引
变量
0
this
1
int a
2
int b
3
int 5
操作数栈
步骤 6:加载局部变量中的结果 1 >mv2.visitVarInsn(Opcodes.ILOAD, 3 );
局部变量表
索引
变量
0
this
1
int a
2
int b
3
int 5
操作数栈
步骤 7:调用println
方法 1 >mv2.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream" , "println" , "(I)V" , false );
从操作数栈中弹出两个操作数:一个PrintStream
对象和一个int
。
调用PrintStream
对象的println
方法,打印整数值。
局部变量表
索引
变量
0
this
1
int a
2
int b
3
int 5
操作数栈 为空。
步骤 8:方法返回 1 >mv2.visitInsn(Opcodes.RETURN);
步骤 9:设置最大堆栈深度和局部变量表大小
2
: 方法执行期间操作数栈的最大深度是2。
4
: 方法的局部变量表大小是4(this
、两个参数和一个局部变量)。
步骤 10:结束方法生成
总结 通过逐步模拟test
方法的执行,我们可以看到每一条字节码指令如何操作局部变量表和操作数栈。希望这个详细的模拟过程能帮助你更好地理解字节码生成的过程。如果你有任何问题,随时提问!
示例五:不调用 visitMaxs() 方法 在创建 ClassWriter
对象时,使用了 ClassWriter.COMPUTE_FRAMES
选项。
1 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES)
使用 ClassWriter.COMPUTE_FRAMES
后,ASM 会自动计算 max stacks、max locals 和 stack map frames 的具体值。 从代码的角度来说,使用 ClassWriter.COMPUTE_FRAMES
,会忽略我们在代码中 visitMaxs()
方法和 visitFrame()
方法传入的具体参数值。 换句话说,无论我们传入的参数值是否正确,ASM 会帮助我们从新计算一个正确的值,代替我们在代码中传入的参数。
第 1 种情况,在创建 ClassWriter
对象时,flags
参数使用 ClassWriter.COMPUTE_FRAMES
值,在调用 mv.visitMaxs(0, 0)
方法之后,仍然能得到一个正确的 .class
文件。
第 2 种情况,在创建 ClassWriter
对象时,flags
参数使用 0
值,在调用 mv.visitMaxs(0, 0)
方法之后,得到的 .class
文件就不能正确运行。
需要注意的是,在创建 ClassWriter
对象时,flags
参数使用 ClassWriter.COMPUTE_FRAMES
值,我们可以给 visitMaxs()
方法传入一个错误的值,但是不能省略对于 visitMaxs()
方法的调用。 如果我们省略掉 visitCode()
和 visitEnd()
方法,生成的 .class
文件也不会出错;当然,并不建议这么做。但是,如果我们省略掉对于 visitMaxs()
方法的调用,生成的 .class
文件就会出错。
如果省略掉对于 visitMaxs()
方法的调用,会出现如下错误:
1 Exception in thread "main" java.lang.VerifyError: Operand stack overflow
示例六:不同的 MethodVisitor 交叉使用 假如我们有两个 MethodVisitor
对象 mv1
和 mv2
,如下所示:
1 2 MethodVisitor mv1 = cw.visitMethod(...) MethodVisitor mv2 = cw.visitMethod(...)
同时,我们也知道 MethodVisitor
类里的 visitXxx()
方法需要遵循一定的调用顺序:
第一步,调用 visitCode()
方法,调用一次
第二步,调用 visitXxxInsn()
方法,可以调用多次
第三步,调用 visitMaxs()
方法,调用一次
第四步,调用 visitEnd()
方法,调用一次
对于 mv1
和 mv2
这两个对象来说,它们的 visitXxx()
方法的调用顺序是彼此独立的、不会相互干扰。
一般情况下,我们可以如下写代码,这样逻辑比较清晰:
1 2 3 4 5 6 7 8 9 10 11 MethodVisitor mv1 = cw.visitMethod(...) mv1.visitCode(...) mv1.visitXxxInsn(...) mv1.visitMaxs(...) mv1.visitEnd() MethodVisitor mv2 = cw.visitMethod(...) mv2.visitCode(...) mv2.visitXxxInsn(...) mv2.visitMaxs(...) mv2.visitEnd()
但是,我们也可以这样来写代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 MethodVisitor mv1 = cw.visitMethod(...);MethodVisitor mv2 = cw.visitMethod(...); mv1.visitCode(...); mv2.visitCode(...); mv2.visitXxxInsn(...) mv1.visitXxxInsn(...) mv1.visitMaxs(...); mv1.visitEnd(); mv2.visitMaxs(...); mv2.visitEnd();
在上面的代码中,mv1
和 mv2
这两个对象的 visitXxx()
方法交叉调用,这是可以的。 换句话说,只要每一个 MethodVisitor
对象在调用 visitXxx()
方法时,遵循了调用顺序,那结果就是正确的; 不同的 MethodVisitor
对象,是相互独立的、不会彼此影响。
那么,可能有的同学会问:MethodVisitor
对象交叉使用有什么作用呢?有没有什么场景下的应用呢?回答是“有的”。 在 ASM 当中,有一个 org.objectweb.asm.commons.StaticInitMerger
类,其中有一个 MethodVisitor mergedClinitVisitor
字段,它就是一个很好的示例,在后续内容中,我们会介绍到这个类。
预期目标 1 2 3 4 5 6 7 8 9 10 11 12 import java.util.Date;public class HelloWorld { public void test () { System.out.println("This is a test method." ); } public void printDate () { Date now = new Date (); System.out.println(now); } }
编码实现(第一种方式,顺序) 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 56 57 58 59 60 61 62 63 64 65 66 67 68 import cmisl.utils.FileUtils;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.MethodVisitor;import static org.objectweb.asm.Opcodes.*;public class HelloWorldGenerateCore { public static void main (String[] args) throws Exception { String relative_path = "sample/HelloWorld.class" ; String filepath = FileUtils.getFilePath(relative_path); byte [] bytes = dump(); FileUtils.writeBytes(filepath, bytes); } public static byte [] dump() throws Exception { ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES); cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld" , null , "java/lang/Object" , null ); { MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>" , "()V" , null , null ); mv1.visitCode(); mv1.visitVarInsn(ALOAD, 0 ); mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object" , "<init>" , "()V" , false ); mv1.visitInsn(RETURN); mv1.visitMaxs(1 , 1 ); mv1.visitEnd(); } { MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test" , "()V" , null , null ); mv2.visitCode(); mv2.visitFieldInsn(GETSTATIC, "java/lang/System" , "out" , "Ljava/io/PrintStream;" ); mv2.visitLdcInsn("This is a test method." ); mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream" , "println" , "(Ljava/lang/String;)V" , false ); mv2.visitInsn(RETURN); mv2.visitMaxs(2 , 1 ); mv2.visitEnd(); } { MethodVisitor mv3 = cw.visitMethod(ACC_PUBLIC, "printDate" , "()V" , null , null ); mv3.visitCode(); mv3.visitTypeInsn(NEW, "java/util/Date" ); mv3.visitInsn(DUP); mv3.visitMethodInsn(INVOKESPECIAL, "java/util/Date" , "<init>" , "()V" , false ); mv3.visitVarInsn(ASTORE, 1 ); mv3.visitFieldInsn(GETSTATIC, "java/lang/System" , "out" , "Ljava/io/PrintStream;" ); mv3.visitVarInsn(ALOAD, 1 ); mv3.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream" , "println" , "(Ljava/lang/Object;)V" , false ); mv3.visitInsn(RETURN); mv3.visitMaxs(2 , 2 ); mv3.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } }
编码实现(第二种方式,交叉) 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 import cmisl.utils.FileUtils;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.MethodVisitor;import static org.objectweb.asm.Opcodes.*;public class HelloWorldGenerateCore { public static void main (String[] args) throws Exception { String relative_path = "sample/HelloWorld.class" ; String filepath = FileUtils.getFilePath(relative_path); byte [] bytes = dump(); FileUtils.writeBytes(filepath, bytes); } public static byte [] dump() throws Exception { ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES); cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld" , null , "java/lang/Object" , null ); { MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>" , "()V" , null , null ); mv1.visitCode(); mv1.visitVarInsn(ALOAD, 0 ); mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object" , "<init>" , "()V" , false ); mv1.visitInsn(RETURN); mv1.visitMaxs(1 , 1 ); mv1.visitEnd(); } { MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test" , "()V" , null , null ); MethodVisitor mv3 = cw.visitMethod(ACC_PUBLIC, "printDate" , "()V" , null , null ); mv3.visitCode(); mv3.visitTypeInsn(NEW, "java/util/Date" ); mv3.visitInsn(DUP); mv3.visitMethodInsn(INVOKESPECIAL, "java/util/Date" , "<init>" , "()V" , false ); mv2.visitCode(); mv2.visitFieldInsn(GETSTATIC, "java/lang/System" , "out" , "Ljava/io/PrintStream;" ); mv2.visitLdcInsn("This is a test method." ); mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream" , "println" , "(Ljava/lang/String;)V" , false ); mv3.visitVarInsn(ASTORE, 1 ); mv3.visitFieldInsn(GETSTATIC, "java/lang/System" , "out" , "Ljava/io/PrintStream;" ); mv3.visitVarInsn(ALOAD, 1 ); mv3.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream" , "println" , "(Ljava/lang/Object;)V" , false ); mv2.visitInsn(RETURN); mv2.visitMaxs(2 , 1 ); mv2.visitEnd(); mv3.visitInsn(RETURN); mv3.visitMaxs(2 , 2 ); mv3.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } }
MethodWriter 类 class info MethodWriter
类的父类是 MethodVisitor
类。MethodWriter
类并不带有 public
修饰,因此它的有效访问范围只局限于它所处的 package 当中,不能像其它的 public
类一样被外部所使用。
1 2 final class MethodWriter extends MethodVisitor { }
fields MethodWriter
类定义的字段。
1 2 3 4 5 6 7 8 final class MethodWriter extends MethodVisitor { private final int accessFlags; private final int nameIndex; private final String name; private final int descriptorIndex; private final String descriptor; private Attribute firstAttribute; }
这些字段与 ClassFile
当中的 method_info
也是对应的:
1 2 3 4 5 6 7 method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
下面的几个字段,是与“方法体”直接相关的几个字段:
1 2 3 4 5 6 7 8 9 10 final class MethodWriter extends MethodVisitor { private int maxStack; private int maxLocals; private final ByteVector code = new ByteVector (); private Handler firstHandler; private Handler lastHandler; private final int numberOfExceptions; private final int [] exceptionIndexTable; private Attribute firstCodeAttribute; }
这些字段对应于 Code
属性结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Code_attribute { u2 attribute_name_index; u4 attribute_length; u2 max_stack; u2 max_locals; u4 code_length; u1 code[code_length]; u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attributes_count]; }
constructors MethodWriter
类定义的构造方法。
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 final class MethodWriter extends MethodVisitor { MethodWriter(SymbolTable symbolTable, int access, String name, String descriptor, String signature, String[] exceptions, int compute) { super (Opcodes.ASM9); this .symbolTable = symbolTable; this .accessFlags = "<init>" .equals(name) ? access | Constants.ACC_CONSTRUCTOR : access; this .nameIndex = symbolTable.addConstantUtf8(name); this .name = name; this .descriptorIndex = symbolTable.addConstantUtf8(descriptor); this .descriptor = descriptor; this .signatureIndex = signature == null ? 0 : symbolTable.addConstantUtf8(signature); if (exceptions != null && exceptions.length > 0 ) { numberOfExceptions = exceptions.length; this .exceptionIndexTable = new int [numberOfExceptions]; for (int i = 0 ; i < numberOfExceptions; ++i) { this .exceptionIndexTable[i] = symbolTable.addConstantClass(exceptions[i]).index; } } else { numberOfExceptions = 0 ; this .exceptionIndexTable = null ; } this .compute = compute; if (compute != COMPUTE_NOTHING) { int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2 ; if ((access & Opcodes.ACC_STATIC) != 0 ) { --argumentsSize; } maxLocals = argumentsSize; currentLocals = argumentsSize; firstBasicBlock = new Label (); visitLabel(firstBasicBlock); } } }
methods MethodWriter
类定义的方法。
在 MethodWriter
类当中,也有两个重要的方法:computeMethodInfoSize()
和 putMethodInfo()
方法。这两个方法也是在 ClassWriter
类的 toByteArray()
方法内使用到。
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 final class MethodWriter extends MethodVisitor { int computeMethodInfoSize () { int size = 8 ; if (code.length > 0 ) { if (code.length > 65535 ) { throw new MethodTooLargeException (symbolTable.getClassName(), name, descriptor, code.length); } symbolTable.addConstantUtf8(Constants.CODE); size += 16 + code.length + Handler.getExceptionTableSize(firstHandler); if (stackMapTableEntries != null ) { boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6; symbolTable.addConstantUtf8(useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap" ); size += 8 + stackMapTableEntries.length; } } if (numberOfExceptions > 0 ) { symbolTable.addConstantUtf8(Constants.EXCEPTIONS); size += 8 + 2 * numberOfExceptions; } return size; } void putMethodInfo (final ByteVector output) { boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5; int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0 ; output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex); int attributeCount = 0 ; if (code.length > 0 ) { ++attributeCount; } if (numberOfExceptions > 0 ) { ++attributeCount; } output.putShort(attributeCount); if (code.length > 0 ) { int size = 10 + code.length + Handler.getExceptionTableSize(firstHandler); int codeAttributeCount = 0 ; if (stackMapTableEntries != null ) { size += 8 + stackMapTableEntries.length; ++codeAttributeCount; } output .putShort(symbolTable.addConstantUtf8(Constants.CODE)) .putInt(size) .putShort(maxStack) .putShort(maxLocals) .putInt(code.length) .putByteArray(code.data, 0 , code.length); Handler.putExceptionTable(firstHandler, output); output.putShort(codeAttributeCount); } if (numberOfExceptions > 0 ) { output .putShort(symbolTable.addConstantUtf8(Constants.EXCEPTIONS)) .putInt(2 + 2 * numberOfExceptions) .putShort(numberOfExceptions); for (int exceptionIndex : exceptionIndexTable) { output.putShort(exceptionIndex); } } } }
关于 MethodWriter
类的使用,在 ClassWriter
类当中,visitMethod()
方法代码如下:
1 2 3 4 5 6 7 8 9 10 11 public class ClassWriter extends ClassVisitor { public final MethodVisitor visitMethod (int access, String name, String descriptor, String signature, String[] exceptions) { MethodWriter methodWriter = new MethodWriter (symbolTable, access, name, descriptor, signature, exceptions, compute); if (firstMethod == null ) { firstMethod = methodWriter; } else { lastMethod.mv = methodWriter; } return lastMethod = methodWriter; } }
toByteArray 方法
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 public class ClassWriter extends ClassVisitor { public byte [] toByteArray() { int size = 24 + 2 * interfaceCount; int methodsCount = 0 ; MethodWriter methodWriter = firstMethod; while (methodWriter != null ) { ++methodsCount; size += methodWriter.computeMethodInfoSize(); methodWriter = (MethodWriter) methodWriter.mv; } ByteVector result = new ByteVector (size); result.putInt(0xCAFEBABE ).putInt(version); symbolTable.putConstantPool(result); int mask = (version & 0xFFFF ) < Opcodes.V1_5 ? Opcodes.ACC_SYNTHETIC : 0 ; result.putShort(accessFlags & ~mask).putShort(thisClass).putShort(superClass); result.putShort(interfaceCount); for (int i = 0 ; i < interfaceCount; ++i) { result.putShort(interfaces[i]); } result.putShort(methodsCount); boolean hasFrames = false ; boolean hasAsmInstructions = false ; methodWriter = firstMethod; while (methodWriter != null ) { hasFrames |= methodWriter.hasFrames(); hasAsmInstructions |= methodWriter.hasAsmInstructions(); methodWriter.putMethodInfo(result); methodWriter = (MethodWriter) methodWriter.mv; } if (hasAsmInstructions) { return replaceAsmInstructions(result.data, hasFrames); } else { return result.data; } } }
关于 MethodWriter
类的使用,它主要出现在 ClassWriter
类当中的 visitMethod()
和 toByteArray()
方法内。
ClassReader 类 ClassReader
类和 ClassWriter
类,从功能角度来说,是完全相反的两个类,一个用于读取 .class
文件,另一个用于生成 .class
文件。
class info 与 ClassWriter
类不同的是,ClassReader
类并没有继承自 ClassVisitor
类。
1 2 public class ClassReader { }
ClassWriter
类的定义如下:
1 2 public class ClassWriter extends ClassVisitor { }
fields ClassReader
类定义的字段有哪些。主要是以下三个片段: classFileBuffer
字段、cpInfoOffsets
字段和 header
字段。
1 2 3 4 5 6 7 8 public class ClassReader { final byte [] classFileBuffer; private final int [] cpInfoOffsets; public final int header; }
这 3 个字段能够体现出 ClassReader
类处理 .class
文件的整体思路:
第 1 组,classFileBuffer
字段:它里面包含的信息,就是从 .class
文件中读取出来的字节码数据。
第 2 组,cpInfoOffsets
字段和 header
字段:它们分别标识了 classFileBuffer
中数据里包含的常量池(constant pool)和访问标识(access flag)的位置信息。
我们拿到 classFileBuffer
字段后,一个主要目的就是对它的内容进行修改,来实现一个新的功能。它处理的大体思路是这样的:
1 .class 文件 --> ClassReader --> byte [] --> 经过各种转换 --> ClassWriter --> byte [] --> .class 文件
constructors ClassReader
类定义的构造方法。在 ClassReader
类当中定义了 5 个构造方法。但是,从本质上来说,这 5 个构造方法本质上是同一个构造方法的不同表现形式。其中,最常用的构造方法有两个:
第一个是 ClassReader cr = new ClassReader("sample.HelloWorld");
第二个是 ClassReader cr = new ClassReader(bytes);
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 public class ClassReader { public ClassReader (final String className) throws IOException { this ( readStream(ClassLoader.getSystemResourceAsStream(className.replace('.' , '/' ) + ".class" ), true ) ); } public ClassReader (final byte [] classFile) { this (classFile, 0 , classFile.length); } public ClassReader (final byte [] classFileBuffer, final int classFileOffset, final int classFileLength) { this (classFileBuffer, classFileOffset, true ); } ClassReader( final byte [] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) { } public ClassReader (InputStream inputStream) throws IOException { this (readStream(inputStream, false )); } }
methods ClassReader
类定义的方法。
getXxx() 方法 这里介绍的几个 getXxx()
方法,都是在 header
字段的基础上获得的:
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 public class ClassReader { public int getAccess () { return readUnsignedShort(header); } public String getClassName () { return readClass(header + 2 , new char [maxStringLength]); } public String getSuperName () { return readClass(header + 4 , new char [maxStringLength]); } public String[] getInterfaces() { int currentOffset = header + 6 ; int interfacesCount = readUnsignedShort(currentOffset); String[] interfaces = new String [interfacesCount]; if (interfacesCount > 0 ) { char [] charBuffer = new char [maxStringLength]; for (int i = 0 ; i < interfacesCount; ++i) { currentOffset += 2 ; interfaces[i] = readClass(currentOffset, charBuffer); } } return interfaces; } }
上面的几个 getXxx()
方法也需要参考 ClassFile 结构来理解:
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] }
假如,有如下一个类:
1 2 3 4 5 import java.io.Serializable ; public class HelloWorld extends Exception implements Serializable , Cloneable { }
我们可以使用 ClassReader
类中的 getXxx()
方法来获取相应的信息:
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 import cmisl.utils.FileUtils;import org.objectweb.asm.ClassReader;import java.util.Arrays;public class HelloWorldRun { public static void main (String[] args) throws Exception { String relative_path = "sample/HelloWorld.class" ; String filepath = FileUtils.getFilePath(relative_path); byte [] bytes = FileUtils.readBytes(filepath); ClassReader cr = new ClassReader (bytes); 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)); } }
输出结果:
1 2 3 4 access: 33 className: sample/HelloWorldsuperName: java/lang/ Exceptioninterfaces: [java/io/ Serializable, java/lang/ Cloneable]
accept() 方法 在 ClassReader
类当中,有一个 accept()
方法,这个方法接收一个 ClassVisitor
类型的参数,因此 accept()
方法是将 ClassReader
和 ClassVisitor
进行连接的“桥梁”。accept()
方法的代码逻辑就是按照一定的顺序来调用 ClassVisitor
当中的 visitXxx()
方法。
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 56 57 58 59 60 61 62 63 64 65 66 67 public class ClassReader { public static final int SKIP_CODE = 1 ; public static final int SKIP_DEBUG = 2 ; public static final int SKIP_FRAMES = 4 ; public static final int EXPAND_FRAMES = 8 ; public void accept (final ClassVisitor classVisitor, final int parsingOptions) { accept(classVisitor, new Attribute [0 ], parsingOptions); } public void accept ( final ClassVisitor classVisitor, final Attribute[] attributePrototypes, final int parsingOptions) { Context context = new Context (); context.attributePrototypes = attributePrototypes; context.parsingOptions = parsingOptions; context.charBuffer = new char [maxStringLength]; char [] charBuffer = context.charBuffer; int currentOffset = header; int accessFlags = readUnsignedShort(currentOffset); String thisClass = readClass(currentOffset + 2 , charBuffer); String superClass = readClass(currentOffset + 4 , charBuffer); String[] interfaces = new String [readUnsignedShort(currentOffset + 6 )]; currentOffset += 8 ; for (int i = 0 ; i < interfaces.length; ++i) { interfaces[i] = readClass(currentOffset, charBuffer); currentOffset += 2 ; } classVisitor.visit(readInt(cpInfoOffsets[1 ] - 7 ), accessFlags, thisClass, signature, superClass, interfaces); int fieldsCount = readUnsignedShort(currentOffset); currentOffset += 2 ; while (fieldsCount-- > 0 ) { currentOffset = readField(classVisitor, context, currentOffset); } int methodsCount = readUnsignedShort(currentOffset); currentOffset += 2 ; while (methodsCount-- > 0 ) { currentOffset = readMethod(classVisitor, context, currentOffset); } classVisitor.visitEnd(); } }
ClassVisitor
类中 visitXxx()
方法的调用顺序:
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
使用 ClassReader 类 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 import cmisl.utils.FileUtils;import org.objectweb.asm.ClassReader;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.Opcodes;public class HelloWorldTransformCore { public static void main (String[] args) { String relative_path = "sample/HelloWorld.class" ; String filepath = FileUtils.getFilePath(relative_path); byte [] bytes1 = FileUtils.readBytes(filepath); ClassReader cr = new ClassReader (bytes1); ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES); int api = Opcodes.ASM9; ClassVisitor cv = new ClassVisitor (api, cw) {
代码的整体处理流程是如下这样的:
1 .class --> ClassReader --> ClassVisitor1 ... --> ClassVisitorN --> ClassWriter --> .class 文件
我们可以将整体的处理流程想像成一条河流,那么
第一步,构建 ClassReader
。生成的 ClassReader
对象,它是这条“河流”的“源头”。
第二步,构建 ClassWriter
。生成的 ClassWriter
对象,它是这条“河流”的“归处”,它可以想像成是“百川东到海”中的“大海”。
第三步,串连 ClassVisitor
。生成的 ClassVisitor
对象,它是这条“河流”上的重要节点,可以想像成一个“水库”;可以有多个 ClassVisitor
对象,也就是在这条“河流”上存在多个“水库”,这些“水库”可以对“河水”进行一些处理,最终会这些“水库”的水会流向“大海”;也就是说多个 ClassVisitor
对象最终会连接到 ClassWriter
对象上。
第四步,结合 ClassReader
和 ClassVisitor
。在 ClassReader
类上,有一个 accept()
方法,它接收一个 ClassVisitor
类型的对象;换句话说,就是将“河流”的“源头”和后续的“水库”连接起来。
第五步,生成 byte[]
。到这一步,就是所有的“河水”都流入 ClassWriter
这个“大海”当中,这个时候我们调用 ClassWriter.toByteArray()
方法,就能够得到 byte[]
内容。
parsingOptions 参数 在 ClassReader
类当中,accept()
方法接收一个 int
类型的 parsingOptions
参数。
1 public void accept (final ClassVisitor classVisitor, final int parsingOptions)
parsingOptions
参数可以选取的值有以下 5 个:
0
ClassReader.SKIP_CODE
ClassReader.SKIP_DEBUG
ClassReader.SKIP_FRAMES
ClassReader.EXPAND_FRAMES
推荐使用:
在调用 ClassReader.accept()
方法时,其中的 parsingOptions
参数,推荐使用 ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES
。
在创建 ClassWriter
对象时,其中的 flags
参数,推荐使用 ClassWriter.COMPUTE_FRAMES
。
示例代码如下:
1 2 3 4 5 ClassReader cr = new ClassReader (bytes);int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES; cr.accept(cv, parsingOptions);ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES);
为什么我们推荐使用 ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES
呢?因为使用这样的一个值,可以生成最少的 ASM 代码,但是又能实现完整的功能。
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()
方法的参数有影响。
简而言之,使用 ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES
的目的是功能完整、代码少、复杂度低, :
不使用 ClassReader.SKIP_CODE
,使代码的功能 保持完整。
使用 ClassReader.SKIP_DEBUG
,减少不必要的调试信息,会使代码量 减少。
使用 ClassReader.SKIP_FRAMES
,降低代码的复杂度 。
不使用 ClassReader.EXPAND_FRAMES
,降低代码的复杂度 。
我们使用 ClassReader.SKIP_DEBUG
的时候,就不会生成调试信息。因为这些调试信息主要是记录某一条 instruction 在代码当中的行数,以及变量的名字等信息;如果没有这些调试信息,也不会影响程序的正常运行,也就是说功能不受影响,因此省略这些信息,就会让 ASM 代码尽可能的简洁。
我们使用 ClassReader.SKIP_FRAMES
的时候,就会忽略 frame 的信息。为什么要忽略这些 frame 信息呢?因为 frame 计算的细节会很繁琐,需要处理的情况也有很多,总的来说,就是比较麻烦。我们解决这个麻烦的方式,就是让 ASM 帮助我们来计算 frame 的情况,也就是在创建 ClassWriter
对象的时候使用 ClassWriter.COMPUTE_FRAMES
选项。
示例一:修改类的版本 假如有一个 HelloWorld.java
文件,经过 Java 8 编译之后,生成的 HelloWorld.class
文件的版本就是 Java 8 的版本,我们的目标是将 HelloWorld.class
由 Java 8 版本转换成 Java 7 版本。
1 2 public class HelloWorld { }
编码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.Opcodes;public class ClassChangeVersionVisitor extends ClassVisitor { public ClassChangeVersionVisitor (int api, ClassVisitor classVisitor) { super (api, classVisitor); } @Override public void visit (int version, int access, String name, String signature, String superName, String[] interfaces) { super .visit(Opcodes.V1_7, access, name, signature, superName, interfaces); } }
进行转换:
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 import cmisl.utils.FileUtils;import org.objectweb.asm.ClassReader;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.Opcodes;public class HelloWorldTransformCore { public static void main (String[] args) { String relative_path = "sample/HelloWorld.class" ; String filepath = FileUtils.getFilePath(relative_path); byte [] bytes1 = FileUtils.readBytes(filepath); ClassReader cr = new ClassReader (bytes1); ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES); int api = Opcodes.ASM9; ClassVisitor cv = new ClassChangeVersionVisitor (api, cw); int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES; cr.accept(cv, parsingOptions); byte [] bytes2 = cw.toByteArray(); FileUtils.writeBytes(filepath, bytes2); } }
示例二:修改类的接口 在下面的 HelloWorld
类中,我们定义了一个 clone()
方法,但存在一个问题,也就是,如果没有实现 Cloneable
接口,clone()
方法就会出错,我们的目标是希望通过 ASM 为 HelloWorld
类添加上 Cloneable
接口。
1 2 3 4 5 6 public class HelloWorld { @Override public Object clone () throws CloneNotSupportedException { return super .clone(); } }
编码实现:
1 2 3 4 5 6 7 8 9 10 11 12 import org.objectweb.asm.ClassVisitor;public class ClassCloneVisitor extends ClassVisitor { public ClassCloneVisitor (int api, ClassVisitor cw) { super (api, cw); } @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" }); } }
注意:ClassCloneVisitor
这个类写的比较简单,直接添加 java/lang/Cloneable
接口信息;在项目代码当中,有一个 ClassAddInterfaceVisitor
类,实现更灵活。
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 import cmisl.utils.FileUtils;import org.objectweb.asm.ClassReader;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.Opcodes;public class HelloWorldTransformCore { public static void main (String[] args) { String relative_path = "sample/HelloWorld.class" ; String filepath = FileUtils.getFilePath(relative_path); byte [] bytes1 = FileUtils.readBytes(filepath); ClassReader cr = new ClassReader (bytes1); ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES); int api = Opcodes.ASM9; ClassVisitor cv = new ClassCloneVisitor (api, cw); int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES; cr.accept(cv, parsingOptions); byte [] bytes2 = cw.toByteArray(); FileUtils.writeBytes(filepath, bytes2); } }
验证结果:
1 2 3 4 5 6 7 public class HelloWorldRun { public static void main (String[] args) throws Exception { HelloWorld obj = new HelloWorld (); Object anotherObj = obj.clone(); System.out.println(anotherObj); } }
总结一 上面的两个例子,一个是修改类的版本信息,另一个是修改类的接口信息,那么这两个示例都是基于 ClassVisitor.visit()
方法实现的:
1 public void visit (int version, int access, String name, String signature, String superName, String[] interfaces)
这两个示例,就是通过修改 visit()
方法的参数实现的:
修改类的版本信息,是通过修改 version
这个参数实现的
修改类的接口信息,是通过修改 interfaces
这个参数实现的
其实,在 visit()
方法当中的其它参数也可以修改:
修改 access
参数,也就是修改了类的访问标识信息。
修改 name
参数,也就是修改了类的名称。但是,在大多数的情况下,不推荐修改 name
参数。因为调用类里的方法,都是先找到类,再找到相应的方法;如果将当前类的类名修改成别的名称,那么其它类当中可能就找不到原来的方法了,因为类名已经改了。但是,也有少数的情况,可以修改 name
参数,比如说对代码进行混淆(obfuscate)操作。
修改 superName
参数,也就是修改了当前类的父类信息。
示例三:删除字段 删除掉 HelloWorld 类里的 String strValue 字段。
1 2 3 4 public class HelloWorld { public int intValue; public String strValue; }
编码实现:
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 package Study.ClassReader;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.FieldVisitor;public class ClassRemoveFieldVisitor extends ClassVisitor { private final String fieldName; private final String fieldDesc; protected ClassRemoveFieldVisitor (int api, ClassVisitor classVisitor, String fieldName, String fieldDesc) { super (api, classVisitor); this .fieldName = fieldName; this .fieldDesc = fieldDesc; } @Override public FieldVisitor visitField (int access, String name, String descriptor, String signature, Object value) { if (name.equals(fieldName)&&descriptor.equals(fieldDesc)){ return null ; } else { return super .visitField(access, name, descriptor, signature, value); } } }
在这段代码中,ClassRemoveFieldVisitor
通过继承 ClassVisitor
类来实现删除特定字段的功能。关键在于 visitField()
方法。当 visitField()
返回 null
时,该字段就会被“过滤”掉,相当于从字节码中删除。
我们可以用邮递系统来比喻这个过程。ClassReader
就像邮件的发送者,ClassVisitor
是邮件经过的中间站,而 ClassWriter
是最终的收件人。如果在某个中间站 ClassVisitor
拦截并丢弃了某封邮件(字段),那么这封邮件就不会到达收件人。
同样,也可以用流水线来形容。ClassReader
是生产线上最初的步骤,ClassVisitor
是中间的处理环节,而 ClassWriter
是最终的成品阶段。如果某一个处理环节(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 package Study.ClassReader;import cmisl.utils.FileUtils;import org.objectweb.asm.ClassReader;import org.objectweb.asm.ClassWriter;import java.io.IOException;import static org.objectweb.asm.Opcodes.ASM9;public class HelloWorldTransformCore { public static void main (String[] args) throws IOException { String relative_path="Study/ClassReader/HelloWorld.class" ; String filepath= FileUtils.getFilePath(relative_path); byte [] bytes = FileUtils.readBytes(filepath); ClassReader classReader = new ClassReader (bytes); ClassWriter classWriter = new ClassWriter (ClassWriter.COMPUTE_MAXS); ClassRemoveFieldVisitor classRemoveFieldVisitor = new ClassRemoveFieldVisitor (ASM9, classWriter,"strValue" ,"Ljava/lang/String;" ); int parsingOptions = ClassReader.SKIP_DEBUG| ClassReader.SKIP_FRAMES; classReader.accept(classRemoveFieldVisitor,parsingOptions); FileUtils.writeBytes(filepath,classWriter.toByteArray()); } }
验证
1 2 3 4 5 6 7 8 9 10 11 public class HelloWorldRun { public static void main (String[] args) throws Exception { Class<?> clazz = Class.forName("Study.ClassReader.HelloWorld" ); System.out.println(clazz.getName()); Field[] declaredFields = clazz.getDeclaredFields(); for (Field f : declaredFields) { System.out.println(" " + f.getName()); } } }
示例四:添加字段 为了 HelloWorld
类添加一个 Object objValue
字段。
1 2 3 4 5 public class HelloWorld { public int intValue; public String strValue; }
编码方式:
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 package Study.ClassReader;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.FieldVisitor;public class ClassAddFieldVisitor extends ClassVisitor { private final int fieldAccess; private final String fieldName; private final String fieldDesc; private boolean isFieldPresent; protected ClassAddFieldVisitor (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 fieldVisitor = super .visitField(fieldAccess,fieldName,fieldDesc,null ,null ); if (fieldVisitor != null ){ fieldVisitor.visitEnd(); } } super .visitEnd(); } }
第一步,在 visitField()
方法中,判断某个字段是否已经存在,其结果存在于 isFieldPresent
字段当中;第二步,就是在 visitEnd()
方法中,根据 isFieldPresent
字段的值,来决定是否添加新的字段。
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 package Study.ClassReader;import cmisl.utils.FileUtils;import org.objectweb.asm.ClassReader;import org.objectweb.asm.ClassWriter;import java.io.IOException;import static org.objectweb.asm.Opcodes.ACC_PUBLIC;import static org.objectweb.asm.Opcodes.ASM9;public class HelloWorldTransformCore { public static void main (String[] args) throws IOException { String relative_path="Study/ClassReader/HelloWorld.class" ; String filepath= FileUtils.getFilePath(relative_path); byte [] bytes = FileUtils.readBytes(filepath); ClassReader classReader = new ClassReader (bytes); ClassWriter classWriter = new ClassWriter (ClassWriter.COMPUTE_MAXS); ClassAddFieldVisitor classAddFieldVisitor = new ClassAddFieldVisitor (ASM9, classWriter, ACC_PUBLIC, "objValue" , "Ljava/lang/Object;" ); int parsingOptions = ClassReader.SKIP_DEBUG| ClassReader.SKIP_FRAMES; classReader.accept(classAddFieldVisitor,parsingOptions); FileUtils.writeBytes(filepath,classWriter.toByteArray()); } }
验证与实例三相同。
总结二 在字节码处理中,ClassVisitor.visitField()
方法用于字段操作,主要包括以下几种:
1 public FieldVisitor visitField (int access, String name, String descriptor, String signature, Object value) ;
修改字段 :
可以改变字段名称、类型或访问权限。
需要通过调整 visitField()
方法中的参数来实现。
通常不建议修改,因为这可能导致引用不匹配,从而导致程序错误。例如,如果某个类中的字段名被修改,其他引用该字段的代码也必须同步更新,否则会报错。
删除字段 :
在 visitField()
中返回 null
可以实现删除。
通常不建议删除已有字段,因为字段通常有其特定用途,删除可能导致缺失功能或程序错误。
添加字段 :
判断字段是否已存在,如果不存在,则在 visitEnd()
中添加。
不建议在 visitField()
中添加,以避免重复添加导致类文件不合法。
以上操作在字节码操作中需要谨慎,以保证程序的正确性和稳定性。
示例五:删除方法 删除掉 HelloWorld
类里的 add()
方法。
1 2 3 4 5 6 7 8 9 public class HelloWorld { public int add (int a, int b) { return a + b; } public int sub (int a, int b) { return a - b; } }
编码实现:
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 package Study.ClassReader;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.MethodVisitor;public class ClassRemoveMethodVisitor extends ClassVisitor { private final String methodName; private final String methodDesc; protected ClassRemoveMethodVisitor (int api, ClassVisitor classVisitor,String methodName,String methodDesc) { super (api, classVisitor); this .methodName = methodName; this .methodDesc = methodDesc; } @Override public MethodVisitor visitMethod (int access, String name, String descriptor, String signature, String[] exceptions) { if (name.equals(methodName) && descriptor.equals(methodDesc)){ return null ; }else { return super .visitMethod(access, name, descriptor, signature, exceptions); } } }
和之前删除字段差不多
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 package Study.ClassReader;import cmisl.utils.FileUtils;import org.objectweb.asm.ClassReader;import org.objectweb.asm.ClassWriter;import java.io.IOException;import static org.objectweb.asm.Opcodes.ACC_PUBLIC;import static org.objectweb.asm.Opcodes.ASM9;public class HelloWorldTransformCore { public static void main (String[] args) throws IOException { String relative_path="Study/ClassReader/HelloWorld.class" ; String filepath= FileUtils.getFilePath(relative_path); byte [] bytes = FileUtils.readBytes(filepath); ClassReader classReader = new ClassReader (bytes); ClassWriter classWriter = new ClassWriter (ClassWriter.COMPUTE_MAXS); ClassRemoveMethodVisitor classRemoveMethodVisitor = new ClassRemoveMethodVisitor (ASM9, classWriter, "add" ,"(II)I" ); int parsingOptions = ClassReader.SKIP_DEBUG| ClassReader.SKIP_FRAMES; classReader.accept(classRemoveMethodVisitor,parsingOptions); FileUtils.writeBytes(filepath,classWriter.toByteArray()); } }
验证:
1 2 3 4 5 6 7 8 9 10 11 public class HelloWorldRun { public static void main (String[] args) throws Exception { Class<?> clazz = Class.forName("Study.ClassReader.HelloWorld" ); System.out.println(clazz.getName()); Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method m : declaredMethods) { System.out.println(" " + m.getName()); } } }
示例六:添加方法 为 HelloWorld
类添加一个 mul()
方法。
1 2 3 4 5 6 7 8 9 10 11 public class HelloWorld { public int add (int a, int b) { return a + b; } public int sub (int a, int b) { return a - b; } }
编码实现:
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 package Study.ClassReader;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.MethodVisitor;public abstract class ClassAddMethodVisitor extends ClassVisitor { private final int methodAccess; private final String methodName; private final String methodDesc; private final String methodSignature; private final String[] methodExceptions; private boolean isMethodPresent; protected ClassAddMethodVisitor (int api, ClassVisitor classVisitor, int methodAccess, String methodName, String methodDesc, String signature, String[] exceptions) { super (api, classVisitor); this .methodAccess = methodAccess; this .methodName = methodName; this .methodDesc = methodDesc; this .methodSignature = signature; this .methodExceptions = exceptions; this .isMethodPresent = false ; } @Override public MethodVisitor visitMethod (int access, String name, String descriptor, String signature, String[] exceptions) { if (name.equals(methodName) && descriptor.equals(methodDesc)) { isMethodPresent = true ; } return super .visitMethod(access, name, descriptor, signature, exceptions); } @Override public void visitEnd () { if (!isMethodPresent) { MethodVisitor methodVisitor = super .visitMethod(methodAccess, methodName, methodDesc, methodSignature, methodExceptions); if (methodVisitor!=null ){ generateMethodBody(methodVisitor); } } super .visitEnd(); } protected abstract void generateMethodBody (MethodVisitor methodVisitor) ; }
添加新的方法,和添加新的字段的思路,在前期,两者是一样的,都是先要判断该字段或该方法是否已经存在;但是,在后期,两者会有一些差异,因为方法需要有“方法体”,在上面的代码中,我们定义了一个 generateMethodBody()
方法,这个方法需要在子类当中进行实现。
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 package Study.ClassReader;import cmisl.utils.FileUtils;import org.objectweb.asm.ClassReader;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.MethodVisitor;import java.io.IOException;import static org.objectweb.asm.Opcodes.*;public class HelloWorldTransformCore { public static void main (String[] args) throws IOException { String relative_path="Study/ClassReader/HelloWorld.class" ; String filepath= FileUtils.getFilePath(relative_path); byte [] bytes = FileUtils.readBytes(filepath); ClassReader classReader = new ClassReader (bytes); ClassWriter classWriter = new ClassWriter (ClassWriter.COMPUTE_MAXS); ClassAddMethodVisitor classAddMethodVisitor = new ClassAddMethodVisitor ( ASM9,classWriter,ACC_PUBLIC,"multiply" ,"(II)I" ,null ,null ) { @Override protected void generateMethodBody (MethodVisitor methodVisitor) { methodVisitor.visitCode(); methodVisitor.visitVarInsn(ILOAD,1 ); methodVisitor.visitVarInsn(ILOAD,2 ); methodVisitor.visitInsn(IMUL); methodVisitor.visitInsn(IRETURN); methodVisitor.visitMaxs(2 ,3 ); methodVisitor.visitEnd(); } }; int parsingOptions = ClassReader.SKIP_DEBUG| ClassReader.SKIP_FRAMES; classReader.accept(classAddMethodVisitor,parsingOptions); FileUtils.writeBytes(filepath,classWriter.toByteArray()); } }
对于方法的操作,都是基于 ClassVisitor.visitMethod()
方法来实现的:
1 public MethodVisitor visitMethod (int access, String name, String descriptor, String signature, String[] exceptions) ;
修改方法 :
可以更新方法体,即具体实现的代码。
不建议修改方法名称或类型(参数和返回值类型),因为这可能导致调用不匹配,从而引发错误。
删除方法 :
通常不建议删除已有方法,因这可能导致调用失败,进而引发程序错误。
添加方法 :
在操作方法时需谨慎,以维护程序的正确性和稳定性。
AdviceAdapter 类 对于 AdviceAdapter
类,可以方便地实现在“方法进入”和“方法退出”时插入自定义代码。
AdviceAdapter
类的核心特点是提供了 onMethodEnter()
和 onMethodExit()
方法,这两个方法可以被重写,以便在方法执行之前和之后执行特定的逻辑。
class info AdviceAdapter
类是一个抽象的 MethodVisitor
子类
具体而言,AdviceAdapter
继承自 GeneratorAdapter
,而 GeneratorAdapter
又继承自 LocalVariablesSorter
,最终 LocalVariablesSorter
继承自 MethodVisitor
。
由于 AdviceAdapter
是一个抽象类,如果想要使用它,我们需要创建一个具体的子类来实现其功能:
1 2 3 public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes { }
fields AdviceAdapter
类定义的字段。其中, isConstructor
字段是判断当前方法是不是构造方法。如果当前方法是构造方法,在“方法进入”时添加代码,需要特殊处理。
1 2 3 4 5 6 public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes { protected int methodAccess; protected String methodDesc; private final boolean isConstructor; }
constructors AdviceAdapter
类定义的构造方法。 AdviceAdapter
的构造方法是用 protected
修饰,因此这个构造方法只能在子类当中访问。 换句话说,在外界不能用 new
关键字来创建对象。
1 2 3 4 5 6 7 8 9 public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes { protected AdviceAdapter (final int api, final MethodVisitor methodVisitor, final int access, final String name, final String descriptor) { super (api, methodVisitor, access, name, descriptor); methodAccess = access; methodDesc = descriptor; isConstructor = "<init>" .equals(name); } }
methods AdviceAdapter
类定义了几个重要的方法,其中最关键的是 onMethodEnter()
和 onMethodExit()
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes { protected void onMethodEnter () {} protected void onMethodExit (final int opcode) {} }
我们可以从三个角度来理解这两个方法:
1. 应用场景
**onMethodEnter()
**:在方法执行开始时,可以插入一些逻辑代码,例如日志记录、性能监控等。
**onMethodExit()
**:在方法执行结束时,可以执行清理操作、获取返回结果或捕获异常等。
2. 注意事项
对于 onMethodEnter()
和 onMethodExit()
,都需要注意:子类可以修改所有局部变量,但不应改变栈的状态 。换句话说,修改前后的操作数栈(operand stack)必须保持一致。
在 onMethodExit()
中,要注意栈顶元素包含返回值或异常实例 ,需要妥善处理,以确保这些信息不会丢失。
3. 工作原理
**onMethodEnter()
**:通常通过 visitCode()
方法触发,使用 onMethodEnter()
的好处在于它能够处理 <init>()
方法的复杂情况,而直接使用 visitCode()
可能会导致 <init>()
方法的错误。
**onMethodExit()
**:通常通过 visitInsn(int opcode)
方法触发,用于在方法结束时执行相应的逻辑。
示例:打印方法参数和返回值 假如有一个 HelloWorld
类,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class HelloWorld { private String name; private int age; public HelloWorld (String name, int age) { this .name = name; this .age = age; } public void test (long idCard, Object obj) { int hashCode = 0 ; hashCode += name.hashCode(); hashCode += age; hashCode += (int ) (idCard % Integer.MAX_VALUE); hashCode += obj.hashCode(); hashCode = Math.abs(hashCode); System.out.println("Hash Code is " + hashCode); if (hashCode % 2 == 1 ) { throw new RuntimeException ("illegal" ); } } }
我们想实现的预期目标:打印出构造方法(<init>()
)和 test()
的参数和返回值。
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 package cmisl.utils;import java.text.SimpleDateFormat;import java.util.Arrays;import java.util.Date;public class ParameterUtils { private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial( () -> new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ) ); public static void printValueOnStack (Object value) { if (value == null ) { System.out.println(" " + value); } else if (value instanceof String) { System.out.println(" " + value); } else if (value instanceof Date) { System.out.println(" " + formatter.get().format(value)); } else if (value instanceof char []) { System.out.println(" " + Arrays.toString((char [])value)); } else { System.out.println(" " + value.getClass() + ": " + value.toString()); } } public static void printText (String str) { System.out.println(str); } }
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 package Study.AdviceAdapter;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.MethodVisitor;import org.objectweb.asm.Type;import org.objectweb.asm.commons.AdviceAdapter;import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;import static org.objectweb.asm.Opcodes.ACC_NATIVE;public class ClassPrintParameterVisitor extends ClassVisitor { public ClassPrintParameterVisitor (int api, ClassVisitor classVisitor) { super (api, classVisitor); } @Override public MethodVisitor visitMethod (int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = super .visitMethod(access, name, descriptor, signature, exceptions); if (mv != null ) { boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0 ; boolean isNativeMethod = (access & ACC_NATIVE) != 0 ; if (!isAbstractMethod && !isNativeMethod) { mv = new MethodPrintParameterAdapter (api, mv, access, name, descriptor); } } return mv; } public static class MethodPrintParameterAdapter extends AdviceAdapter { public MethodPrintParameterAdapter (int api, MethodVisitor mv, int access, String name, String descriptor) { super (api, mv, access, name, descriptor); } @Override protected void onMethodEnter () { printMessage("Method Enter: " + getName() + methodDesc); Type[] argumentTypes = getArgumentTypes(); for (int i = 0 ; i < argumentTypes.length; i++) { Type t = argumentTypes[i]; loadArg(i); box(t); printValueOnStack("(Ljava/lang/Object;)V" ); } } @Override protected void onMethodExit (int opcode) { printMessage("Method Exit: " + getName() + methodDesc); if (opcode == ATHROW) { super .visitLdcInsn("abnormal return" ); } else if (opcode == RETURN) { super .visitLdcInsn("return void" ); } else if (opcode == ARETURN) { dup(); } else { if (opcode == LRETURN || opcode == DRETURN) { dup2(); } else { dup(); } box(getReturnType()); } printValueOnStack("(Ljava/lang/Object;)V" ); } private void printMessage (String str) { super .visitLdcInsn(str); super .visitMethodInsn(INVOKESTATIC, "cmisl/utils/ParameterUtils" , "printText" , "(Ljava/lang/String;)V" , false ); } private void printValueOnStack (String descriptor) { super .visitMethodInsn(INVOKESTATIC, "cmisl/utils/ParameterUtils" , "printValueOnStack" , descriptor, false ); } } }
开始在方法前后插入
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 package Study.AdviceAdapter;import cmisl.utils.FileUtils;import org.objectweb.asm.ClassReader;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.Opcodes;import java.io.IOException;public class HelloWorldTransformCore { public static void main (String[] args) throws IOException { String relative_path = "Study/AdviceAdapter/HelloWorld.class" ; String filepath = FileUtils.getFilePath(relative_path); byte [] bytes1 = FileUtils.readBytes(filepath); ClassReader cr = new ClassReader (bytes1); ClassWriter cw = new ClassWriter (ClassWriter.COMPUTE_FRAMES); int api = Opcodes.ASM9; ClassVisitor cv = new ClassPrintParameterVisitor (api, cw); int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES; cr.accept(cv, parsingOptions); byte [] bytes2 = cw.toByteArray(); FileUtils.writeBytes(filepath, bytes2); } }
Java虚拟机指令表
十六进制
助记符
指令说明
0x00
nop
什么都不做
0x01
aconst_null
将null推送至栈顶
0x02
iconst_m1
将int型-1推送至栈顶
0x03
iconst_0
将int型0推送至栈顶
0x04
iconst_1
将int型1推送至栈顶
0x05
iconst_2
将int型2推送至栈顶
0x06
iconst_3
将int型3推送至栈顶
0x07
iconst_4
将int型4推送至栈顶
0x08
iconst_5
将int型5推送至栈顶
0x09
lconst_0
将long型0推送至栈顶
0x0a
lconst_1
将long型1推送至栈顶
0x0b
fconst_0
将float型0推送至栈顶
0x0c
fconst_1
将float型1推送至栈顶
0x0d
fconst_2
将float型2推送至栈顶
0x0e
dconst_0
将double型0推送至栈顶
0x0f
dconst_1
将double型1推送至栈顶
0x10
bipush
将单字节的常量值(-128~127)推送至栈顶
0x11
sipush
将一个短整型常量值(-32768~32767)推送至栈顶
0x12
ldc
将int, float或String型常量值从常量池中推送至栈顶
0x13
ldc_w
将int, float或String型常量值从常量池中推送至栈顶(宽索引)
0x14
ldc2_w
将long或double型常量值从常量池中推送至栈顶(宽索引)
0x15
iload
将指定的int型本地变量推送至栈顶
0x16
lload
将指定的long型本地变量推送至栈顶
0x17
fload
将指定的float型本地变量推送至栈顶
0x18
dload
将指定的double型本地变量推送至栈顶
0x19
aload
将指定的引用类型本地变量推送至栈顶
0x1a
iload_0
将第一个int型本地变量推送至栈顶
0x1b
iload_1
将第二个int型本地变量推送至栈顶
0x1c
iload_2
将第三个int型本地变量推送至栈顶
0x1d
iload_3
将第四个int型本地变量推送至栈顶
0x1e
lload_0
将第一个long型本地变量推送至栈顶
0x1f
lload_1
将第二个long型本地变量推送至栈顶
0x20
lload_2
将第三个long型本地变量推送至栈顶
0x21
lload_3
将第四个long型本地变量推送至栈顶
0x22
fload_0
将第一个float型本地变量推送至栈顶
0x23
fload_1
将第二个float型本地变量推送至栈顶
0x24
fload_2
将第三个float型本地变量推送至栈顶
0x25
fload_3
将第四个float型本地变量推送至栈顶
0x26
dload_0
将第一个double型本地变量推送至栈顶
0x27
dload_1
将第二个double型本地变量推送至栈顶
0x28
dload_2
将第三个double型本地变量推送至栈顶
0x29
dload_3
将第四个double型本地变量推送至栈顶
0x2a
aload_0
将第一个引用类型本地变量推送至栈顶
0x2b
aload_1
将第二个引用类型本地变量推送至栈顶
0x2c
aload_2
将第三个引用类型本地变量推送至栈顶
0x2d
aload_3
将第四个引用类型本地变量推送至栈顶
0x2e
iaload
将int型数组指定索引的值推送至栈顶
0x2f
laload
将long型数组指定索引的值推送至栈顶
0x30
faload
将float型数组指定索引的值推送至栈顶
0x31
daload
将double型数组指定索引的值推送至栈顶
0x32
aaload
将引用型数组指定索引的值推送至栈顶
0x33
baload
将boolean或byte型数组指定索引的值推送至栈顶
0x34
caload
将char型数组指定索引的值推送至栈顶
0x35
saload
将short型数组指定索引的值推送至栈顶
0x36
istore
将栈顶int型数值存入指定本地变量
0x37
lstore
将栈顶long型数值存入指定本地变量
0x38
fstore
将栈顶float型数值存入指定本地变量
0x39
dstore
将栈顶double型数值存入指定本地变量
0x3a
astore
将栈顶引用型数值存入指定本地变量
0x3b
istore_0
将栈顶int型数值存入第一个本地变量
0x3c
istore_1
将栈顶int型数值存入第二个本地变量
0x3d
istore_2
将栈顶int型数值存入第三个本地变量
0x3e
istore_3
将栈顶int型数值存入第四个本地变量
0x3f
lstore_0
将栈顶long型数值存入第一个本地变量
0x40
lstore_1
将栈顶long型数值存入第二个本地变量
0x41
lstore_2
将栈顶long型数值存入第三个本地变量
0x42
lstore_3
将栈顶long型数值存入第四个本地变量
0x43
fstore_0
将栈顶float型数值存入第一个本地变量
0x44
fstore_1
将栈顶float型数值存入第二个本地变量
0x45
fstore_2
将栈顶float型数值存入第三个本地变量
0x46
fstore_3
将栈顶float型数值存入第四个本地变量
0x47
dstore_0
将栈顶double型数值存入第一个本地变量
0x48
dstore_1
将栈顶double型数值存入第二个本地变量
0x49
dstore_2
将栈顶double型数值存入第三个本地变量
0x4a
dstore_3
将栈顶double型数值存入第四个本地变量
0x4b
astore_0
将栈顶引用型数值存入第一个本地变量
0x4c
astore_1
将栈顶引用型数值存入第二个本地变量
0x4d
astore_2
将栈顶引用型数值存入第三个本地变量
0x4e
astore_3
将栈顶引用型数值存入第四个本地变量
0x4f
iastore
将栈顶int型数值存入指定数组的指定索引位置
0x50
lastore
将栈顶long型数值存入指定数组的指定索引位置
0x51
fastore
将栈顶float型数值存入指定数组的指定索引位置
0x52
dastore
将栈顶double型数值存入指定数组的指定索引位置
0x53
aastore
将栈顶引用型数值存入指定数组的指定索引位置
0x54
bastore
将栈顶boolean或byte型数值存入指定数组的指定索引位置
0x55
castore
将栈顶char型数值存入指定数组的指定索引位置
0x56
sastore
将栈顶short型数值存入指定数组的指定索引位置
0x57
pop
将栈顶数值弹出 (数值不能是long或double类型的)
0x58
pop2
将栈顶的一个(long或double类型的)或两个数值弹出(其它)
0x59
dup
复制栈顶数值并将复制值压入栈顶
0x5a
dup_x1
复制栈顶数值并将两个复制值压入栈顶
0x5b
dup_x2
复制栈顶数值并将三个(或两个)复制值压入栈顶
0x5c
dup2
复制栈顶一个(long或double类型的)或两个(其它)数值并将复制值压入栈顶
0x5d
dup2_x1
<待补充>
0x5e
dup2_x2
<待补充>
0x5f
swap
将栈最顶端的两个数值互换(数值不能是long或double类型的)
0x60
iadd
将栈顶两int型数值相加并将结果压入栈顶
0x61
ladd
将栈顶两long型数值相加并将结果压入栈顶
0x62
fadd
将栈顶两float型数值相加并将结果压入栈顶
0x63
dadd
将栈顶两double型数值相加并将结果压入栈顶
0x64
isub
将栈顶两int型数值相减并将结果压入栈顶
0x65
lsub
将栈顶两long型数值相减并将结果压入栈顶
0x66
fsub
将栈顶两float型数值相减并将结果压入栈顶
0x67
dsub
将栈顶两double型数值相减并将结果压入栈顶
0x68
imul
将栈顶两int型数值相乘并将结果压入栈顶
0x69
lmul
将栈顶两long型数值相乘并将结果压入栈顶
0x6a
fmul
将栈顶两float型数值相乘并将结果压入栈顶
0x6b
dmul
将栈顶两double型数值相乘并将结果压入栈顶
0x6c
idiv
将栈顶两int型数值相除并将结果压入栈顶
0x6d
ldiv
将栈顶两long型数值相除并将结果压入栈顶
0x6e
fdiv
将栈顶两float型数值相除并将结果压入栈顶
0x6f
ddiv
将栈顶两double型数值相除并将结果压入栈顶
0x70
irem
将栈顶两int型数值作取模运算并将结果压入栈顶
0x71
lrem
将栈顶两long型数值作取模运算并将结果压入栈顶
0x72
frem
将栈顶两float型数值作取模运算并将结果压入栈顶
0x73
drem
将栈顶两double型数值作取模运算并将结果压入栈顶
0x74
ineg
将栈顶int型数值取负并将结果压入栈顶
0x75
lneg
将栈顶long型数值取负并将结果压入栈顶
0x76
fneg
将栈顶float型数值取负并将结果压入栈顶
0x77
dneg
将栈顶double型数值取负并将结果压入栈顶
0x78
ishl
将int型数值左移位指定位数并将结果压入栈顶
0x79
lshl
将long型数值左移位指定位数并将结果压入栈顶
0x7a
ishr
将int型数值右(符号)移位指定位数并将结果压入栈顶
0x7b
lshr
将long型数值右(符号)移位指定位数并将结果压入栈顶
0x7c
iushr
将int型数值右(无符号)移位指定位数并将结果压入栈顶
0x7d
lushr
将long型数值右(无符号)移位指定位数并将结果压入栈顶
0x7e
iand
将栈顶两int型数值作“按位与”并将结果压入栈顶
0x7f
land
将栈顶两long型数值作“按位与”并将结果压入栈顶
0x80
ior
将栈顶两int型数值作“按位或”并将结果压入栈顶
0x81
lor
将栈顶两long型数值作“按位或”并将结果压入栈顶
0x82
ixor
将栈顶两int型数值作“按位异或”并将结果压入栈顶
0x83
lxor
将栈顶两long型数值作“按位异或”并将结果压入栈顶
0x84
iinc
将指定int型变量增加指定值(i++, i–, i+=2)
0x85
i2l
将栈顶int型数值强制转换成long型数值并将结果压入栈顶
0x86
i2f
将栈顶int型数值强制转换成float型数值并将结果压入栈顶
0x87
i2d
将栈顶int型数值强制转换成double型数值并将结果压入栈顶
0x88
l2i
将栈顶long型数值强制转换成int型数值并将结果压入栈顶
0x89
l2f
将栈顶long型数值强制转换成float型数值并将结果压入栈顶
0x8a
l2d
将栈顶long型数值强制转换成double型数值并将结果压入栈顶
0x8b
f2i
将栈顶float型数值强制转换成int型数值并将结果压入栈顶
0x8c
f2l
将栈顶float型数值强制转换成long型数值并将结果压入栈顶
0x8d
f2d
将栈顶float型数值强制转换成double型数值并将结果压入栈顶
0x8e
d2i
将栈顶double型数值强制转换成int型数值并将结果压入栈顶
0x8f
d2l
将栈顶double型数值强制转换成long型数值并将结果压入栈顶
0x90
d2f
将栈顶double型数值强制转换成float型数值并将结果压入栈顶
0x91
i2b
将栈顶int型数值强制转换成byte型数值并将结果压入栈顶
0x92
i2c
将栈顶int型数值强制转换成char型数值并将结果压入栈顶
0x93
i2s
将栈顶int型数值强制转换成short型数值并将结果压入栈顶
0x94
lcmp
比较栈顶两long型数值大小,并将结果(1,0,-1)压入栈顶
0x95
fcmpl
比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0x96
fcmpg
比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
0x97
dcmpl
比较栈顶两double型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0x98
dcmpg
比较栈顶两double型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
0x99
ifeq
当栈顶int型数值等于0时跳转
0x9a
ifne
当栈顶int型数值不等于0时跳转
0x9b
iflt
当栈顶int型数值小于0时跳转
0x9c
ifge
当栈顶int型数值大于等于0时跳转
0x9d
ifgt
当栈顶int型数值大于0时跳转
0x9e
ifle
当栈顶int型数值小于等于0时跳转
0x9f
if_icmpeq
比较栈顶两int型数值大小,当结果等于0时跳转
0xa0
if_icmpne
比较栈顶两int型数值大小,当结果不等于0时跳转
0xa1
if_icmplt
比较栈顶两int型数值大小,当结果小于0时跳转
0xa2
if_icmpge
比较栈顶两int型数值大小,当结果大于等于0时跳转
0xa3
if_icmpgt
比较栈顶两int型数值大小,当结果大于0时跳转
0xa4
if_icmple
比较栈顶两int型数值大小,当结果小于等于0时跳转
0xa5
if_acmpeq
比较栈顶两引用型数值,当结果相等时跳转
0xa6
if_acmpne
比较栈顶两引用型数值,当结果不相等时跳转
0xa7
goto
无条件跳转
0xa8
jsr
跳转至指定16位offset位置,并将jsr下一条指令地址压入栈顶
0xa9
ret
返回至本地变量指定的index的指令位置(一般与jsr, jsr_w联合使用)
0xaa
tableswitch
用于switch条件跳转,case值连续(可变长度指令)
0xab
lookupswitch
用于switch条件跳转,case值不连续(可变长度指令)
0xac
ireturn
从当前方法返回int
0xad
lreturn
从当前方法返回long
0xae
freturn
从当前方法返回float
0xaf
dreturn
从当前方法返回double
0xb0
areturn
从当前方法返回对象引用
0xb1
return
从当前方法返回void
0xb2
getstatic
获取指定类的静态域,并将其值压入栈顶
0xb3
putstatic
为指定的类的静态域赋值
0xb4
getfield
获取指定类的实例域,并将其值压入栈顶
0xb5
putfield
为指定的类的实例域赋值
0xb6
invokevirtual
调用实例方法
0xb7
invokespecial
调用超类构造方法,实例初始化方法,私有方法
0xb8
invokestatic
调用静态方法
0xb9
invokeinterface
调用接口方法
0xba
–
0xbb
new
创建一个对象,并将其引用值压入栈顶
0xbc
newarray
创建一个指定原始类型(如int, float, char…)的数组,并将其引用值压入栈顶
0xbd
anewarray
创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶
0xbe
arraylength
获得数组的长度值并压入栈顶
0xbf
athrow
将栈顶的异常抛出
0xc0
checkcast
检验类型转换,检验未通过将抛出ClassCastException
0xc1
instanceof
检验对象是否是指定的类的实例,如果是将1压入栈顶,否则将0压入栈顶
0xc2
monitorenter
获得对象的锁,用于同步方法或同步块
0xc3
monitorexit
释放对象的锁,用于同步方法或同步块
0xc4
wide
<待补充>
0xc5
multianewarray
创建指定类型和指定维度的多维数组(执行该指令时,操作栈中必须包含各维度的长度值),并将其引用值压入栈顶
0xc6
ifnull
为null时跳转
0xc7
ifnonnull
不为null时跳转
0xc8
goto_w
无条件跳转(宽索引)
0xc9
jsr_w
跳转至指定32位offset位置,并将jsr_w下一条指令地址压入栈顶