FieldVisitor使用abstract
修饰,用于创建变量,在使用时调用 ClassWriter.visitField即可创建FieldVisitor
方法介绍
visitField(Opcodes.ACC_PUBLIC, “str”, “Ljava/lang/String;”, null, “Hello World”)
第一个参数是修饰类型,第二个参数是变量名,第三个是变量类型,第四个签名,第五个是变量的值(设置值好像没什么用,所以我在下面代码的初始化中重新初始化了str的值)
示例代码如下
package com.example.asmapplication import org.junit.Test import org.objectweb.asm.ClassWriter import org.objectweb.asm.Opcodes import java.io.File import java.io.FileOutputStream import java.net.URLClassLoader class DemoASMGenerateField { @Test fun generate() { val filePath = "E:\\Develop\\ASMApplication2\\app\\src\\test\\java\\com\\example\\asmapplication\\generate\\GenerateField.class" val file = File(filePath) if (!file.parentFile.exists()) { file.parentFile.mkdir() } //创建ClassWriter val cw = ClassWriter(ClassWriter.COMPUTE_FRAMES) //设定包名和类名 cw.visit( Opcodes.V1_8, Opcodes.ACC_PUBLIC, "com/example/asmapplication/generate/GenerateField", null, "java/lang/Object", null ) //创建全局变量 val vfStr = cw.visitField(Opcodes.ACC_PUBLIC, "str", "Ljava/lang/String;", null, "Hello World") vfStr.visitEnd() //每个classFile都有一个<init>的初始化方法 val mvInit = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null) mvInit.visitCode() mvInit.visitVarInsn(Opcodes.ALOAD, 0) mvInit.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false) mvInit.visitVarInsn(Opcodes.ALOAD, 0) //初始化定义的变量的值 mvInit.visitLdcInsn("Hello World") mvInit.visitFieldInsn(Opcodes.PUTFIELD, "com/example/asmapplication/generate/GenerateField", "str", "Ljava/lang/String;") mvInit.visitInsn(Opcodes.RETURN) mvInit.visitMaxs(0, 0) mvInit.visitEnd() //创建一个test()方法 val mvTest = cw.visitMethod(Opcodes.ACC_PUBLIC, "test", "()V", null, null) mvTest.visitCode() //打印Hello Word mvTest.visitVarInsn(Opcodes.ALOAD, 0); mvTest.visitFieldInsn( Opcodes.GETFIELD, "com/example/asmapplication/generate/GenerateField", "str", "Ljava/lang/String;" ); mvTest.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;") mvTest.visitInsn(Opcodes.SWAP) mvTest.visitMethodInsn( Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false ) mvTest.visitInsn(Opcodes.RETURN) mvTest.visitMaxs(0, 0) mvTest.visitEnd() //类的访问结束 cw.visitEnd() //输出为class文件 val outputStream = FileOutputStream(file) outputStream.write(cw.toByteArray()) outputStream.flush() outputStream.close() val classLoader = URLClassLoader( arrayOf( File("E:\\Develop\\ASMApplication2\\app\\src\\test\\java").toURI().toURL() ) ) val clazz = classLoader.loadClass("com.example.asmapplication.generate.GenerateField") val obj = clazz.newInstance() clazz.getMethod("test").invoke(obj) } }
最终生成代码:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package com.example.asmapplication.generate; public class GenerateField { public String str = "Hello World"; public GenerateField() { } public void test() { System.out.println(this.str); } }
技巧分享
假如我们想在MainActivity中动态生成两个变量
public int age = 100;
public String str = "Hello World";
可以这样做:
1、先在一个Test类中创建两个变量,代码 如下:
public class Test { public int age = 100; public String str = "Hello World"; private int sum(int i, int j) { return i+j; } }
编译后,通过ASM Bytecode Outline Rebooted等工具查看Test.class的字节码信息:
public class com/baidu/main/test/Test { // access flags 0x1 public I age // access flags 0x1 public Ljava/lang/String; str // access flags 0x1 public <init>()V ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V ALOAD 0 BIPUSH 100 PUTFIELD com/baidu/main/test/Test.age : I ALOAD 0 LDC "Hello World" PUTFIELD com/baidu/main/test/Test.str : Ljava/lang/String; RETURN MAXSTACK = 2 MAXLOCALS = 1 // access flags 0x2 private sum(II)I ILOAD 1 ILOAD 2 IADD IRETURN MAXSTACK = 2 MAXLOCALS = 3 }
对应的ASM信息如下:
public class TestDump implements Opcodes { public static byte[] dump() throws Exception { ClassWriter classWriter = new ClassWriter(0); FieldVisitor fieldVisitor; MethodVisitor methodVisitor; AnnotationVisitor annotationVisitor0; classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "com/baidu/main/test/Test", null, "java/lang/Object", null); { fieldVisitor = classWriter.visitField(ACC_PUBLIC, "age", "I", null, null); fieldVisitor.visitEnd(); } { fieldVisitor = classWriter.visitField(ACC_PUBLIC, "str", "Ljava/lang/String;", null, null); fieldVisitor.visitEnd(); } { methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); methodVisitor.visitCode(); methodVisitor.visitVarInsn(ALOAD, 0); methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); methodVisitor.visitVarInsn(ALOAD, 0); methodVisitor.visitIntInsn(BIPUSH, 100); methodVisitor.visitFieldInsn(PUTFIELD, "com/baidu/main/test/Test", "age", "I"); methodVisitor.visitVarInsn(ALOAD, 0); methodVisitor.visitLdcInsn("Hello World"); methodVisitor.visitFieldInsn(PUTFIELD, "com/baidu/main/test/Test", "str", "Ljava/lang/String;"); methodVisitor.visitInsn(RETURN); methodVisitor.visitMaxs(2, 1); methodVisitor.visitEnd(); } { methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "sum", "(II)I", null, null); methodVisitor.visitCode(); methodVisitor.visitVarInsn(ILOAD, 1); methodVisitor.visitVarInsn(ILOAD, 2); methodVisitor.visitInsn(IADD); methodVisitor.visitInsn(IRETURN); methodVisitor.visitMaxs(2, 3); methodVisitor.visitEnd(); } classWriter.visitEnd(); return classWriter.toByteArray(); } }
从ASM生成的代码中可以看到,age和str两个变量后并没有立刻赋值(赋值了不一定生效),而是在init方法里面进行了赋值。
2.对MainActivity进行插装
package com.baidu.plugin.print import org.objectweb.asm.AnnotationVisitor import org.objectweb.asm.Attribute import org.objectweb.asm.ClassVisitor import org.objectweb.asm.FieldVisitor import org.objectweb.asm.Label import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes import org.objectweb.asm.commons.AdviceAdapter class PrintClassVisitor(nextVisitor: ClassVisitor, private val className: String): ClassVisitor(Opcodes.ASM5, nextVisitor) { override fun visit( version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array<out String>? ) { println("visit access $access className $className name $name signature $signature") val numFiledVisitor = cv.visitField(Opcodes.ACC_PRIVATE, "age", "I", null, 25) numFiledVisitor.visitEnd() val strFiledVisitor = cv.visitField(Opcodes.ACC_PUBLIC, "str", "Ljava/lang/String;", null, "Hello World") strFiledVisitor.visitEnd() super.visit(version, access, name, signature, superName, interfaces) } override fun visitEnd() { super.visitEnd() } override fun visitField( access: Int, name: String?, descriptor: String?, signature: String?, value: Any? ): FieldVisitor { println("visitField access $access className $className name $name descriptor $descriptor") val filedVisitor = super.visitField(access, name, descriptor, signature, value) return MyPrintFieldVisitor( Opcodes.ASM5, filedVisitor ) } override fun visitMethod( access: Int, name: String?, descriptor: String?, signature: String?, exceptions: Array<out String>? ): MethodVisitor { val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions) return MyPrintMethodVisitor( Opcodes.ASM5, methodVisitor, access, className, name, descriptor ) } } class MyPrintFieldVisitor(api: Int, filedVisitor: FieldVisitor) : FieldVisitor(api, filedVisitor) { override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor { println("visitAnnotation descriptor $descriptor visible $visible") return super.visitAnnotation(descriptor, visible) } } class MyPrintMethodVisitor( api: Int, methodVisitor: MethodVisitor?, private val access: Int, private val className: String, private val name: String?, private val descriptor: String? ) : AdviceAdapter(api, methodVisitor, access, name, descriptor) { override fun onMethodEnter() { println("onMethodEnter access $access className $className name $name descriptor $descriptor") if (name.equals("<init>") && descriptor.equals("()V")) { //每个classFile都有一个<init>的初始化方法(固定写法) mv.visitVarInsn(Opcodes.ALOAD, 0) mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false) mv.visitVarInsn(Opcodes.ALOAD, 0) //初始化定义的变量的值 mv.visitIntInsn(BIPUSH, 100) mv.visitFieldInsn(PUTFIELD, "com/baidu/main/MainActivity", "age", "I" ) //String的初始化会在构造方法中 mv.visitVarInsn(Opcodes.ALOAD, 0) mv.visitLdcInsn("Hello World") mv.visitFieldInsn(PUTFIELD, "com/baidu/main/MainActivity", "str", "Ljava/lang/String" ) mv.visitInsn(RETURN) } if (name.equals("sum999")) { //System.out.println("999999"); mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("999999-begin") mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); } super.onMethodEnter() } override fun onMethodExit(opcode: Int) { println("onMethodExit access $access className $className name $name descriptor $descriptor") if (name.equals("sum999")) { //System.out.println("999999"); mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("999999-end") mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); } super.onMethodExit(opcode) } override fun visitFieldInsn(opcode: Int, owner: String?, name: String?, descriptor: String?) { println("visitFieldInsn opcode $opcode owner $owner name $name descriptor $descriptor") super.visitFieldInsn(opcode, owner, name, descriptor) } override fun visitLocalVariable( name: String?, descriptor: String?, signature: String?, start: Label?, end: Label?, index: Int ) { println("visitLocalVariable name $name descriptor $descriptor start $start end $end index $index") super.visitLocalVariable(name, descriptor, signature, start, end, index) } override fun visitAttribute(attribute: Attribute?) { println("visitAttribute attribute $attribute") super.visitAttribute(attribute) } override fun visitLineNumber(line: Int, start: Label?) { println("visitLineNumber line $line start $start") super.visitLineNumber(line, start) } override fun visitFrame( type: Int, numLocal: Int, local: Array<out Any>?, numStack: Int, stack: Array<out Any>? ) { println("visitFrame type $type numLocal $numLocal local $local numStack $numStack stack $stack") super.visitFrame(type, numLocal, local, numStack, stack) } override fun visitLabel(label: Label?) { println("visitLabel label $label") super.visitLabel(label) } override fun visitCode() { println("visitCode") super.visitCode() } override fun visitMaxs(maxStack: Int, maxLocals: Int) { println("visitMaxs maxStack $maxStack maxLocals $maxLocals") super.visitMaxs(maxStack, maxLocals) } override fun visitEnd() { println("visitEnd") super.visitEnd() } }
关键代码如下:
创建变量:
override fun visit( version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array<out String>? ) { println("visit access $access className $className name $name signature $signature") val numFiledVisitor = cv.visitField(Opcodes.ACC_PRIVATE, "age", "I", null, 25) numFiledVisitor.visitEnd() val strFiledVisitor = cv.visitField(Opcodes.ACC_PUBLIC, "str", "Ljava/lang/String;", null, "Hello World") strFiledVisitor.visitEnd() super.visit(version, access, name, signature, superName, interfaces) }
注意:
cv.visitField(Opcodes.ACC_PUBLIC, "str", "Ljava/lang/String;", null, "Hello World")也给变量赋值了,但是没有生效。
在init方法中给变量赋值:
override fun onMethodEnter() { println("onMethodEnter access $access className $className name $name descriptor $descriptor") if (name.equals("<init>") && descriptor.equals("()V")) { //每个classFile都有一个<init>的初始化方法(固定写法) mv.visitVarInsn(Opcodes.ALOAD, 0) mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false) mv.visitVarInsn(Opcodes.ALOAD, 0) //初始化定义的变量的值 mv.visitIntInsn(BIPUSH, 100) mv.visitFieldInsn(PUTFIELD, "com/baidu/main/MainActivity", "age", "I" ) //String的初始化会在构造方法中 mv.visitVarInsn(Opcodes.ALOAD, 0) mv.visitLdcInsn("Hello World") mv.visitFieldInsn(PUTFIELD, "com/baidu/main/MainActivity", "str", "Ljava/lang/String" ) mv.visitInsn(RETURN) } super.onMethodEnter() }
如果不会写,可以参考Test.class的ASM生成代码,直接照抄就行。
插桩后的代码如下:
package com.baidu.main; import android.os.Bundle; import androidx.activity.ComponentActivity; import kotlin.Metadata; /* compiled from: MainActivity.kt */ @Metadata(d1 = {"\u0000&\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\b\n\u0002\b\u0005\n\u0002\u0010\t\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0012\u0010\u0003\u001a\u00020\u00042\b\u0010\u0005\u001a\u0004\u0018\u00010\u0006H\u0014J\u0018\u0010\u0007\u001a\u00020\b2\u0006\u0010\t\u001a\u00020\b2\u0006\u0010\n\u001a\u00020\bH\u0002J\u0018\u0010\u000b\u001a\u00020\b2\u0006\u0010\t\u001a\u00020\b2\u0006\u0010\n\u001a\u00020\bH\u0002J\u0010\u0010\f\u001a\u00020\u00042\u0006\u0010\r\u001a\u00020\u000eH\u0002¨\u0006\u000f"}, d2 = {"Lcom/baidu/main/MainActivity;", "Landroidx/activity/ComponentActivity;", "()V", "onCreate", "", "savedInstanceState", "Landroid/os/Bundle;", "sum", "", "i", "j", "sum999", "test", "time", "", "app_debug"}, k = 1, mv = {1, 8, 0}, xi = 48) /* loaded from: classes4.dex */ public final class MainActivity extends ComponentActivity { private int age = 100; public String str; public MainActivity() { this.str = "Hello World"; } /* access modifiers changed from: protected */ @Override // androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); test(100); sum(1, 2); } private final void test(long time) { Thread.sleep(time); } private final int sum(int i, int j) { return i + j; } private final int sum999(int i, int j) { System.out.println("999999-begin"); int i2 = i + j; System.out.println("999999-end"); return i2; } }