概述
是什么
ASM是一个字节码操作结构,针对ASM搞清楚如下两个问题:
- ASM处理的目标是字节码
- 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).
能做什么
组成部分
主流程
ClassReader、ClassVisitor以及ClassWriter三个类的联系构成了ASM的主流程,流程如下:
主流程阐明:
- 上述流程能够幻想成一条传送带,ClassReader是传送带的进口,ClassWriter是传送带的出口,ClassVisitor能够看成是传送带。
- 假设将传送带上的货物拿走,那该货物就不会跟着传送带打包出去。映射到代码的完成,即假如想要删去某个类的特点字段或许办法时,只需求将对应的FieldVisitor或许MethodVisitor回来Null即可。
Label效果:
首先,MethodVisitor类中的visitXXInsn办法是用来生成办法体代码的,指令只能是次序履行的,因而一般情况下,只能完成”次序”结构的代码,而运用Label,则能够完成”挑选”和”循环”的代码。
ClassFile与Stack Frame
ClassFile
常见字节码类库与ClassFile的联系,能够用下图表明:
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];
}
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; |
阐明:
- 引证类型: L + internal name + 分隔符
- 数组:[
办法映射
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就会出栈。
一、两个重要组成部分:
- 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
}
栈帧的改变进程如下图:
具体示例
怎么写
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")
}
}
}
成果打印:
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)
}
}
成果:
计划二
核心思路:先找到办法调用点,即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;
}
}
先上效果图:
代码插桩逻辑:
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", "用户名和暗码不正确.");
}
}
}
首要分成三部分:
- 界说辅佐类LoginSlice,并运用@Slice注解标示
- 界说一个hook办法,@Proxy注解标示,留意入参
a. 假如方针办法为实例办法,则该hook办法的第一个参数为Object,其他参数同方针办法参数
b. 假如方针办法为静态办法,则该hook办法的参数为方针办法参数 - 办法体的完成
a. 新增逻辑
b. SliceProxyInsn.invoke,表明调用原办法,即Login#login
注:SliceProxyInsn是AOP结构中界说的
public class SliceProxyInsn {
public static Object invoke(Object... args) {
return null;
}
}
效果图
即,在每个调用Login#login办法的类中,生成一个_generate_hookLogin_slice静态办法,调用login办法改成调用生成的静态办法。
完成原理
搜集
首要任务:
- 找到@Slice标识的类,找到@Proxy注解标识的办法,进行搜集,封装成SliceData目标
- 指令修正,即对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个部分:
- 加载一个数组,巨细为3
- Object[0] = ALOAD_0,加载局部变量表中下标为0的目标,赋值给Object[0],最后将Object[0]存储到局部变量表
- Object[1] = ALOAD_1,加载局部变量表中下标为1的目标,赋值给Object[1],最后将Object[1]存储到局部变量表
- Object[2] = ALOAD_2,加载局部变量表中下标为2的目标,赋值给Object[2],最后将Object[2]存储到局部变量表
- 调用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)
}
参考文献
- asm.ow2.io/