42 - ASM之Class Transformation总结

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究竟能够帮助我们修改哪些信息。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容