ASM字节码插桩,看完不会的来打我

本文介绍了使用ASM框架在Java中进行方法插桩的过程,包括ClassReader的使用、MethodVisitor的重写以及如何在Android中检测并处理Class文件。同时提到了性能优化的重要性和学习资源。
摘要由CSDN通过智能技术生成

(“xxx/test/java2/InjectTest.class”);
fos.write(newClassBytes);

fos.close();

关于ASM框架本身的设计,我们这里先不讨论。上面的代码会获取上一步生成的class,然后由ASM执行完插桩之后,将结果输出到test/java2目录下。其中关键点就在于第2步中,如何进行插桩。

把class数据交给ClassReader,然后进行分析,类似于XML解析,分析结果会以事件驱动的形式告知给accept的第一个参数ClassAdapterVisitor

public class ClassAdapterVisitor extends ClassVisitor {

public ClassAdapterVisitor(ClassVisitor cv) {
super(Opcodes.ASM7, cv);
}

@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions) {
System.out.println(“方法:” + name + " 签名:" + desc);

MethodVisitor mv = super.visitMethod(access, name, desc, signature,
exceptions);
return new MethodAdapterVisitor(api,mv, access, name, desc);
}
}

分析结果通过ClassAdapterVisitor获得,一个类中会存在方法、注解、属性等,因此ClassReader会将调用ClassAdapterVisitor中对应的visitMethodvisitAnnotationvisitField这些visitXX方法。

我们的目的是进行函数插桩,因此重写visitMethod方法,在这个方法中我们返回一个MethodVisitor方法分析器对象。一个方法的参数、注解以及方法体需要在MethodVisitor中进行分析与处理。

package com.enjoy.asminject.example;

import com.enjoy.asminject.ASMTest;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;
import org.objectweb.asm.commons.Method;

/**

  • AdviceAdapter: 子类
  • 对methodVisitor进行了扩展, 能让我们更加轻松的进行方法分析
    */
    public class MethodAdapterVisitor extends AdviceAdapter {

private boolean inject;

protected MethodAdapterVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
super(api, methodVisitor, access, name, descriptor);
}

/**

  • 分析方法上面的注解
  • 在这里干嘛???
  • 判断当前这个方法是不是使用了injecttime,如果使用了,我们就需要对这个方法插桩
  • 没使用,就不管了。
  • @param desc
  • @param visible
  • @return
    */
    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
    if (Type.getDescriptor(ASMTest.class).equals(desc)) {
    System.out.println(desc);
    inject = true;
    }
    return super.visitAnnotation(desc, visible);
    }

private int start;

@Override
protected void onMethodEnter() {
super.onMethodEnter();
if (inject) {
//执行完了怎么办? 记录到本地变量中
invokeStatic(Type.getType(“Ljava/lang/System;”),
new Method(“currentTimeMillis”, “()J”));

start = newLocal(Type.LONG_TYPE); //创建本地 LONG类型变量
//记录 方法执行结果给创建的本地变量
storeLocal(start);
}
}

@Override
protected void onMethodExit(int opcode) {
super.onMethodExit(opcode);
if (inject){
invokeStatic(Type.getType(“Ljava/lang/System;”),
new Method(“currentTimeMillis”, “()J”));
int end = newLocal(Type.LONG_TYPE);
storeLocal(end);

getStatic(Type.getType(“Ljava/lang/System;”),“out”,Type.getType(“Ljava/io” +
“/PrintStream;”));

//分配内存 并dup压入栈顶让下面的INVOKESPECIAL 知道执行谁的构造方法创建StringBuilder
newInstance(Type.getType(“Ljava/lang/StringBuilder;”));
dup();
invokeConstructor(Type.getType(“Ljava/lang/StringBuilder;”),new Method(“”,“()V”));

visitLdcInsn(“execute:”);
invokeVirtual(Type.getType(“Ljava/lang/StringBuilder;”),new Method(“append”,“(Ljava/lang/String;)Ljava/lang/StringBuilder;”));

//减法
loadLocal(end);
loadLocal(start);
math(SUB,Type.LONG_TYPE);

invokeVirtual(Type.getType(“Ljava/lang/StringBuilder;”),new Method(“append”,“(J)Ljava/lang/StringBuilder;”));
invokeVirtual(Type.getType(“Ljava/lang/StringBuilder;”),new Method(“toString”,“()Ljava/lang/String;”));
invokeVirtual(Type.getType(“Ljava/io/PrintStream;”),new Method(“println”,“(Ljava/lang/String;)V”));

}
}
}

MethodAdapterVisitor继承自AdviceAdapter,其实就是MethodVisitor 的子类,AdviceAdapter封装了指令插入方法,更为直观与简单。

上述代码中onMethodEnter进入一个方法时候回调,因此在这个方法中插入指令就是在整个方法最开始加入一些代码。我们需要在这个方法中插入long s = System.currentTimeMillis();。在onMethodExit中即方法最后插入输出代码。

@Override
protected void onMethodEnter() {
super.onMethodEnter();
if (inject) {
//执行完了怎么办? 记录到本地变量中
invokeStatic(Type.getType(“Ljava/lang/System;”),
new Method(“currentTimeMillis”, “()J”));

start = newLocal(Type.LONG_TYPE); //创建本地 LONG类型变量
//记录 方法执行结果给创建的本地变量
storeLocal(start);
}
}

这里面的代码怎么写?其实就是long s = System.currentTimeMillis();这句代码的相对的指令。我们可以先写一份代码

void test(){
//插入的代码
long s = System.currentTimeMillis();
/**

  • 方法实现代码…
    */
    //插入的代码
    long e = System.currentTimeMillis();
    System.out.println(“execute:”+(e-s)+" ms.");
    }

然后使用javac编译成Class再使用javap -c查看字节码指令。也可以借助插件来查看,就不需要我们手动执行各种命令。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

安装完成之后,可以在需要插桩的类源码中点击右键:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

点击ASM Bytecode Viewer之后会弹出

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

所以第20行代码:long s = System.currentTimeMillis();会包含两个指令:INVOKESTATICLSTORE

再回到onMethodEnter方法中

@Override
protected void onMethodEnter() {
super.onMethodEnter();
if (inject) {
//invokeStatic指令,调用静态方法
invokeStatic(Type.getType(“Ljava/lang/System;”),
new Method(“currentTimeMillis”, “()J”));
//创建本地 LONG类型变量
start = newLocal(Type.LONG_TYPE);
//store指令 将方法执行结果从操作数栈存储到局部变量
storeLocal(start);
}
}

onMethodExit也同样根据指令去编写代码即可。最终执行完插桩之后,我们就可以获得修改后的class数据。

四、Android中的实现

在Android中实现,我们需要考虑的第一个问题是如何获得所有的Class文件来判断是否需要插桩。Transform就是干这件事情的。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

  • 思维脑图
  • 性能优化学习笔记


  • 性能优化视频

    当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

mg-cOLgGbd9-1712452764555)]

  • 性能优化视频
    [外链图片转存中…(img-3Y2mIKl4-1712452764556)]
    当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值