ASM从入门到精通

概述

作者:marsxingzhi111
链接:https://juejin.cn/post/7239923199608864826

是什么

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).

能做什么

e5570ab521f6238abd383f313ce17e91.jpeg

组成部分

ba5138547116b68fee00f1f98c768d87.jpeg

主流程

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

2aa73252b182a142821a43dfb381c00d.jpeg

主流程说明:

  1. 上述流程可以想象成一条传送带,ClassReader是传送带的入口,ClassWriter是传送带的出口,ClassVisitor可以看成是传送带。

  2. 假设将传送带上的货物拿走,那该货物就不会随着传送带打包出去。映射到代码的实现,即如果想要删除某个类的属性字段或者方法时,只需要将对应的FieldVisitor或者MethodVisitor返回Null即可。

Label作用:

首先,MethodVisitor类中的visitXXInsn方法是用来生成方法体代码的,指令只能是顺序执行的,因此一般情况下,只能实现"顺序"结构的代码,而使用Label,则可以实现"选择"和"循环"的代码。

ClassFile与Stack Frame

ClassFile

常见字节码类库与ClassFile的关系,可以用下图表示:

c8319f39022fa344ab2f820f423ef548.jpeg

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

28648eb860c2d21bb993fc4d97267005.jpeg

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

映射关系

Type描述符

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

Java TypeType descriptor
booleanZ
charC
byteB
shortS
intI
floatF
longJ
doubleD
ObjectLjava/lang/Object;
int[ ][I
Object[ ][ ][[Ljava/lang/Object;

说明:

  1. 引用类型: L + internal name + 分隔符

  2. 数组:[

方法映射

Java方法和Class方法的映射关系如下:

Method declaration in source fileMethod 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就会出栈。

a818a13d6443db7931c1d6c420871472.jpeg

一、两个重要组成部分:

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

栈帧的变化过程如下图:

b2e3197898352954392a6d2b7458b04b.jpeg

详细示例

如何写

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")
        }
    }
}

结果打印:

9c84bfb6c161ff1736f60f97d27ee3a3.jpeg

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)
    }
}
结果:

e6e834bc06f0e15e886353eefcac9db3.jpeg

方案二

核心思路:先找到方法调用点,即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;
    }
}

先上效果图:

5dc3c1cc7a059e45921b169b19594b01.jpeg

代码插桩逻辑:

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;
    }
}
效果图

7a444b36218976c928ada4341f61ffb8.jpeg

即,在每个调用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)
}

关注我获取更多知识或者投稿

a423f5de4cd1579bb3c744f5c48dafc2.jpeg

9a3c1c0237e1ca17008c3cc61166e247.jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值