Java ASM系列:(032)第三章内容总结

本文属于[Java ASM系列一:Core API](https://blog.51cto.com/lsieun/2924583)当中的一篇。 在本章当中,从Core API的角度来说(第二个层次),我们介绍了`asm.jar`当中的`Cla***eader`和`Type`两个类;同时,从应用的角度来说(第一个层次),我们也介绍了Class Transformation的原理和示例。 ![ASM的学习层次](http://www.icode9.com/i/li/?n=2&i=images/20210619/1624105643863231.png?,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) ## 1. Class Transformation的原理 在Class Transformation的过程中,我们主要使用到了`Cla***eader`、`ClassVisitor`和`ClassWriter`三个类;其中`Cla***eader`类负责“读”Class,`ClassWriter`负责“写”Class,而`ClassVisitor`则负责进行“转换”(Transformation)。 ![多个ClassVisitor串联到一起](http://www.icode9.com/i/li/?n=2&i=images/20210628/1624882119265917.png?,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) 在Java ASM当中,Class Transformation的本质就是利用了“中间人公(攻)鸡(击)”的方式来实现对已有的Class文件进行修改或转换。 ![Man-in-the-middle attack](http://www.icode9.com/i/li/?n=2&i=images/20210628/1624882180784922.png?,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) 详细的来说,我们自己定义的`ClassVisitor`类就是一个“中间人”,那么这个“中间人”可以做什么呢?可以做三种类型的事情: - 对“原有的信息”进行篡改,就可以实现“修改”的效果。对应到ASM代码层面,就是对`ClassVisitor.visitXxx()`和`MethodVisitor.visitXxx()`的参数值进行修改。 - 对“原有的信息”进行扔掉,就可以实现“删除”的效果。对应到ASM代码层面,将原本的`FieldVisitor`和`MethodVisitor`对象实例替换成`null`值,或者对原本的一些`ClassVisitor.visitXxx()`和`MethodVisitor.visitXxx()`方法不去调用了。 - 伪造一条“新的信息”,就可以实现“添加”的效果。对应到ASM代码层面,就是在原来的基础上,添加一些对于`ClassVisitor.visitXxx()`和`MethodVisitor.visitXxx()`方法的调用。 ## 2. ASM能够做哪些转换操作 ### 2.1 类层面的修改 在类层面所做的修改,主要是通过`ClassVisitor`类来完成。我们将类层面可以修改的信息,分成以下三个方面: - 类自身信息:修改当前类、父类、接口的信息,通过`ClassVisitor.visit()`方法实现。 - 字段:添加一个新的字段、删除已有的字段,通过`ClassVisitor.visitField()`方法实现。 - 方法:添加一个新的方法、删除已有的方法,通过`ClassVisitor.visitMethod()`方法实现。 ```java 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`值就可以了。 ![多个FieldVisitor和MethodVisitor串联到一起](http://www.icode9.com/i/li/?n=2&i=images/20210628/1624882148334418.png?,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) 最后,**如何添加一个字段或方法呢?**我们只要让中间的某一个`ClassVisitor`向后多传递一个字段和方法就可以了。在具体的代码实现上,我们是在`visitEnd()`方法完成对字段或方法的添加,而不是在`visitField()`或`visitMethod()`当中添加。因为我们要避免“一个类里有重复的字段和方法出现”,在`visitField()`或`visitMethod()`方法中,我们要判断该字段或方法是否已经存在;如果该字段或方法不存在,那我们就在`visitEnd()`方法进行添加;如果该字段或方法存在,那么我们就不需要在`visitEnd()`方法中添加了。 ### 2.2 方法体层面的修改 在方法体层面所做的修改,主要是通过`MethodVisitor`类来完成。 在方法体层面的修改,更准确的地说,就是对方法体内包含的Instruction进行修改。就像数据库的操作“增删改查”一样,我们也可以对Instruction进行添加、删除、修改和查找。 为了让大家更直观的理解,我们假设有如下代码: ```java public class HelloWorld { public int test(String name, int age) { int hashCode = name.hashCode(); hashCode = hashCode + age * 31; return hashCode; } } ``` 其中,`test()`方法的方法体包含的Instruction内容如下: ```text 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](https://asm.ow2.io/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修改前和修改后是一致的**。 ## 3. 总结 本文内容总结如下: - 第一点,希望大家可以理解Class Transformation的原理。 - 第二点,在Class Transformation中,ASM究竟能够帮助我们修改哪些信息。
上一篇:音乐研究(C语言)


下一篇:032.核心组件-kube-proxy