字节码插桩技术---ASM的使用(一)

字节码插桩技术---Android项目实操(二)

字节码插桩技术---Transform配合ASM进行插桩(三)

上篇文章字节码插桩技术---Android项目实操(二)_紫气东来_life的博客-CSDN博客介绍了在build.gradle中如何使用ASM进行字节码处理。在build.gradle中进行插桩的话,会存在几个问题:

(1)对groovy语法要有一些了解(2)不同的gradle版本,所做的处理会有不同,比如打包dex的指令,对class文件的处理方式上等。这些问题上篇文章都有说过。今天将介绍一种新的方式:使用Transform的相关API配合ASM进行插桩。

一、创建插件Module

如图的项目文件结构

第一步:我们在和app同级的目录中创建一个文件夹buildSrc,不用new module,直接创建文件夹即可,文件夹名称为buildSrc

第二步:根据app的文件目录结构,依次创建src,main,java等目录,创建完之后同步一下,然后就可以看见文件夹颜色的变化

第三步:在java路径下,创建包路径,我这里是com.gzc.plugin,包路径的名称随意

第四步:在buildSrc中创建build.gradle文件,然后再dependencies中依赖gradle plugin,内容如下:

apply plugin: 'java'repositories {google()mavenCentral()
}dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])implementation 'com.android.tools.build:gradle:3.6.3'}sourceCompatibility = "7"
targetCompatibility = "7"tasks.withType(JavaCompile) {options.encoding = "UTF-8"
}

二、使用Transform进行编译的拦截

首先,我们创建一个plugin文件,内容如下:

package com.gzc.plugin;import com.android.build.gradle.AppExtension;
import org.gradle.api.Plugin;
import org.gradle.api.Project;public class ASMPlugin implements Plugin<Project> {@Overridepublic void apply(Project project) {project.getExtensions().getByType(AppExtension.class).registerTransform(new ASMTransform());}
}

这个文件创建完成之后,我们就可以在其他项目的build.gradle中进行引入了,引入的方式如下:

apply plugin:com.gzc.plugin.ASMPlugin

在apply方法中,调用了registerTransform方法,注册了ASMTransform文件。这个ASMTransform其实就是一个Task,也就是说,注册成功之后,在build过程中的输出,会有我们这个自定义Task名称的输出。具体的输出内容,我们根据文件内容来说:

