40 - ASM之查找、方法调用
查找Instruction
如何查找Instruction
在方法当中,查找某一个特定的Instruction,那么应该怎么做呢?简单来说,就是通过MethodVisitor类当中定义的visitXxxInsn()方法来查找。
让我们回顾一下MethodVisitor类当中定义了哪些visitXxx()方法。
MethodVisitor在MethodVisitor类当中,定义的主要visitXxx()方法可以分成四组:
- 第一组,visitCode()方法,标志着方法体(method body)的开始。
- 第二组,visitXxxInsn()方法,对应方法体(method body)本身,这里包含多个方法。
- 第三组,visitMaxs()方法,标志着方法体(method body)的结束。
- 第四组,visitEnd()方法,是最后调用的方法。
在方法当中,任何一条Instruction,放在ASM代码中,它都是通过调用MethodVisitor.visitXxxInsn()方法的形式来呈现的。换句话说,想去找某一条特定的Instruction,分成两个步骤:
- 第一步,找到该Instruction对应的visitXxxInsn()方法。
- 第二步,对该visitXxxInsn()方法接收的opcode和其它参数进行判断。
简而言之,查找Instruction的过程,就是对visitXxxInsn()方法接收的参数进行检查的过程。 举一个形象的例子,平时我们坐地铁,随身物品要过安检,其实就是对书包(方法)里的物品(参数)进行检查。
Class Analysis
查找Instruction的过程, 并不属于Class Transformation(因为没有生成新的类),而是属于Class Analysis。 在下图当中,Class Analysis包括find potential bugs、detect unused code和reverse engineer code等操作。但是,这些分析操作(analysis)是比较困难的,它需要编程经验的积累和对问题模式的识别,需要编码处理各种不同情况,所以不太容易实现。
ASM can do但是,Class Analysis,并不是只包含复杂的分析操作,也包含一些简单的分析操作。例如,当前方法里调用了哪些其它的方法、当前的方法被哪些别的方法所调用。对于方法的调用,就对应着MethodVisitor.visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface)方法。
另外,要注意一点:在Class Transformation当中,需要用到ClassReader、ClassVisitor和ClassWriter类;但是,在Class Analysis中,我们只需要用到ClassReader和ClassVisitor类,而不需要用到ClassWriter类。
class transform示例一:调用了哪些方法
预期目标
假如有一个HelloWorld类,代码如下:
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内容:
$ 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
}
编码实现
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<String> 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();
}
}
}
进行分析
import lsieun.utils.FileUtils;
import org.objectweb.asm.ClassReader;
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)构建ClassReader
ClassReader cr = new ClassReader(bytes);
//(2)分析ClassVisitor
int api = Opcodes.ASM9;
ClassVisitor cv = new MethodFindInvokeVisitor(api, null, "test", "(II)V");
//(3)结合ClassReader和ClassVisitor
int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
cr.accept(cv, parsingOptions);
}
}
输出结果:
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
示例二:被哪些方法所调用
在IDEA当中,有一个Find Usages功能:在类名、字段名、或方法名上,右键之后,选择Find Usages,就可以查看该项内容在哪些地方被使用了。
被调用显示这样一个功能,如果我们自己来实现,那该怎么编写ASM代码呢?
预期目标
假如有一个HelloWorld类,代码如下:
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()方法进行了调用。
编码实现
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<String> 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);
}
}
}
进行分析
import lsieun.utils.FileUtils;
import org.objectweb.asm.ClassReader;
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)构建ClassReader
ClassReader cr = new ClassReader(bytes);
//(2)分析ClassVisitor
int api = Opcodes.ASM9;
ClassVisitor cv = new MethodFindRefVisitor(api, null, "sample/HelloWorld", "test", "(III)V");
//(3)结合ClassReader和ClassVisitor
int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
cr.accept(cv, parsingOptions);
}
}
输出结果
sample/HelloWorld.add(II)I
sample/HelloWorld.sub(II)I
小结
本文主要对查找Instruction进行了介绍,内容总结如下:
- 第一点,查找Instruction的过程,就是对MethodVisitor类的visitXxxInsn()方法及参数进行判断的过程。
- 第二点,查找Instruction,并不属于Class Transformation,而属于Class Analysis。Class Analysis,只需要用到ClassReader和ClassVisitor类,而不需要用到ClassWriter类。
- 第三点,在两个代码示例中,都是围绕着“方法调用”展开,而“方法调用”就对应着MethodVisitor.visitMethodInsn()方法。