美文网首页
Android字节码插桩demo

Android字节码插桩demo

作者: 瀚海网虫 | 来源:发表于2020-10-25 08:13 被阅读0次

1. 基本概念

1.1 java字节码

Java字节码是Java虚拟机执行的一种虚拟指令格式。
可通过javac 编译java文件得到字节码文件。
javap 分析字节码文件内容。

1.2 插桩

面向切面编程的一种编程实现。
在需要统一处理的切面上,增加额外代码功能。
具体而言就是在编译期(class --> dex 阶段)修改class文件的结构,达到统一处理目的。

2. demo目标

针对被注解修饰的全部方法,执行插桩,方法进入及离开时,记录系统时间,以实现统计方法执行耗时的目的。

3. 实现步骤

3.1 自定义gradle 组件

目前网上可以搜出大把的通过ASM 插件的方式,实现自定义的gradle 插桩组件,也可直接参考别的文章。

根据作用范围不同,可将gradle 插件分为三类:

  1. 直接在module build.gradle 中编写:仅作用在自身的build.gradle 文件中
  2. buildSrc目录:插件部分源码放在 buildSrc/src/main/groovy/ 中,只对本项目中可见。
  3. 独立的java module(new module 时选择 java or kotlin Libray): 可以发布到jcenter或者maven仓库,别的项目可以直接引入。

demo 中并未采用 常见的ASM “org.ow2.asm:asm:7.2” ,“org.ow2.asm:asm-commons:7.2” 之类的插件,因为“java-gradle-plugin”最近的版本 已经集成了相关功能,无需再借助单独的ASM 插件了。

3.2 定义注解类

实际上就是限制插桩的作用范围。

3.3 关于ASM 核心原理

访问者模式
ClassReader:它将字节数组或者 class 文件读入到内存,以树的数据结构表示,树中的一个节点代表着 class 文件中的某个区域。
ClassVisitor(抽象类):调用 ClassReader#accept() 方法,入参为一个 ClassVisitor 对象。ClassReader 遍历树结构的不同节点时会调用 ClassVisitor 对象中不同的 visit() 方法,从而实现对字节码的修改。

如果有不同操作功能的ClassVisitor,则使用责任链模式,逐级传递。

ClassWriter:ClassWriter 是 ClassVisitor 的实现类,它是生成字节码的工具类,一般是责任链中的最后一个节点,其之前的每一个 ClassVisitor 责任为修改原始字节码,而 ClassWriter 的操作则是把每一个节点修改后的字节码输出为字节数组。

ASM 大致的工作流程是:
ClassReader: 读取字节码到内存中,生成用于表示该字节码的树结构,对应于访问者模式中的元素;
组装 ClassVisitor 责任链: 完成对不同的字节码修改工作,对应于访问者模式中的访问者 Visitor
ClassReader#accept() :传入 ClassVisitor 对象,此 ClassVisitor 是责任链的头结点,逐级传递给责任链中每一个 ClassVisitor,实现对加载进内存的字节码的树结构上的每个节点的访问和修改。
ClassWriter:通常在在责任链的末端,输出修改后的字节码

4 核心代码

public class TimeMethodVisitor extends MethodVisitor {

    ...

    @Override
    AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        if (desc.contains("InjectTimestamp")) {
            isInject = true
        }
        return super.visitAnnotation(desc, visible)
    }

    @Override
    void visitCode() {

        if (isInject) {
            mv.visitLdcInsn(className + " -> TAG");
            mv.visitLdcInsn("开始时间:" + System.currentTimeMillis());
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "e", "(Ljava/lang/String;Ljava/lang/String;)I", false);
            mv.visitInsn(Opcodes.POP);
        }
        super.visitCode()
    }

    @Override
    void visitInsn(int opcode) {

        if (isInject && opcode == Opcodes.RETURN) {
            mv.visitLdcInsn(className + " -> TAG");
            mv.visitLdcInsn("结束时间:" + System.currentTimeMillis());
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "e", "(Ljava/lang/String;Ljava/lang/String;)I", false);
            mv.visitInsn(Opcodes.POP);
        }
        super.visitInsn(opcode)
    }
}
class TimePluginTransform extends Transform {
 @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        transformInvocation.inputs.each { TransformInput input ->
            input.directoryInputs.each { DirectoryInput directoryInput ->
                if (directoryInput.file.isDirectory()) {
                    directoryInput.file.eachFileRecurse { File file ->
                        tranformFile(file)
                    }
                } else {
                    tranformFile(file)
                }
                // Transform 拷贝文件到 transforms 目录
                File dest = transformInvocation.outputProvider.getContentLocation(
                        directoryInput.getName(),
                        directoryInput.getContentTypes(),
                        directoryInput.getScopes(),
                        Format.DIRECTORY);
                // 将修改过的字节码copy到dest,实现编译期间干预字节码
                FileUtils.copyDirectory(directoryInput.getFile(), dest);
            }

            input.jarInputs.each { JarInput jarInput ->
                def jarName = jarInput.name
                def dest = transformInvocation.outputProvider.getContentLocation(jarName,
                        jarInput.contentTypes, jarInput.scopes, Format.JAR)

                FileUtils.copyFile(jarInput.getFile(), dest)
            }
        }
    }

5 关于插件扩展

例如系统提供的 apply plugin: 'com.android.application'


image.png

定义方法:
project.getExtensions().create(“扩展名称", JavaBean.class);
demo暂未涉及

6 源码地址

https://github.com/jjbheda/gradlePluginDemo

相关文章

网友评论

      本文标题:Android字节码插桩demo

      本文链接:https://www.haomeiwen.com/subject/mmqsmktx.html