public class ASMTransform extends Transform {@Overridepublic String getName() {return "asm";}@Overridepublic Set<QualifiedContent.ContentType> getInputTypes() {return TransformManager.CONTENT_CLASS;}@Overridepublic Set<? super QualifiedContent.Scope> getScopes() {return ImmutableSet.of(QualifiedContent.Scope.PROJECT, QualifiedContent.Scope.SUB_PROJECTS);}@Overridepublic boolean isIncremental() {return false;}@Overridepublic void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {super.transform(transformInvocation);TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();outputProvider.deleteAll();Collection<TransformInput> inputs = transformInvocation.getInputs();for (TransformInput input : inputs) {Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();for (DirectoryInput directoryInput : directoryInputs) {String dirName = directoryInput.getName();File src = directoryInput.getFile();String md5Name = DigestUtils.md5Hex(src.getAbsolutePath());File dest = outputProvider.getContentLocation(dirName + md5Name, directoryInput.getContentTypes(),directoryInput.getScopes(), Format.DIRECTORY);processInject(src, dest);}//jar包的处理Collection<JarInput> jarInputs = input.getJarInputs();for (JarInput jarInput : jarInputs) {processJarInput(jarInput,transformInvocation.getOutputProvider());}}}void processJarInput(JarInput jarInput, TransformOutputProvider outputProvider) throws IOException {File dest = outputProvider.getContentLocation(jarInput.getFile().getAbsolutePath(),jarInput.getContentTypes(),jarInput.getScopes(),Format.JAR);FileUtils.copyFile(jarInput.getFile(), dest);}void processInject(File src, File dest) throws IOException {String dir = src.getAbsolutePath();FluentIterable<File> allFiles = FileUtils.getAllFiles(src);for (File file : allFiles) {if (!file.getAbsolutePath().endsWith(".class")) {continue;}FileInputStream fis = new FileInputStream(file);ClassReader classReader = new ClassReader(fis);ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);classReader.accept(new ClassVisitor(Opcodes.ASM7, classWriter) {@Overridepublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);return new MyMethodVisitor(Opcodes.ASM7, methodVisitor, access, name, descriptor);}}, ClassReader.EXPAND_FRAMES);byte[] newClassBytes = classWriter.toByteArray();String absolutePath = file.getAbsolutePath();String fullClassPath = absolutePath.replace(dir, "");File outFile = new File(dest, fullClassPath);FileUtils.mkdirs(outFile.getParentFile());FileOutputStream fos = new FileOutputStream(outFile);fos.write(newClassBytes);fos.close();}}
}

1.getName方法

返回值写的是“asm”,那么在build的输出中就会有如下Task的输出:

transformClassesWithAsmForDebug就是我们自定义Task的输出,我们可以看见,这个Task是在dexBuilderDebug之前执行的,dexBuilderDebug上篇文章我们说了,是class文件打包为dex的Task。也就是说,我们在自定义Task中可以拿到相关的class。

2.getInputTypes方法

这个方法的含义就是要处理文件的类型,除了上面的TransformManager.CONTENT_CLASS外,一共有三种:

TransformManager.CONTENT_CLASS:拦截class以及jar文件。自己项目中的源文件都会被编译为class文件,而三方库中的源文件会以jar的形式输出到这里

TransformManager.CONTENT_JARS:除了上面的class以及jar文件外,还有会拦截resources中的相关资源文件,这种资源文件也是以jar包的形式存在的

TransformManager.CONTENT_RESOURCES:之拦截resources中的相关资源文件

3.getScopes方法

这个方法的意思就是文件的作用域,可以使用系统提供的,比如

TransformManager.SCOPE_FULL_PROJECT:表示作用域为当前的module,依赖的子module以及三方库

TransformManager.PROJECT_ONLY:作用域为当前的module。

当然也可以自己组合,我使用的就是组合的方式,作用域为当前module以及依赖的子module

4.isInsremental方法

表示是否增量,如果设置为true,那么只会拦截有增量或减量变化的文件,我这里设置的是false,所以不会考虑增量变化,所有文件都会输出

5.transform方法

核心方法,要处理的文件会在这里被拦截,以及处理后传递给下一个Task。其中transformInvocation.getInputs方法拿到的是所有符合条件文件的目录;所有处理完之后的文件,都会重新填充到transformInvocation.getOutputProvider方法的返回值中。

6.processInject方法

这个方法是我们自定义的方法,其中的逻辑不难,就是遍历每一个class文件,然后使用ASM进行字节码操作;之后将修改后的class文件填充到指定目录dest中。而这个dest的路径为:app/build/intermediates/transforms,自定义Task生成的文件都会存在这里,这个路径中的文件会被打包进dex

7.processJarInput方法

这个方法也是我们自定义的方法,主要是处理Jar文件。这个方法需要我们注意一下:我们从代码逻辑上看,仅仅是改变了Jar的路径,对其中的class文件并没处理。而这里的Jar包其实是R.jar,里面包含了资源索引。开始的时候,我也没有去处理,因为我想的是应该和上篇文章一样,只处理想要处理的文件就行了。其实不是,我们不想要处理的文件,也是需要重新填充到transformInvocation.getOutputProvider这里的,作为下一个任务的输入。

8.MyMethodVisitor类

这个类上篇文章有,可以从上篇文章copy

三、Transform的其他用法

1.使用引号的方式依赖插件

我们上面所说的依赖方式和我们看见的不同,我们经常看见是使用引号的方式,比如:

apply plugin: 'com.android.application'

当然,我们也可以通过注册的方式,将我们的插件文件注册在配置文件中,从而使用引号的方式进行引用,如图:

第一步:创建如图中的目录结构,以及后缀为.properties的文件

第二步:在文件中写如图的配置

之后,我们就可以通过双引号的方式进行引用了,双引号中的名称就是.properties的文件名,这个名称是随意命名的。

2.插件拓展的使用

图片中的代码大家应该都很熟悉,这就是插件的拓展。比如:android,compileSdkVersion等,我们自定义类似这些的字段,如下:

对上面我们所说的ASMPlugin进行改造,如下:

public class ASMPlugin implements Plugin<Project> {@Overridepublic void apply(Project project) {//注释1project.getExtensions().create("gzc", GzcExt.class);project.afterEvaluate(new Action<Project>() {@Overridepublic void execute(Project project) {GzcExt ext = project.getExtensions().findByType(GzcExt.class);System.out.println("test:"+ext.isTest());}});}
}

注释1这里的“gzc”,就是类似android的拓展字段;GzcExt是一个类,其中的字段就是拓展中使用的字段:

public class GzcExt {private boolean test;public boolean isTest() {return test;}public void setTest(boolean test) {this.test = test;}
}

创建完成之后,我们就可以在其他的build.gradle中写如下的插件拓展了:

gzc{test true
}

四、注意

个人建议使用的gradle版本在7.0以下,因为gradle7版本的结构变化比较大,配置的时候总会有一些问题,我没有成功解决,如果有成功的小伙伴记得留言,请教请教~

字节码插桩技术---Transform配合ASM进行插桩(三)相关推荐

  1. Java 动态代理与class字节码动态修改技术

