FieldVisitor使用abstract
修饰,用于创建变量,在使用时调用 ClassWriter.visitField即可创建FieldVisitor
方法介绍
visitField(Opcodes.ACC_PUBLIC, "str", "Ljava/lang/String;", null, "Hello World")
第一个参数是修饰类型,第二个参数是变量名,第三个是变量类型,第四个签名,第五个是变量的值(设置值好像没什么用,所以我在下面代码的初始化中重新初始化了str的值)
示例代码如下
java
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)
}
}
最终生成代码:
java
//
// 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类中创建两个变量,代码 如下:
java
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的字节码信息:
bash
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信息如下:
java
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进行插装
java
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()
}
}
关键代码如下:
创建变量:
java
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方法中给变量赋值:
java
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生成代码,直接照抄就行。
插桩后的代码如下:
java
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;
}
}