ASM

今天介绍下ASM3.0,开始之前先思考几个问题:

1.ASM是什么?
2.ASM 跟传说中的AOP三剑客APT、aspectJ、Javassit有什么关系?
3.ASM是怎样修改class文件的?

带着问题开始今天的分享:

  • 1.ASM是什么?

ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。说白了asm是直接通过字节码来修改class文件。

  • 2.ASM 跟传说中的AOP三剑客APT、aspectJ、Javassit有什么关系?

分别解释下这几个名词

APT:APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出。简单来说就是在编译期,通过注解生成.java文件

aspectJ:AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,所以它有一个专门的[编译器]用来生成遵守Java字节编码规范的Class文件。适合在某一个方法前后插入部分代码,处理某些逻辑:比如方法运行时间、插入动态权限检查等。问题会造成很多的冗余代码,产生很多代理类。简单来说就是在生成class时动态织入代码

Javassit: Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba(千叶滋)所创建的。简单来说就是源码级别的api去修改字节码

各种方式作用时机
  • 3.ASM是怎样修改class文件的?

开始这个问题之前我们先学习几个东西。

  • 字节码

这里的字节码主要说的是Java字节码,看之前的一篇文章java bytecode

  • 访问者模式

一个称为元素(Element),另一个称为访问者(Visitor)。元素有一个accept方法,该方法接收访问者作为参数;accept()方法调用访问者的visit()方法,并且将元素自身作为参数传递给访问者。由元素本身决定是否访问

在ASM中元素(被访问者)ClassReader、MethodNode等等,访问者接口包含ClassVisitor、AnnotationVisitor、FieldVisitor、MethodVisitor

下面我们先简单实现一个插桩操作
有如下代码:

public class Main2Activity extends AppCompatActivity {

    private static int MESSAGE_KEY = 0x2019;
    @SuppressLint("HandlerLeak")
    private static Handler sHandler =new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what ==MESSAGE_KEY) {
                if (msg.obj!=null) {
                    Log.i("xmq", String.valueOf(msg.obj));
                }
            }
        }
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sendMessage(getClass().getSimpleName());
    }
    
    private void sendMessage(String string) {
        Message message = new Message();
        message.what =MESSAGE_KEY;
        sHandler.sendMessage(message);
    }
}

我们要在sendMessage方法中添加一行代码变为下列

private void sendMessage(String string) {
        Message message = new Message();
        message.what =MESSAGE_KEY;
        message.obj = string;
        sHandler.sendMessage(message);
}
  • 1.我们这里使用一个Android studio的plugin (ASM ByteCode Outline)查看Main2Activity的ASM代码,看主要的sendMessage部分
{
            mv = cw.visitMethod(ACC_PRIVATE, "sendMessage", "(Ljava/lang/String;)V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(33, l0);
            mv.visitTypeInsn(NEW, "android/os/Message");
            mv.visitInsn(DUP);
            mv.visitMethodInsn(INVOKESPECIAL, "android/os/Message", "<init>", "()V", false);
            mv.visitVarInsn(ASTORE, 2);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLineNumber(34, l1);
            mv.visitVarInsn(ALOAD, 2);
            mv.visitFieldInsn(GETSTATIC, "com/lucky/lib/studyapp/Main2Activity", "MESSAGE_KEY", "I");
            mv.visitFieldInsn(PUTFIELD, "android/os/Message", "what", "I");
            Label l2 = new Label();
            mv.visitLabel(l2);
            mv.visitLineNumber(36, l2);
            mv.visitFieldInsn(GETSTATIC, "com/lucky/lib/studyapp/Main2Activity", "sHandler", "Landroid/os/Handler;");
            mv.visitVarInsn(ALOAD, 2);
            mv.visitMethodInsn(INVOKEVIRTUAL, "android/os/Handler", "sendMessage", "(Landroid/os/Message;)Z", false);
            mv.visitInsn(POP);
            Label l3 = new Label();
            mv.visitLabel(l3);
            mv.visitLineNumber(37, l3);
            mv.visitInsn(RETURN);
            Label l4 = new Label();
            mv.visitLabel(l4);
            mv.visitLocalVariable("this", "Lcom/lucky/lib/studyapp/Main2Activity;", null, l0, l4, 0);
            mv.visitLocalVariable("string", "Ljava/lang/String;", null, l0, l4, 1);
            mv.visitLocalVariable("message", "Landroid/os/Message;", null, l1, l4, 2);
            mv.visitMaxs(2, 3);
            mv.visitEnd();
        }

看起来很懵逼,其实这里只不过是ASM帮助我们调用java bytecode罢了。

    1. 修改你想要的代码,同样使用ASM ByteCode Outlineplugin对比差异代码
***
mv.visitFieldInsn(GETSTATIC, "com/lucky/lib/studyapp/Main2Activity", "MESSAGE_KEY", "I");
mv.visitFieldInsn(PUTFIELD, "android/os/Message", "what", "I");
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLineNumber(35, l2);
mv.visitVarInsn(ALOAD, 2);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, "android/os/Message", "obj", "Ljava/lang/Object;");
Label l3 = new Label();
mv.visitLabel(l3);
mv.visitLineNumber(36, l3);
***

看的出我们在mv.visitFieldInsn(PUTFIELD, "android/os/Message", "what", "I");后边插入了三行代码(其中的Label以及行数设置可以不用理),那么这三行代码什么意思呢?这里就用到了上边提到的java bytecode知识,意思是:将变量2,1分别入栈,并将2变量赋值给message的obj。

