美文网首页
40 - ASM之查找、方法调用

40 - ASM之查找、方法调用

作者: 舍是境界 | 来源:发表于2022-02-20 08:02 被阅读0次

    查找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()方法。

    相关文章

      网友评论

          本文标题:40 - ASM之查找、方法调用

          本文链接:https://www.haomeiwen.com/subject/rfqulrtx.html