Java ASM系列:(030)查找已有的方法(查找-方法调用)

2021/7/1 20:22:09

本文主要是介绍Java ASM系列:(030)查找已有的方法(查找-方法调用),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

本文属于[Java ASM系列一:Core API](https://blog.51cto.com/lsieun/2924583)当中的一篇。 ## 1. 查找Instruction ### 1.1 如何查找Instruction 在方法当中,查找某一个特定的Instruction,那么应该怎么做呢?简单来说,就是**通过`MethodVisitor`类当中定义的`visitXxxInsn()`方法来查找**。 让我们回顾一下`MethodVisitor`类当中定义了哪些`visitXxx()`方法。 ![methodvisitorvisitxxxinsnmethods.png](http://www.www.zyiz.net/i/li/?n=2&i=images/20210629/1624967433536552.png?,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) 在`MethodVisitor`类当中,定义的主要`visitXxx()`方法可以分成四组: - 第一组,`visitCode()`方法,标志着方法体(method body)的开始。 - 第二组,`visitXxxInsn()`方法,对应方法体(method body)本身,这里包含多个方法。 - 第三组,`visitMaxs()`方法,标志着方法体(method body)的结束。 - 第四组,`visitEnd()`方法,是最后调用的方法。 在方法当中,任何一条Instruction,放在ASM代码中,它都是通过调用`MethodVisitor.visitXxxInsn()`方法的形式来呈现的。换句话说,想去找某一条特定的Instruction,分成两个步骤: - 第一步,找到该Instruction对应的`visitXxxInsn()`方法。 - 第二步,对该`visitXxxInsn()`方法接收的`opcode`和其它参数进行判断。 **简而言之,查找Instruction的过程,就是对`visitXxxInsn()`方法接收的参数进行检查的过程。** 举一个形象的例子,平时我们坐地铁,随身物品要过安检,其实就是对书包(方法)里的物品(参数)进行检查,如下图: ![安检机器](http://www.www.zyiz.net/i/li/?n=2&i=images/20210701/1625137506187777.jpg?,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) ### 1.2 Class Analysis **查找Instruction的过程,** 并不属于Class Transformation(因为没有生成新的类),而**是属于Class Analysis。** 在下图当中,Class Analysis包括find potential bugs、detect unused code和reverse engineer code等操作。但是,这些分析操作(analysis)是比较困难的,它需要编程经验的积累和对问题模式的识别,需要编码处理各种不同情况,所以不太容易实现。 ![ASM能够做什么](http://www.www.zyiz.net/i/li/?n=2&i=images/20210618/1624005632705532.png?,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) 但是,Class Analysis,并不是只包含复杂的分析操作,也包含一些简单的分析操作。例如,当前方法里调用了哪些其它的方法、当前的方法被哪些别的方法所调用。对于方法的调用,就对应着`MethodVisitor.visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface)`方法。 另外,要注意一点:在Class Transformation当中,需要用到`Cla***eader`、`ClassVisitor`和`ClassWriter`类;但是,在Class Analysis中,我们只需要用到`Cla***eader`和`ClassVisitor`类,而不需要用到`ClassWriter`类。 ![ASM里的核心类](http://www.www.zyiz.net/i/li/?n=2&i=images/20210618/1624028333369109.png?,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) ## 2. 示例一:调用了哪些方法 ### 2.1 预期目标 假如有一个`HelloWorld`类,代码如下: ```java public class HelloWorld { public void test(int a, int b) { int c = Math.addExact(a, b); String line = String.format("%d + %d = %d", a, b, c); System.out.println(line); } } ``` 我们想要实现的预期目标:打印出`test()`方法当中调用了哪些方法。 在编写ASM代码之前,可以使用`javap`命令查看`test()`方法所包含的Instruction内容: ```text $ javap -c sample.HelloWorld Compiled from "HelloWorld.java" public class sample.HelloWorld { ... public void test(int, int); Code: 0: iload_1 1: iload_2 2: invokestatic #2 // Method java/lang/Math.addExact:(II)I 5: istore_3 6: ldc #3 // String %d + %d = %d 8: iconst_3 9: anewarray #4 // class java/lang/Object 12: dup 13: iconst_0 14: iload_1 15: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 18: aastore 19: dup 20: iconst_1 21: iload_2 22: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 25: aastore 26: dup 27: iconst_2 28: iload_3 29: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 32: aastore 33: invokestatic #6 // Method java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; 36: astore 4 38: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 41: aload 4 43: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 46: return } ``` ### 2.2 编码实现 ```java import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.util.Printer; import java.util.ArrayList; import java.util.List; public class MethodFindInvokeVisitor extends ClassVisitor { private final String methodName; private final String methodDesc; public MethodFindInvokeVisitor(int api, ClassVisitor classVisitor, String methodName, String methodDesc) { super(api, classVisitor); this.methodName = methodName; this.methodDesc = methodDesc; } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { if (methodName.equals(name) && methodDesc.equals(descriptor)) { return new MethodFindInvokeAdapter(api, null); } return null; } private static class MethodFindInvokeAdapter extends MethodVisitor { private final List list = new ArrayList<>(); public MethodFindInvokeAdapter(int api, MethodVisitor methodVisitor) { super(api, methodVisitor); } @Override public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { // 首先,处理自己的代码逻辑 String info = String.format("%s %s.%s%s", Printer.OPCODES[opcode], owner, name, descriptor); if (!list.contains(info)) { list.add(info); } // 其次,调用父类的方法实现 super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); } @Override public void visitEnd() { // 首先,处理自己的代码逻辑 for (String item : list) { System.out.println(item); } // 其次,调用父类的方法实现 super.visitEnd(); } } } ``` ### 2.3 进行分析 ```java import lsieun.utils.FileUtils; import org.objectweb.asm.Cla***eader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; public class HelloWorldAnalysisCore { public static void main(String[] args) { String relative_path = "sample/HelloWorld.class"; String filepath = FileUtils.getFilePath(relative_path); byte[] bytes = FileUtils.readBytes(filepath); //(1)构建Cla***eader Cla***eader cr = new Cla***eader(bytes); //(2)分析ClassVisitor int api = Opcodes.ASM9; ClassVisitor cv = new MethodFindInvokeVisitor(api, null, "test", "(II)V"); //(3)结合Cla***eader和ClassVisitor int parsingOptions = Cla***eader.SKIP_DEBUG | Cla***eader.SKIP_FRAMES; cr.accept(cv, parsingOptions); } } ``` 输出结果: ```text INVOKESTATIC java/lang/Math.addExact(II)I INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer; INVOKESTATIC java/lang/String.format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V ``` ## 3. 示例二:被哪些方法所调用 在IDEA当中,有一个Find Usages功能:在类名、字段名、或方法名上,右键之后,选择Find Usages,就可以查看该项内容在哪些地方被使用了。 ![Find Usages](http://www.www.zyiz.net/i/li/?n=2&i=images/20210701/1625137638539075.png?,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) 查找结果如下: ![Find Usages Result](http://www.www.zyiz.net/i/li/?n=2&i=images/20210701/1625137667714870.png?,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) 这样一个功能,如果我们自己来实现,那该怎么编写ASM代码呢? ### 3.1 预期目标 假如有一个`HelloWorld`类,代码如下: ```java public class HelloWorld { public int add(int a, int b) { int c = a + b; test(a, b, c); return c; } public int sub(int a, int b) { int c = a - b; test(a, b, c); return c; } public int mul(int a, int b) { return a * b; } public int div(int a, int b) { return a / b; } public void test(int a, int b, int c) { String line = String.format("a = %d, b = %d, c = %d", a, b, c); System.out.println(line); } } ``` 我们想要实现的预期目标:找出是哪些方法对`test()`方法进行了调用。 ### 3.2 编码实现 ```java import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import java.util.ArrayList; import java.util.List; import static org.objectweb.asm.Opcodes.ACC_ABSTRACT; import static org.objectweb.asm.Opcodes.ACC_NATIVE; public class MethodFindRefVisitor extends ClassVisitor { private final String methodOwner; private final String methodName; private final String methodDesc; private String owner; private final List resultList = new ArrayList<>(); public MethodFindRefVisitor(int api, ClassVisitor classVisitor, String methodOwner, String methodName, String methodDesc) { super(api, classVisitor); this.methodOwner = methodOwner; this.methodName = methodName; this.methodDesc = methodDesc; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); this.owner = name; } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0; boolean isNativeMethod = (access & ACC_NATIVE) != 0; if (!isAbstractMethod && !isNativeMethod) { return new MethodFindRefAdaptor(api, null, owner, name, descriptor); } return null; } @Override public void visitEnd() { // 首先,处理自己的代码逻辑 for (String item : resultList) { System.out.println(item); } // 其次,调用父类的方法实现 super.visitEnd(); } private class MethodFindRefAdaptor extends MethodVisitor { private final String currentMethodOwner; private final String currentMethodName; private final String currentMethodDesc; public MethodFindRefAdaptor(int api, MethodVisitor methodVisitor, String currentMethodOwner, String currentMethodName, String currentMethodDesc) { super(api, methodVisitor); this.currentMethodOwner = currentMethodOwner; this.currentMethodName = currentMethodName; this.currentMethodDesc = currentMethodDesc; } @Override public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { // 首先,处理自己的代码逻辑 if (methodOwner.equals(owner) && methodName.equals(name) && methodDesc.equals(descriptor)) { String info = String.format("%s.%s%s", currentMethodOwner, currentMethodName, currentMethodDesc); if (!resultList.contains(info)) { resultList.add(info); } } // 其次,调用父类的方法实现 super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); } } } ``` ### 3.3 进行分析 ```java import lsieun.utils.FileUtils; import org.objectweb.asm.Cla***eader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; public class HelloWorldAnalysisCore { public static void main(String[] args) { String relative_path = "sample/HelloWorld.class"; String filepath = FileUtils.getFilePath(relative_path); byte[] bytes = FileUtils.readBytes(filepath); //(1)构建Cla***eader Cla***eader cr = new Cla***eader(bytes); //(2)分析ClassVisitor int api = Opcodes.ASM9; ClassVisitor cv = new MethodFindRefVisitor(api, null, "sample/HelloWorld", "test", "(III)V"); //(3)结合Cla***eader和ClassVisitor int parsingOptions = Cla***eader.SKIP_DEBUG | Cla***eader.SKIP_FRAMES; cr.accept(cv, parsingOptions); } } ``` 输出结果: ```text sample/HelloWorld.add(II)I sample/HelloWorld.sub(II)I ``` ## 4. 总结 本文主要对查找Instruction进行了介绍,内容总结如下: - 第一点,查找Instruction的过程,就是对`MethodVisitor`类的`visitXxxInsn()`方法及参数进行判断的过程。 - 第二点,查找Instruction,并不属于Class Transformation,而属于Class Analysis。Class Analysis,只需要用到`Cla***eader`和`ClassVisitor`类,而不需要用到`ClassWriter`类。 - 第三点,在两个代码示例中,都是围绕着“方法调用”展开,而“方法调用”就对应着`MethodVisitor.visitMethodInsn()`方法。

这篇关于Java ASM系列:(030)查找已有的方法(查找-方法调用)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程