好了那么我们开始写ASM,这里我们使用android transform api作为前置条件扫描文件。(有关于transform api后期分享)

开始之前先解释几个类:

1.Opcodes接口定义了一些常量,尤其是版本号,访问标示符,字节码等信息;
2.ClassReader用于读取Class文件,主要用于Class文件的分析,可接受一个ClassVisitor;ClassReader会将解析过程中产生的类的部分信息,比如访问标识符,字段,方法逐个送入ClassVisitor,后者在接收到对应的信息后,进行各自的处理;
3.ClassVisitor的子类ClassWriter: 负责进行Class文件的输出和生成。ClassVisitor在进行字段和方法处理的时候,会委托给FieldVistor和MethodVisitor进行处理;在类的处理过程中,会创建对应的FieldVisitor和MethodVisitor对象;FieldVisitor和MethodVisitor类也各自有1个重要的子类,FieldWriter和MethodWriter;当ClassWriter进行字段和方法的处理时,也是依赖这两个类进行的;
4.ClassVisitor,FieldVisitor,MethodVisitor都可以使用委托的方式,将实际的处理工作交给内部的委托类进行;它们内部有一些列的visitXXX方法,这些方法就是ASM 的实际方法code。

创建元素跟访问者

private static byte [] scanClass(InputStream inputStream) {
        //被访问者(元素)
        ClassReader cr = new ClassReader(inputStream)
        //访问者
        ClassWriter cw = new ClassWriter(cr, 0)
        ScanClassVisitor cv = new ScanClassVisitor(Opcodes.ASM5,cw) //ClassWriter 的代理类
        cr.accept(cv, ClassReader.EXPAND_FRAMES)
        return cw.toByteArray()
    }

这里的访问者ScanClassVisitor继承了ClassVisitor,这里我们要修改某一个方法,所以实现了visitMethod方法,并筛选其中的sendMessage方法

    static class ScanClassVisitor extends ClassVisitor{
        ScanClassVisitor(int api, ClassVisitor cv) { //这里很奇怪我无法使用继承Opcodes,内部直接调用ASM5,只能传参数
            super(api, cv)
        }
        @Override
        MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            MethodVisitor mv =  cv.visitMethod(access, name, desc, signature, exceptions)
            if (name == "sendMessage") {
                mv = new ScanMethodVisitor(Opcodes.ASM5,mv)
            }
            return mv
        }
    }

上边的ScanMethodVisitor是实现了MethodVisitor访问者,看ASM代码,我们需要在mv.visitFieldInsn(PUTFIELD, "android/os/Message", "what", "I");行后插入代码,所以需要实现visitFieldInsn方法

opcode: PUTFIELD
owner: "android/os/Message"
name:"what"
desc:"I"

static class ScanMethodVisitor extends MethodVisitor {
        ScanMethodVisitor(int api, MethodVisitor mv) {
            super(api, mv)
        }
        @Override
        void visitFieldInsn(int opcode, String owner, String name, String desc) {
            if (opcode == Opcodes.PUTFIELD && owner.equals("android/os/Message")) {
                mv.visitFieldInsn(opcode, owner, name, desc)
                mv.visitVarInsn(Opcodes.ALOAD, 2)
                mv.visitVarInsn(Opcodes.ALOAD, 1)
                mv.visitFieldInsn(Opcodes.PUTFIELD, "android/os/Message", "obj", "Ljava/lang/Object;")
            } else {
                mv.visitFieldInsn(opcode, owner, name, desc)
            }
        }
    }

好了接下来运行代码transform看效果

public class Main2Activity extends AppCompatActivity {
    private static int MESSAGE_KEY = 8217;
    @SuppressLint({"HandlerLeak"})
    private static Handler sHandler = new Handler() {
        public void handleMessage(Message msg) {
            if (msg.what == Main2Activity.MESSAGE_KEY && msg.obj != null) {
                Log.i("xmq", String.valueOf(msg.obj));
            }

        }
    };

    public Main2Activity() {
    }

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(2131296284);
        this.sendMessage(this.getClass().getSimpleName());
    }

    private void sendMessage(String string) {
        Message message = new Message();
        message.what = MESSAGE_KEY;
        message.obj = string;
        sHandler.sendMessage(message);
    }
}

总结:ASM直接修改class文件确实效率很高,但因直接操作字节码,需要有字节码知识,不适合直接上手,相比较来Javassit源码级修改class文件更方便些。
demo

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

推荐阅读更多精彩内容

  • 前言 很早之前就写过面向切面的编程思想,主要学习了AOP的思想(参考:AOP简介)以及使用 AspectJ 实现简...
    Whyn阅读 10,770评论 4 40
  • 引言 什么是 ASM ? ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。AS...
    Chauncey_Chen阅读 1,476评论 0 6
  • 最近进行组内分享时选择了这个Java字节码处理这个主题,特此记录下来。众所周知,Java是一门运行在虚拟机上的语言...
    sheepm阅读 16,364评论 1 71
  • 1. 概述 AOP(面向切面编程)的概念现在已经应用的非常广泛了,下面是从百度百科上摘抄的一段解释,比较浅显易懂 ...
    lijiankun24阅读 17,894评论 4 33
  • ASM是一款基于java字节码层面的代码分析和修改工具。无需提供源代码即可对应用嵌入所需debug代码,用于应用A...
    LedBoot阅读 6,917评论 4 23