美文网首页
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总结

    Class Transformation,从Core API的角度来说(第二个层次),我们介绍了asm.jar当中...

  • 32 - ASM之Class Transformation的原理

    Class-Reader/Visitor/Writer 我们使用ClassReader、ClassVisitor和...

  • Android ASM快速入门

    ASM介绍 ASM是一个字节码操作库,它可以直接修改已经存在的class文件或者生成class文件。ASM提供了一...

  • ASM简介(六)

    TreeAPI Class ASM中修改生成class主要依赖ClassNode类 生成class时我们只需构造对...

  • ASM笔记

    1. 准备工作 idea安装插件:ASM ByteCode Outline,用于将class生成ASM相关代码,供...

  • capt 正式开源

    capt 全称 Class Annotation Processor Tool,是作者基于 ASM 和 Andro...

  • 29 - ASM创建类总结

    使用ASM创建新的类是围绕着Class Generation来展开,在前面的文章中,我们介绍了ASM Core A...

  • JVM 字节码操作 ASM 框架简单讲解

    什么是 ASM ? ASM 是一款直接操作字节码(即 class 文件)的框架,可以都已生成好的字节码进行改动或者...

  • kylo问题总结1

    kylo问题总结1 Getting unexpected error in Data Transformation...

  • 奇门遁甲之ASM操纵字节码

    本文记录对ASM 字节码操控框架的梳理和总结,方便需要时查看。 一、什么是ASM ASM 是一个 Java 字节码...

网友评论

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

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