    代理分两种技术,一种是jdk代理(机制就是反射,只对接口操作),一种就是字节码操作技术.前者不能算技术,后者算是新的技术.未来将有大的动作或者较为广泛的应用和变革,它可以实现代码自我的编码(人工智能, ...

  2. java字节码提取if语句_java – 使用ASM选择和修改`if`语句

    问题1可能是最困难的.您需要通过识别某些模式来找出插入指令的位置.如果您假设firstPar.check()只被调用一次,那么您可以查找if(firstPar.check())的以下字节码指令: AL ...

  3. 【字节码插桩】AOP 技术 ( “字节码插桩“ 技术简介 | AspectJ 插桩工具 | ASM 插桩工具 )

    文章目录 一." 字节码插桩 " 技术简介 二.AspectJ 插桩工具 三.ASM 插桩工具 一." 字节码插桩 " 技术简介 性能优化 , 插件化 , 热修 ...

  4. 调研字节码插桩技术,用于系统监控设计和实现

    作者:小傅哥 博客:https://bugstack.cn ❝ 沉淀.分享.成长,让自己和他人都能有所收获!???? ❞ 目录 一.来自深夜的电话! 二.准备工作 三.使用 AOP 做个切面监控 1. ...

  5. Java ASM框架与字节码插桩的常见用法(生成类,修改类,方法插桩,方法注入)

    前言 ASM 是一款读写Java字节码的工具,可以达到跳过源码编写,编译,直接以字节码的形式创建类,修改已经存在类(或者jar中的class)的属性,方法等. 通常用来开发一些Java开发的辅助框架, ...

  6. Java字节码技术(二)字节码增强之ASM、JavaAssist、Agent、Instrumentation

    文章目录 前言 从AOP说起 静态代理 动态代理 JavaProxy CGLIB 字节码增强实现AOP ASM JavaAssist 运行时类加载 Instrumentation接口 JavaAgen ...

  7. ASM字节码编程 | 用字节码增强技术给所有方法加上TryCatch捕获异常并输出

    作者:小傅哥 博客:https://bugstack.cn Wiki:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有所收 ...

  8. 一起来学字节码插桩:从分析class文件结构开始

    文章目录 Class字节码 class字节码构成 类型描述符 基本类型描述符 非数组的引用类型 数组引用类型 方法描述符 OpCode操作码指令 类操作码 字段操作码 方法操作码 加载.存储等操作 计 ...

  9. 【JVM】字节码与ASM字节码增强、Instrument实现类的动态重加载

    目录 字节码与ASM字节码增强 什么是字节码? 字节码结构 操作数栈与字节码 字节码增强 ASM 运行时类加载 Instrument JPDA与JVMTI instrument实现热加载的过程 字节码 ...

  10. aspectj 获取方法入参_深入探索编译插桩技术(二、AspectJ)

    本文来自jsonchao的投稿,个人微信:bcce5360 现如今,编译插桩技术已经深入 Android 开发中的各个领域,而 AOP 技术正是一种高效实现插桩的模式,它的出现正好给处于黑暗中的我们带 ...

最新文章

  1. 【强化学习篇】--强化学习从初识到应用
  2. 1085 Perfect Sequence
  3. jquery取值,赋值,以及下拉框获取选中value值
  4. HTML 5核心内容
  5. Window右键添加“用vim打开”
  6. 前端学习(488):文本标签
  7. python同时输出多个值_怎样在python中输出多个数组元素?
  8. OC 与 C++ 混编导致 ’string‘ not found
  9. ASP.NET MVC 5– 使用Wijmo MVC 5模板1分钟创建应用
  10. ubuntu 16.04 和win10双系统ubuntu无法更新问题解决
  11. 苹果Mac高级音乐播放器:Swinsian
  12. 正态分布推导瑞利分布,瑞利信道的模型
  13. 2019年SpringBoot视频教程【全网免费】每周更新
  14. 2017阿里巴巴实习生C/C++研发内推一面、二面经历
  15. 相比REG007 不仅免费还好用 的手机号绑定查询工具
  16. 蓝桥杯最终冲刺(冲刺Day2)
  17. trackerslist GitHub12月无重复更新版
  18. JS:关于事件触发机制
  19. Linux下禁用root远程登录并且新建一个用户赋予root权限
  20. COBIT+2019框架简介和方法(资料下载)

热门文章

  1. Linux下怎么刷显卡bios,nvidia显卡如何刷bios?nvidia显卡刷bios教程
  2. Mysql如何清空数据库的单表数据 , 所有表数据
  3. linux图片转成pdf文件大小,Linux下实现图片转pdf以及pdf转图片的命令_沃航科技
  4. 《网络运维 - 基础知识》
  5. 盘古开源:中央网信办发布“十四五”国家信息化规划,数字化春风吹遍全国
  6. 数字化转型— 华为业务流程模型学习
  7. 从0开始实现一个直播礼物系统
  8. tp 数据库查询排序_ThinkPHP对查询的数据随机排序
  9. 《遥感原理与应用》总结—遥感平台
  10. 逆clarke变换_克拉克(CLARKE)及帕克(PARK)变换.pdf