概述

是什么

ASM是一个字节码操作结构,针对ASM搞清楚如下两个问题:

  1. ASM处理的目标是字节码
  2. ASM处理字节码的方式:分化、修正、重组

维基百科界说:

The ASM library is a project of the OW2 Consortium. It provides a simple API for decomposing, modifying, and recomposing binary Java classes (i.e. bytecode).

能做什么

ASM从入门到精通

组成部分

ASM从入门到精通

主流程

ClassReader、ClassVisitor以及ClassWriter三个类的联系构成了ASM的主流程,流程如下:

ASM从入门到精通

主流程阐明:

  1. 上述流程能够幻想成一条传送带,ClassReader是传送带的进口,ClassWriter是传送带的出口,ClassVisitor能够看成是传送带。
  2. 假设将传送带上的货物拿走,那该货物就不会跟着传送带打包出去。映射到代码的完成,即假如想要删去某个类的特点字段或许办法时,只需求将对应的FieldVisitor或许MethodVisitor回来Null即可。

Label效果:

首先,MethodVisitor类中的visitXXInsn办法是用来生成办法体代码的,指令只能是次序履行的,因而一般情况下,只能完成”次序”结构的代码,而运用Label,则能够完成”挑选”和”循环”的代码。

ClassFile与Stack Frame

ClassFile

常见字节码类库与ClassFile的联系,能够用下图表明:

ASM从入门到精通

class文件是遵从一定标准的,这个标准是由ClassFile界说的。

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

ASM从入门到精通

magic就是0xCAFEBABE,JVM通过这个字段判别是否是class文件,假如是class文件,才处理。

映射联系

Type描绘符

Java类型与Class类型的映射联系如下:

