40 - ASM之查找、方法调用

2022-02-20  本文已影响0人  舍是境界

查找Instruction

如何查找Instruction

在方法当中,查找某一个特定的Instruction,那么应该怎么做呢?简单来说,就是通过MethodVisitor类当中定义的visitXxxInsn()方法来查找。

让我们回顾一下MethodVisitor类当中定义了哪些visitXxx()方法。

MethodVisitor

在MethodVisitor类当中,定义的主要visitXxx()方法可以分成四组:

在方法当中,任何一条Instruction,放在ASM代码中,它都是通过调用MethodVisitor.visitXxxInsn()方法的形式来呈现的。换句话说,想去找某一条特定的Instruction,分成两个步骤:

简而言之,查找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进行了介绍,内容总结如下:

上一篇下一篇

猜你喜欢

热点阅读