美文网首页
42 - ASM之Class Transformation总结

42 - ASM之Class Transformation总结

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

    Class Transformation,从Core API的角度来说(第二个层次),我们介绍了asm.jar当中的ClassReader和Type两个类;同时,从应用的角度来说(第一个层次),我们也介绍了Class Transformation的原理和示例。

    asm学习层次

    Class Transformation的原理

    在Class Transformation的过程中,我们主要使用到了ClassReader、ClassVisitor和ClassWriter三个类;其中ClassReader类负责“读”Class,ClassWriter负责“写”Class,而ClassVisitor则负责进行“转换”(Transformation)。

    ClassVisitor传递示意图

    在Java ASM当中,Class Transformation的本质就是利用了“中间人公(攻)鸡(击)”的方式来实现对已有的Class文件进行修改或转换。

    中间人攻击

    详细的来说,我们自己定义的ClassVisitor类就是一个“中间人”,那么这个“中间人”可以做什么呢?可以做三种类型的事情:

    • 对“原有的信息”进行篡改,就可以实现“修改”的效果。对应到ASM代码层面,就是对ClassVisitor.visitXxx()和MethodVisitor.visitXxx()的参数值进行修改。
    • 对“原有的信息”进行扔掉,就可以实现“删除”的效果。对应到ASM代码层面,将原本的FieldVisitor和MethodVisitor对象实例替换成null值,或者对原本的一些ClassVisitor.visitXxx()和MethodVisitor.visitXxx()方法不去调用了。
    • 伪造一条“新的信息”,就可以实现“添加”的效果。对应到ASM代码层面,就是在原来的基础上,添加一些对于ClassVisitor.visitXxx()和MethodVisitor.visitXxx()方法的调用。

    ASM能够做哪些转换操作

    在类层面所做的修改,主要是通过ClassVisitor类来完成。我们将类层面可以修改的信息,分成以下三个方面:

    • 类自身信息:修改当前类、父类、接口的信息,通过ClassVisitor.visit()方法实现。
    • 字段:添加一个新的字段、删除已有的字段,通过ClassVisitor.visitField()方法实现。
    • 方法:添加一个新的方法、删除已有的方法,通过ClassVisitor.visitMethod()方法实现。
    public class HelloWorld extends Object implements Cloneable {
        public int intValue;
        public String strValue;
    
        public int add(int a, int b) {
            return a + b;
        }
    
        public int sub(int a, int b) {
            return a - b;
        }
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    

    为了让大家更明确的知道需要修改哪一个visitXxx()方法的参数,我们做了如下总结:

    • ClassVisitor.visit(int version, int access, String name, String signature, String superName, String[] interfaces)
    • version: 修改当前Class版本的信息
    • access: 修改当前类的访问标识(access flag)信息。
    • name: 修改当前类的名字。
    • signature: 修改当前类的泛型信息。
    • superName: 修改父类。
    • interfaces: 修改接口信息。

    ClassVisitor.visitField(int access, String name, String descriptor, String - signature, Object value)

    • access: 修改当前字段的访问标识(access flag)信息。
    • name: 修改当前字段的名字。
    • descriptor: 修改当前字段的描述符。
    • signature: 修改当前字段的泛型信息。
    • value: 修改当前字段的常量值。

    ClassVisitor.visitMethod(int access, String name, String descriptor, String signature, String[] exceptions)

    • access: 修改当前方法的访问标识(access flag)信息。
    • name: 修改当前方法的名字。
    • descriptor: 修改当前方法的描述符。
    • signature: 修改当前方法的泛型信息。
    • exceptions: 修改当前方法可以招出的异常信息。

    再有,如何删除一个字段或者方法呢?其实很简单,我们只要让中间的某一个ClassVisitor在遇到该字段或方法时,不向后传递就可以了。在具体的代码实现上,我们只要让visitField()或visitMethod()方法返回一个null值就可以了。


    最后,如何添加一个字段或方法呢?我们只要让中间的某一个ClassVisitor向后多传递一个字段和方法就可以了。在具体的代码实现上,我们是在visitEnd()方法完成对字段或方法的添加,而不是在visitField()或visitMethod()当中添加。因为我们要避免“一个类里有重复的字段和方法出现”,在visitField()或visitMethod()方法中,我们要判断该字段或方法是否已经存在;如果该字段或方法不存在,那我们就在visitEnd()方法进行添加;如果该字段或方法存在,那么我们就不需要在visitEnd()方法中添加了。

    方法体层面的修改

    在方法体层面所做的修改,主要是通过MethodVisitor类来完成。

    在方法体层面的修改,更准确的地说,就是对方法体内包含的Instruction进行修改。就像数据库的操作“增删改查”一样,我们也可以对Instruction进行添加、删除、修改和查找。

    为了让大家更直观的理解,我们假设有如下代码:

    public class HelloWorld {
        public int test(String name, int age) {
            int hashCode = name.hashCode();
            hashCode = hashCode + age * 31;
            return hashCode;
        }
    }
    

    其中,test()方法的方法体包含的Instruction内容如下:

    public test(Ljava/lang/String;I)I
        ALOAD 1
        INVOKEVIRTUAL java/lang/String.hashCode ()I
        ISTORE 3
        ILOAD 3
        ILOAD 2
        BIPUSH 31
        IMUL
        IADD
        ISTORE 3
        ILOAD 3
        IRETURN
        MAXSTACK = 3
        MAXLOCALS = 4
    

    有的时候,我们想实现某个功能,但是感觉无从下手。这个时候,我们需要解决两个问题。第一个问题,就是要明确需要修改什么?第二个问题,就是“定位”方法,也就是要使用哪个方法进行修改。我们可以结合这两个问题,和下面的示例应用来理解。

    • 添加
      • 在“方法进入”时和“方法退出”时,
        • 打印方法参数和返回值
        • 方法计时
    • 删除
      • 移除NOP
      • 移除打印语句、加零、字段赋值
      • 清空方法体
    • 修改
      • 替换方法调用(静态方法和非静态方法)
    • 查找
      • 当前方法调用了哪些方法
      • 当前方法被哪些方法所调用

    由于MethodVisitor类里定义了很多的visitXxxInsn()方法,我们就不详细介绍了。但是,大家可以的看一下 asm4-guide.pdf的一段描述:

    Methods can be transformed, i.e. by using a method adapter that forwards the method calls it receives with some modifications:

    • changing arguments can be used to change individual instructions,
    • not forwarding a received call removes an instruction,
    • and inserting calls between the received ones adds new instructions.

    需要要注意一点:无论是添加instruction,还是删除instruction,还是要替换instruction,都要保持operand stack修改前和修改后是一致的。

    小结

    本文内容总结如下:

    • 第一点,希望大家可以理解Class Transformation的原理。
    • 第二点,在Class Transformation中,ASM究竟能够帮助我们修改哪些信息。

    相关文章

      网友评论

          本文标题:42 - ASM之Class Transformation总结

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