一文搞懂JVM架构:Android程序员的硬通货

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`中对应的 `visitMethod`、 `visitAnnotation`、 `visitField`这些 `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`进入一个方法时候回调,因此在这个方法中插入指令就是在整个方法最开始加入一些代码。我们需要在这个方法中插入 `longs=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);
}
}


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

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

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

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

![](https://upload-images.jianshu.io/upload_images/15590149-e9855d4aeb3e14b1?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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

![](https://upload-images.jianshu.io/upload_images/15590149-595b471115c44473?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

点击**ASM Bytecode Viewer**之后会弹出

![](https://upload-images.jianshu.io/upload_images/15590149-d23024fb89b7a9f7?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

所以第20行代码: `longs=System.currentTimeMillis();`会包含两个指令: `INVOKESTATIC`与 `LSTORE`。

再回到 `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中的实现**

# **感受:**

其实我投简历的时候,都不太敢投递阿里。因为在阿里一面前已经过了字节的三次面试,投阿里的简历一直没被捞,所以以为简历就挂了。

特别感谢一面的面试官捞了我,给了我机会,同时也认可我的努力和态度。对比我的面经和其他大佬的面经,自己真的是运气好。别人8成实力,我可能8成运气。所以对我而言,我要继续加倍努力,弥补自己技术上的不足,以及与科班大佬们基础上的差距。希望自己能继续保持学习的热情,继续努力走下去。

**也祝愿各位同学,都能找到自己心动的offer。**

分享我在这次面试前所做的准备(刷题复习资料以及一些大佬们的学习笔记和学习路线),都已经整理成了电子文档,**[需要的朋友可以【点赞+关注】戳这里即可免费获取](https://gitee.com/vip204888/java-p7)**

续保持学习的热情,继续努力走下去。

**也祝愿各位同学,都能找到自己心动的offer。**

分享我在这次面试前所做的准备(刷题复习资料以及一些大佬们的学习笔记和学习路线),都已经整理成了电子文档,**[需要的朋友可以【点赞+关注】戳这里即可免费获取](https://gitee.com/vip204888/java-p7)**

![拿到字节跳动offer后,简历被阿里捞了起来,二面迎来了P9"盘问"](https://img-blog.csdnimg.cn/img_convert/d3472d0025d7359fd4c9bb63943e7ba0.png)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值