Java Type Type descriptor
boolean Z
char C
byte B
short S
int I
float F
long J
double D
Object Ljava/lang/Object;
int[ ] [I
Object[ ][ ] [[Ljava/lang/Object;

阐明:

  1. 引证类型: L + internal name + 分隔符
  2. 数组:[

办法映射

Java办法和Class办法的映射联系如下:

Method declaration in source file Method descriptor
void func(int i, float f) (IF)V
int func(Object o) (Ljava/lang/Object;)I
int[] func(int i, String s) (ILjava/lang/String;)[I
Obejct func(int[] i) ([I)Ljava/lang/Object;

Stack Frame

在程序运行进程中,会给每个线程在内存中分配一个JVM Stack;当线程履行完毕后,对应的JVM Stack内存空间也随之被回收。

Stack中存储的是Frames,一个办法对应一个Frames。当调用一个新办法时,会在Stack上分配一个Frames;办法退出时,Frames就会出栈。

ASM从入门到精通

一、两个重要组成部分:

  • operand stack:操作数栈
  • local variables:局部变量表

二、初始状况:

  • operand:空

  • local variables:根据办法是否是静态办法以及入参

    • 非静态办法,下标为的方位存放的是this
    • long和double类型占有两个方位,其他类型包括引证类型占有1个方位

三、示例:

办法一

public static void add (int a, int b) {
    ...
}

初始状况:local varibales: [I][I]

办法二:

public void add (long a, int b) {
    ...
}

初始状况:local varibales: [this][J][J][I]

简单示例

package com.mars.infra.asm;
import com.mars.infra.login.LoginService;
/**
 * @author geyan05
 * @date 2022/8/25
 */
public class HelloWorld {
    public static int add(int a, int b) {
        return a + b;
    }
    public long add(long a, int b) {
        return a + b;
    }
    public void login() {
        LoginService service = new LoginService();
        service.login();
    }
}

通过javap -c /Users/geyan05/projects/ASMDemo/app/build/intermediates/javac/debug/classes/com/mars/infra/asm/HelloWorld.class

public class com.mars.infra.asm.HelloWorld {
  public com.mars.infra.asm.HelloWorld();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
  public static int add(int, int);
    Code:
       0: iload_0
       1: iload_1
       2: iadd
       3: ireturn
  public long add(long, int);
    Code:
       0: lload_1
       1: iload_3
       2: i2l
       3: ladd
       4: lreturn
  public void login();
    Code:
       0: new           #2                  // class com/mars/infra/login/LoginService
       3: dup
       4: invokespecial #3                  // Method com/mars/infra/login/LoginService."<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method com/mars/infra/login/LoginService.login:()V
      12: return
}

栈帧的改变进程如下图:

ASM从入门到精通

具体示例

怎么写

val cr = ClassReader(bytes)
val cw = ClassWriter(ClassWriter.COMPUTE_FRAMES)
val cv = MyClassVisitor(Opcodes.ASM9, cw)
cr.accept(cv, ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)

留意:

引荐运用ClassWriter.COMPUTE_FRAMES,能够主动核算栈帧等信息,即在调用methodVisitor的visitMax(maxStack, maxLocals)办法时,即使实参出错了,也不会有任何的影响,由于当运用COMPUTE_FRAMES标签时,内部会主动批改maxStack和maxLocals。

case1:生成新类

package com.mars.infra.asm.generate
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Opcodes.*
/**
 * @author geyan05
 */
object HelloWorldDump: Opcodes {
    @JvmStatic
    fun dump(): ByteArray {
        val cw = ClassWriter(ClassWriter.COMPUTE_FRAMES)
        buildClass(cw)
        buildConstructor(cw)
        cw.visitEnd()
        return cw.toByteArray()
    }
    private fun buildConstructor(cw: ClassWriter) {
        val 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()
    }
    private fun buildClass(cw: ClassWriter) {
        cw.visit(V1_8, ACC_PUBLIC or ACC_SUPER, "generate/_HelloWorld", null, "java/lang/Object", null)
    }
}

case2:删去和新增字段

需求如下:

public class _HelloWorld {
    public String strValue;  // ASM:删去
//    public Object objValue;  ASM:新增
}

完成代码:

public class HelloWorldTransformCode {
    public static void main(String[] args) {
        byte[] bytes = FileUtils.readBytes(filePath);
        ClassReader classReader = new ClassReader(bytes);
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        FieldAddClassVisitor  fieldAddCV=
                new FieldAddClassVisitor(Opcodes.ASM9, classWriter, ACC_PUBLIC, "objValue", "Ljava/lang/Object;");
        FieldRemoveClassVisitor fieldRemoveCV = 
                new FieldRemoveClassVisitor(Opcodes.ASM9, classWriter, "strValue", "Ljava/lang/String;");
        classReader.accept(fieldAddCV, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
        FileUtils.writeBytes(filePath, classWriter.toByteArray());
    }
}

删去字段:

class FieldRemoveClassVisitor(
    api: Int,
    classVisitor: ClassVisitor,
    private val fieldName: String,
    private val fieldDesc: String
) : ClassVisitor(api, classVisitor) {
    override fun visitField(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        value: Any?
    ): FieldVisitor? {
        if (name.equals(fieldName) && descriptor.equals(fieldDesc)) {
            return null
        }
        return super.visitField(access, name, descriptor, signature, value)
    }
}

新增字段:

class FieldAddClassVisitor(
    api: Int,
    classVisitor: ClassVisitor,
    private val fieldAccess: Int,
    private val fieldName: String,
    private val fieldDesc: String
) : ClassVisitor(api, classVisitor) {
    private var hasField = false
    override fun visitField(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        value: Any?
    ): FieldVisitor {
        if (name.equals(fieldName) && descriptor.equals(fieldDesc)) {
            hasField = true
        }
        return super.visitField(access, name, descriptor, signature, value)
    }
    override fun visitEnd() {
        if (!hasField) {
            val fv = super.visitField(fieldAccess, fieldName, fieldDesc, null, null)
            fv?.visitEnd()
        }
        super.visitEnd()
    }
}

case3:新增办法

需求:新增 int add(int a, int b)办法 完成代码:

class MethodAddClassVisitor(
    classVisitor: ClassVisitor,
    private val methodAccess: Int,
    private val methodName: String,
    private val methodDesc: String
) : ClassVisitor(Opcodes.ASM9, classVisitor) {
    private var hasMethod = false
    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        if (name.equals(methodName) && descriptor.equals(methodDesc)) {
            hasMethod = true
        }
        return super.visitMethod(access, name, descriptor, signature, exceptions)
    }
    override fun visitEnd() {
        if (!hasMethod) {
            val mv = super.visitMethod(methodAccess, methodName, methodDesc, null, null)
            mv?.let {
                generateMethodBody(it)
            }
        }
        super.visitEnd()
    }
    private fun generateMethodBody(methodVisitor: MethodVisitor) {
        methodVisitor.visitCode()
        methodVisitor.visitVarInsn(Opcodes.ILOAD, 1)
        methodVisitor.visitVarInsn(Opcodes.ILOAD, 2)
        methodVisitor.visitInsn(Opcodes.IADD)
        methodVisitor.visitInsn(Opcodes.IRETURN)
        methodVisitor.visitMaxs(2, 3)
        methodVisitor.visitEnd()
    }
}

case4:查找办法A被哪些办法调用

需求:判别办法check被哪些办法调用

public class _HelloWorld {
    public String strValue;  // ASM:删去
//    public Object objValue;  ASM:新增
    public void login() {
        boolean check = check("zhangsan", "123");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void logout() {
        check("zhangsan", "123");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void test() {
    }
    private boolean check(String username, String pwd) {
        return "zhangsan".equals(username) && "123".equals(pwd);
    }
}

完成代码:

class MethodInvokeFindClassVisitor(
    classVisitor: ClassVisitor,
    private val invokedMethodName: String,
    private val invokedMethodDesc: String
) : ClassVisitor(Opcodes.ASM9, classVisitor) {
    private var className: String? = null
    override fun visit(
        version: Int,
        access: Int,
        name: String?,
        signature: String?,
        superName: String?,
        interfaces: Array<out String>?
    ) {
        super.visit(version, access, name, signature, superName, interfaces)
        className = name
    }
    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        var mv = super.visitMethod(access, name, descriptor, signature, exceptions)
        val isAbstractMethod = (access and Opcodes.ACC_ABSTRACT) != 0
        val isNativeMethod = (access and Opcodes.ACC_NATIVE) != 0
        if (!isAbstractMethod && !isNativeMethod) {
            mv = MethodInvokeFindV2Adapter(api, mv, className, name, descriptor,
                invokedMethodName, invokedMethodDesc)
        }
        return  mv
    }
}
class MethodInvokeFindV2Adapter(
    api: Int,
    methodVisitor: MethodVisitor,
    private val className: String?,
    private val curMethodName: String?,
    private val curMethodDesc: String?,
    private val invokedMethodName: String,
    private val invokedMethodDesc: String) : MethodVisitor(api, methodVisitor) {
    private val methodInvokeSet = mutableSetOf<String>()
    override fun visitMethodInsn(
        opcode: Int,
        owner: String?,
        name: String?,
        descriptor: String?,
        isInterface: Boolean
    ) {
        if (name == invokedMethodName && descriptor == invokedMethodDesc) {
            val info = String.format(
                "%s %s.%s%s", Printer.OPCODES[opcode], className, curMethodName, curMethodDesc)
            methodInvokeSet.add(info)
        }
        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
    }
    override fun visitEnd() {
        super.visitEnd()
        methodInvokeSet.forEach { info ->
            println("${invokedMethodName}${invokedMethodDesc} is invoked by $info")
        }
    }
}

成果打印:

ASM从入门到精通

case5:删去Log句子

需求:删去项目中的Log句子,例如:Log.i(TAG, “onCreate”)

示例:

public class _HelloWorld {
 static {
        Log.e("gy", "this is static code");
        System.out.println("ss");
    }
    public void say() {
        Log.i("gy", "this is say method");
    }
    public static int build(String key, String value) {
        Log.d("gy", "this is build method");
        return -1;
    }
}

以Log.i(“gy”, “this is say method”)为例,其对应的Instruction如下:

Ldc

Ldc

INVOKESTATIC, “sample01/utils/Log”, “e”, “(Ljava/lang/String;Ljava/lang/String;)V”

计划一

关于简单的句子删去,能够运用状况机处理

设置三个状况,分别为初始状况、STATUS_LDC、STATUC_LDC_LDC 代码完成:

class RemoveLogClassVisitor(classVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM9, classVisitor) {
    override fun visit(
        version: Int,
        access: Int,
        name: String?,
        signature: String?,
        superName: String?,
        interfaces: Array<out String>?
    ) {
        super.visit(version, access, name, signature, superName, interfaces)
    }
    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        var mv = super.visitMethod(access, name, descriptor, signature, exceptions)
        val isAbstractMethod = (access and Opcodes.ACC_ABSTRACT) != 0
        val isNativeMethod = (access and Opcodes.ACC_NATIVE) != 0
        if (!isAbstractMethod && !isNativeMethod) {
            mv = RemoveLogAdapter(api, mv)
        }
        return mv
    }
}
class RemoveLogAdapter(api: Int, methodVisitor: MethodVisitor) :
    MethodPatternAdapter(api, methodVisitor) {
    private val STATUC_LDC = 1
    private val STATUC_LDC_LDC = 2
    private var isLdcFromLog = false
    override fun visitInsn() {
        when (state) {
            STATUC_LDC -> {
                mv.visitLdcInsn(ldcValue)
            }
            STATUC_LDC_LDC -> {
                if (!isLdcFromLog) {
                    mv.visitLdcInsn(ldcValue)
                    mv.visitLdcInsn(ldcValue2)
                }
            }
        }
        state = STATUS_INIT
    }
    private var ldcValue: Any? = null
    private var ldcValue2: Any? = null
    override fun visitLdcInsn(value: Any?) {
        when (state) {
            STATUS_INIT -> {
                state = STATUC_LDC
                ldcValue = value
                return
            }
            STATUC_LDC -> {
                state = STATUC_LDC_LDC
                ldcValue2 = value
                return
            }
        }
        super.visitLdcInsn(value)
    }
    override fun visitMethodInsn(
        opcode: Int,
        owner: String?,
        name: String?,
        descriptor: String?,
        isInterface: Boolean
    ) {
        when (state) {
            STATUC_LDC_LDC -> {
                if (opcode == Opcodes.INVOKESTATIC
                    && owner == "sample01/utils/Log"
                    && (name == "i" || name == "d" || name == "e")
                    && descriptor == "(Ljava/lang/String;Ljava/lang/String;)V"
                ) {
                    isLdcFromLog = true
                    return
                }
            }
        }
        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
    }
}

成果:

ASM从入门到精通

计划二

核心思路:先找到办法调用点,即MethodInsnNode,opcode是INVOKEXXX,以该指令为起始点,结合入参的个数,逆序删去相应的指令。相关的逻辑,详见后边的AOP部分

case6:服务发现

需求:路由结构里面完成服务发现的功用

代码完成:

object Router {
    fun <T> getService(serviceClass: Class<T>): T? {
        if (DowngradeManager.isForceDowngrade()) {
            val service =  DowngradeManager.getDowngradeImpl(serviceClass)
            if (service != null) {
                return service
            }
        }
        val serviceImpl = ServiceManager.getService(serviceClass)
        if (serviceImpl != null) {
            return serviceImpl
        }
        val downgradeImpl = DowngradeManager.getDowngradeImpl(serviceClass)
        if (downgradeImpl != null) {
            return downgradeImpl
        }
        return null
    }
}
public class ServiceManager {
    // ASM
    public static <T> T getService(Class<T> serviceClass) {
        return null;
    }
}

先上效果图:

ASM从入门到精通

代码插桩逻辑:

class ServiceImplMethodVisitorV2(
    api: Int,
    methodVisitor: MethodVisitor,
    private val serviceImplSet: Set<String>?
) : MethodVisitor(api, methodVisitor) {
    private fun inject(isStart: Boolean, isLast: Boolean, data: ServiceImplData) {
        val label = Label()
        mv.visitVarInsn(Opcodes.ALOAD, 0)
        mv.visitMethodInsn(
            Opcodes.INVOKEVIRTUAL,
            "java/lang/Class",
            "getName",
            "()Ljava/lang/String;",
            false
        )
        mv.visitLdcInsn(data.interfaceClass)
        mv.visitMethodInsn(
            Opcodes.INVOKEVIRTUAL,
            "java/lang/String",
            "equals",
            "(Ljava/lang/Object;)Z",
            false
        )
        mv.visitJumpInsn(Opcodes.IFEQ, label)  // 假如等于0,即false,则跳转到label标签对应的代码地方
        mv.visitTypeInsn(Opcodes.NEW, data.implementClass)
        mv.visitInsn(Opcodes.DUP)
        mv.visitMethodInsn(
            Opcodes.INVOKESPECIAL,
            data.implementClass,
            "<init>",
            "()V",
            false
        )
        mv.visitVarInsn(Opcodes.ASTORE, 1)
        mv.visitVarInsn(Opcodes.ALOAD, 1)
        mv.visitInsn(Opcodes.ARETURN)
        mv.visitLabel(label)
    }
    override fun visitInsn(opcode: Int) {
        if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
            mv.visitCode()
            val serviceDataList = ServiceImplManager.getDataList()
            for (i in serviceDataList.indices) {
                val data: ServiceImplData = serviceDataList[i]
                inject(i == 0, i == serviceDataList.size - 1, data)
            }
            mv.visitInsn(Opcodes.ACONST_NULL)
            mv.visitInsn(Opcodes.ARETURN)
            mv.visitMaxs(2, 2)
            mv.visitEnd()
        }
        super.visitInsn(opcode)
    }
}

case7:AOP

是什么

AOP,面向切面编程,旨在将横切关注点与事务主体进一步分离,以提高程序代码的模块化程度。

怎么运用

示例

场景:主事务调用登录SDK,进行登录操作。

1、登录SDK的Login完成如下:

public class Login {
    ...
    public void login(String username, String password) {
        System.out.println("开端登录~");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(username + " 登录成功");
    }
}

2、主事务开端登录,完成代码如下:

public class LoginService {
    public static void startLogin() {
        Log.e("LoginService", "invoke login");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 真正履行登录逻辑
        Login login = new Login();
        login.login("zhangsan", "123456");
        Log.i("LoginService", "login end");
    }
}

遇到问题:各个事务方在调用登录办法时,没有对用户名和暗码做有用性的判别。此刻,能够运用AOP解决上述问题。

运用

界说一个辅佐类,该类不会打进APK中,代码如下:

@Slice
class LoginSlice {
    @Proxy(owner = "com/mars/infra/mixin/lib/Login", name = "login", isStatic = false)
    public static void hookLogin(Object obj, String username, String password) {
        System.out.println("start hookLogin invoke.");
        if (LoginUtils.check(username, password)) {
            SliceProxyInsn.invoke(obj, username, password);
        } else {
            Log.e("Login", "用户名和暗码不正确.");
        }
    }
}

首要分成三部分:

  1. 界说辅佐类LoginSlice,并运用@Slice注解标示
  2. 界说一个hook办法,@Proxy注解标示,留意入参
    a. 假如方针办法为实例办法,则该hook办法的第一个参数为Object,其他参数同方针办法参数
    b. 假如方针办法为静态办法,则该hook办法的参数为方针办法参数
  3. 办法体的完成
    a. 新增逻辑
    b. SliceProxyInsn.invoke,表明调用原办法,即Login#login

注:SliceProxyInsn是AOP结构中界说的

public class SliceProxyInsn {
    public static Object invoke(Object... args) {
        return null;
    }
}

效果图

ASM从入门到精通

即,在每个调用Login#login办法的类中,生成一个_generate_hookLogin_slice静态办法,调用login办法改成调用生成的静态办法。

完成原理

搜集

首要任务:

  1. 找到@Slice标识的类,找到@Proxy注解标识的办法,进行搜集,封装成SliceData目标
  2. 指令修正,即对SliceProxyInsn指令进行”desugar”操作
    a. 回来值
    b. 入参
data class SliceData(
    val owner: String?,  // com/mars/infra/mixin/hook/LoginSlice
    val methodName: String?,  // hookLogin
    val descriptor: String?,  // (Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V
    var proxyData: ProxyData? = null,  // 注解的信息
    val methodNode: MethodNode? = null  // hookLogin办法的指令
)

读取class,找到@Slice标识的类

object Slice {
    ...
    private fun forEachJar(jarInput: JarInput) {
        ZipFile(jarInput.file).use { originJar ->
            originJar.entries().iterator().forEach { zipEntry ->
                if (!zipEntry.isDirectory && zipEntry.name.endsWith(".class")) {
                    collectInternalV2(originJar.getInputStream(zipEntry))
                }
            }
        }
    }
    private fun collectInternalV2(inputStream: InputStream) {
        inputStream.use {
            val classReader = ClassReader(it.readBytes())
            val classNode = ClassNode()
            classReader.accept(classNode, ClassReader.EXPAND_FRAMES)
            // Slice注解是AnnotationRetention.BINARY,因而运用invisibleAnnotations
            var sliceClass = false
            classNode.invisibleAnnotations?.forEach { node ->
                if (node.desc == ANNOTATION_SLICE) {   // "Lcom/mars/infra/mixin/annotations/Slice;"
                    sliceClass = true
                    sliceHookClasses.add(classNode.name)
                }
            }
            sliceClass.yes {  // 当时遍历的class是@Slice标识的辅佐类,例如:LoginSlice
                classNode.handleNode()
            }
        }
    }
}

处理辅佐类,@Slice标识的类

  • 结构SliceData目标
  • 处理SliceProxyInsn.invoke办法
    • 回来值
    • 入参
private fun ClassNode.handleNode() {
    methods.asIterable().filter {
        it.access and Opcodes.ACC_ABSTRACT == 0
                && it.access and Opcodes.ACC_NATIVE == 0
                && it.name != "<init>"
    }.forEach { methodNode ->
        // 1. 结构SliceData目标
        val sliceData = SliceData(name, methodNode.name, methodNode.desc, methodNode = methodNode)
        // 只有Proxy注解标识的办法,才是hook办法
        var isHookMethod = false
        methodNode.invisibleAnnotations?.forEach { annotationNode ->
            if (annotationNode.desc == ANNOTATION_PROXY) {
                isHookMethod = true
                var index = 0
                val owner = (annotationNode.values[++index] as String).getInternalName()
                index++
                val name = annotationNode.values[++index] as String
                index++
                val isStatic = annotationNode.values[++index] as Boolean
                val argumentTypes = Type.getArgumentTypes(methodNode.desc)
                val returnType = Type.getReturnType(methodNode.desc)
                var realDescriptor = "("
                for (i in argumentTypes.indices) {
                    if (i == 0 && !isStatic) {
                        continue
                    }
                    realDescriptor += argumentTypes[i]
                }
                realDescriptor += ")"
                realDescriptor += returnType.descriptor
                // 2. 结构ProxyData目标
                sliceData.proxyData = ProxyData(owner, name, realDescriptor, isStatic)
                checkHookMethodExist(owner, name, success = {
                    Slice.sliceDataMap[it] = sliceData
                }, error = {
                    throw Exception("${owner}.${name} 办法现已被hook了,不能重复hook")
                })
            }
        }
        isHookMethod.yes {
            methodNode.instructions.iterator().forEach {
                if (it is MethodInsnNode
                    && it.owner == PROXY_INSN_CHAIN_NAME
                    && it.name == "invoke"
                ) {
                    val returnType = Type.getReturnType(methodNode.desc)
                    // 1. SliceProxyInsn.invoke是有回来值的,假如方针办法是不带回来值的,则需求移除hook办法中的POP指令
                    if (returnType == Type.VOID_TYPE) {
                        if (it.next.opcode == Opcodes.POP) {
                            methodNode.instructions.remove(it.next)
                        }
                    } else {
                        /**
                         * 示例一:
                         * boolean res = (boolean) SliceProxyInsn.invoke(code);
                         * return res;
                         *
                         * 对应指令如下:
                         * MethodInsnNode(INVOKESTATIC, "run/test/SliceProxyInsn", "invoke", "([Ljava/lang/Object;)Ljava/lang/Object;", false)
                         * TypeInsnNode(CHECKCAST, "java/lang/Boolean")
                         * MethodInsnNode(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false)
                         *
                         * 示例二:
                         * Boolean res = (Boolean) SliceProxyInsn.handle(username, password, code);
                         *
                         * 对应的指令如下:
                         * MethodInsnNode(INVOKESTATIC, "run/test/SliceProxyInsn", "invoke", "([Ljava/lang/Object;)Ljava/lang/Object;", false)
                         * TypeInsnNode(CHECKCAST, "java/lang/Boolean")
                         *
                         * 运用者不同的行为会呈现可能会有拆箱指令,可能也没有拆箱指令。
                         * 因而,Slice中为了一致判别,则不采纳删去指令(无法知道要不要删去拆箱的),而是采纳新增装箱的指令
                         * 1. 删去CHECKCAST指令
                         * 2. 添加装箱指令
                         */
                        val nextInsnNode = it.next
                        if (nextInsnNode.opcode == Opcodes.CHECKCAST && nextInsnNode is TypeInsnNode) {
                            val boxType = typeMap[returnType]
                            boxType?.let { boxType ->
                                val boxInsnNode = MethodInsnNode(
                                    Opcodes.INVOKESTATIC,
                                    boxType.internalName,
                                    "valueOf",
                                    "(${returnType.descriptor})${boxType.descriptor}",
                                    false)
                                methodNode.instructions.insert(it, boxInsnNode)
                            }
                            methodNode.instructions.remove(nextInsnNode)
                        }
                    }
                    // 3. 入参的处理
                    if (it.name == "invoke") {
                        val argumentTypes = Type.getArgumentTypes(methodNode.desc)
                        methodNode.desugarInstruction(argumentTypes, it, sliceData.proxyData!!)
                    }
                }
            }
        }
    }
}

SliceProxyInsn#invoke入参的修正
一句话描绘:SliceProxyInsn.invoke办法参数是可变参数,即描绘符为 ([Ljava/lang/Object;)Ljava/lang/Object;

例如:SliceProxyInsn.invoke(username, password, code)对应的指令如下:

new InsnNode(ICONST_3);
new TypeInsnNode(ANEWARRAY, "java/lang/Object"));
new InsnNode(DUP);
new InsnNode(ICONST_0);
new VarInsnNode(ALOAD, 0);
new InsnNode(AASTORE);
new InsnNode(DUP);
new InsnNode(ICONST_1);
new VarInsnNode(ALOAD, 1);
new InsnNode(AASTORE);
new InsnNode(DUP);
new InsnNode(ICONST_2);
new VarInsnNode(ILOAD, 2);
new MethodInsnNode(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
new InsnNode(AASTORE);
new MethodInsnNode(INVOKESTATIC, "run/test/SliceProxyInsn", "invoke", "([Ljava/lang/Object;)Ljava/lang/Object;", false);
new TypeInsnNode(CHECKCAST, "java/lang/Boolean");
new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);

上述指令能够分成5个部分:

  1. 加载一个数组,巨细为3
  2. Object[0] = ALOAD_0,加载局部变量表中下标为0的目标,赋值给Object[0],最后将Object[0]存储到局部变量表
  3. Object[1] = ALOAD_1,加载局部变量表中下标为1的目标,赋值给Object[1],最后将Object[1]存储到局部变量表
  4. Object[2] = ALOAD_2,加载局部变量表中下标为2的目标,赋值给Object[2],最后将Object[2]存储到局部变量表
  5. 调用SliceProxyInsn.invoke指令

然而,咱们需求的指令如下:

new VarInsnNode(ALOAD, 0);
new VarInsnNode(ALOAD, 1);
new VarInsnNode(ILOAD, 2);
new MethodInsnNode(INVOKESTATIC, "run/test/SliceProxyInsn", "invoke", "(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/Object;", false);   

因而,需求将剩余的指令删去,代码如下:

/**
 * 指令脱糖
 *
 * il.add(new InsnNode(ICONST_3));
 * il.add(new TypeInsnNode(ANEWARRAY, "java/lang/Object"));
 *
 * il.add(new InsnNode(DUP));
 * il.add(new InsnNode(ICONST_0));
 * il.add(new VarInsnNode(ALOAD, 0));
 * il.add(new InsnNode(AASTORE));
 *
 * il.add(new InsnNode(DUP));
 * il.add(new InsnNode(ICONST_1));
 * il.add(new VarInsnNode(ALOAD, 1));
 * il.add(new InsnNode(AASTORE));
 *
 * il.add(new InsnNode(DUP));
 * il.add(new InsnNode(ICONST_2));
 * il.add(new VarInsnNode(ILOAD, 2));
 * il.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false));
 * il.add(new InsnNode(AASTORE));
 *
 * il.add(new MethodInsnNode(INVOKESTATIC, "run/test/SliceProxyInsn", "invoke", "([Ljava/lang/Object;)Ljava/lang/Object;", false));
 * 逆序查找
 */
fun MethodNode.desugarInstruction(argumentTypes: Array<Type>?, handleInsnNode: MethodInsnNode, proxyData: ProxyData) {
    if (argumentTypes == null || argumentTypes.isEmpty()) {
        throw Exception("暂不支撑无参函数的hook")
    }
    // 1. handleInsnNode对应的是SliceProxyInsn.invoke指令,findValidPreviousInsnNode递归找到前面一个有用指令
    val lastAASTOREInsn = handleInsnNode.findValidPreviousInsnNode()
        ?: throw Exception("handle办法呈现异常, errorCode = 1")
    var cur = lastAASTOREInsn.previous?: throw Exception("handle办法呈现异常, errorCode = 1.5")
    // 2. 找到ANEWARRAY
    while (!(cur.opcode == Opcodes.ANEWARRAY && cur is TypeInsnNode && cur.desc == TYPE_ANY.internalName)) {
        cur = cur.previous
    }
    // 3. 当时cur对应TypeInsnNode(ANEWARRAY, "java/lang/Object")
    val pre = cur.previous?: throw Exception("handle办法呈现异常, errorCode = 2")  // 对应数组size
    if (pre.opcode in Opcodes.ICONST_0..Opcodes.ICONST_5) {
        val size = pre.opcode - Opcodes.ICONST_0
        if (size == argumentTypes.size) {
            instructions.remove(pre)  // 删去InsnNode(ICONST_3)
        }
    }
    // 4. 删去TypeInsnNode(ANEWARRAY, "java/lang/Object")
    var headNode = cur.next
    instructions.remove(cur)  
    // dup ---> aastore作为一个block,有几个入参,就有几个block
    for (index in argumentTypes.indices) {
        // headNode对应DUP指令
        val insnNode = headNode.findValidNextInsnNode()!!
        if (insnNode.opcode in Opcodes.ICONST_0..Opcodes.ICONST_5) {
            val i = insnNode.opcode - Opcodes.ICONST_0
            if (i != index) {
                throw Exception("handle办法呈现异常, errorCode = 4")
            }
            // 5. 删去InsnNode(DUP)
            instructions.remove(headNode)  
            headNode = insnNode.next?: throw Exception("handle办法呈现异常, errorCode = 5") // 此刻dupHead对应VarInsnNode(ALOAD, i),这个需求保存
            instructions.remove(insnNode)  // 删去InsnNode(ICONST_0)
            // 6. 寻找aastore了
            while (headNode != lastAASTOREInsn && headNode.opcode != Opcodes.AASTORE) {
                headNode = headNode.next
            }
            // dupHead此刻等于InsnNode(AASTORE)
            if (i != argumentTypes.size - 1 && headNode == lastAASTOREInsn) {
                throw Exception("handle办法呈现异常, errorCode = 6")
            }
            // 7. 强转指令,在调用SliceProxyInsn的invoke办法之前,假如是实例办法,需求将ALOAD_0的值进行强转
            if (i == 0 && !proxyData.isStatic) {
                val checkCastInsnNode = TypeInsnNode(Opcodes.CHECKCAST, proxyData.owner)
                instructions.insert(headNode.previous, checkCastInsnNode)  // 不是在handleInsnNode指令之前,而是在AASTORE之前
            }
            ...
            // 8. 下一个dup指令
            val nextDup = headNode.findValidNextInsnNode()  // 下一块的开端节点,即下一个dup指令
            instructions.remove(headNode)  // 移除aastore指令
            headNode  = nextDup
        }
    }
}
/**
 * 查找有用的前序,即排除LineNumberNode和LabelNode
 */
fun AbstractInsnNode.findValidPreviousInsnNode(): AbstractInsnNode? {
    return previous?.let {
        if (it.isValidInsnNode()) {
            it
        } else {
            it.findValidPreviousInsnNode()
        }
    }
}
fun AbstractInsnNode.findValidNextInsnNode(): AbstractInsnNode? {
    return next?.let {
        if (it.isValidInsnNode()) {
            it
        } else {
            it.findValidNextInsnNode()
        }
    }
}
fun AbstractInsnNode.isValidInsnNode(): Boolean {
    return !(this is LineNumberNode || this is LabelNode)
}

修正

添加办法

一句话描绘:在调用方针办法的类中,新增静态办法,转而以静态办法取代方针办法。

新建办法,即办法名和对应的描绘符

class MixinClassNode(private val classVisitor: ClassVisitor?) : ClassNode(Opcodes.ASM7), IHook {
    override fun hook(insnNode: MethodInsnNode, sliceData: SliceData) {
        hasHook.no {
            ...
            val hookMethodNode = MethodNode(Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC,
                sliceData.methodName.buildMixinMethodName(),
                sliceData.descriptor, null, null)
            methods.add(hookMethodNode)
            ...
        }
    }
}

将hook办法的办法体的指令写入该新建的办法中,假如遇到SliceProxyInsn.invoke指令,则写入原指令

class MixinClassNode(private val classVisitor: ClassVisitor?) : ClassNode(Opcodes.ASM7), IHook {
    override fun hook(insnNode: MethodInsnNode, sliceData: SliceData) {
        hasHook.no {
            ...
            sliceData.methodNode?.accept(object: MethodVisitor(Opcodes.ASM7, hookMethodNode) {
                private var hasInvokeMixinProxyInsn = false
                override fun visitMethodInsn(
                    opcode: Int,
                    owner: String?,
                    name: String?,
                    descriptor: String?,
                    isInterface: Boolean
                ) {
                    if (owner == PROXY_INSN_CHAIN_NAME) {  // "com/mars/infra/mixin/annotations/SliceProxyInsn"
                        // 原指令写入
                        hasInvokeMixinProxyInsn = true
                        insnNode.accept(mv)
                    } else {
                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
                    }
                }
                ...
            })
        }
    }
}
调用新办法

删去原办法的指令,插入新办法的指令

private fun MethodNode.modify(insnNode: MethodInsnNode, sliceData: SliceData, owner: String) {
        val newMethodInsnNode =
            MethodInsnNode(
                Opcodes.INVOKESTATIC,  // 这里始终是调用静态办法
                owner,
                sliceData.methodName.buildMixinMethodName(),  // "_generate_hookXxxx_slice"
                sliceData.descriptor,
                false
            )
    instructions.insert(insnNode, newMethodInsnNode)
    instructions.remove(insnNode)
}

参考文献

  1. asm.ow2.io/