ASM之FieldVisitor创建变量

简介: ASM之FieldVisitor创建变量

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;
    }
}


相关文章
|
6月前
|
Java Android开发
编写ASM代码工具类
编写ASM代码工具类
51 0
[ASM教程]#4创建对象和数组
这期我们要创建一个对象和数组。
90 0
[ASM教程]#2生成类
使用classWriter生成类
71 0
|
Oracle 关系型数据库 测试技术
GoldenGate 对asm磁盘的访问的两种方式:asm实例连接和API(dblogreader)两种方式的对比
GoldenGate在抽取数据的时候要读取online redo里的数据,online redo很多时候是储存在asm磁盘里,对asm磁盘的访问有两种方式
190 0
|
存储 Java API
ASM 关键接口 MethodVisitor
ASM 关键接口 MethodVisitor
435 0
ASM 关键接口 MethodVisitor
|
Java
ASM 库的 classVisitor 类解析
ASM 库的 classVisitor 类解析
466 0
|
Oracle 关系型数据库 数据库
ASM 创建pdb
asm创建pdb,asm克隆pdb.
808 0
|
SQL 关系型数据库 数据建模
asm 对磁盘名称的要求是否必须一致?
oracle asm 对磁盘名称并不要求一致。
1378 0
|
Oracle 关系型数据库 网络架构
asm 状态INTERMEDIATE
asm INTERMEDIATE,asm 磁盘中状态显示INTERMEDIATE。
3197 0
|
Oracle 关系型数据库 数据库