Android ASM使用
ASM
ASM是一种基于java字节码层面的代码分析和修改工具,ASM的目标是生成,转换和分析已编译的java class文件,可使用ASM工具读/写/转换JVM指令集。通俗点讲就是来处理javac编译之后的class文件
Java字节码
Java字节码是Java虚拟机执行的一种指令格式。通俗来讲字节码就是经过javac命令编译之后生成的Class文件。Class文件包含了Java虚拟机指令集和符号表以及若干其他的辅助信息。Class是一组以8位字节为基础单位的二进制文件。各个数据项目严格按照顺序紧凑的排列在Class文件之中。中间没有任何分隔符,这使得整个Class文件中存储的内容几乎全是程序运行时的必要数据。
class文件有固定的结构,保留了几乎所有的源代码文件中的符号。class文件的结构:
20140730105614312.png- 描述类的modifier,name,父类,接口和注释
- 描述类中变量的modfier,名字,类型和注释
- 描述类中方法和构造函数的modifier,名字参数类型,返回类型,注释等信息,当然也包含已编译成java字节码指令序列的方法具体内容
- class文件的静态池区域,用来保存所有的数字,字符串,类型的常量,这些常量只被定义过一次且被其他class中区域所引用
一个Java文件编译之后可能对应多个class文件。
字节码描述符
不同类型的Java类型使用不同的描述符
-
类
Class文件中使用全限定名来表示一个类的引用,即把类名所有“.”换成了“/”,例如:
android.content.Context在class中文android/content/Context -
数据类型
数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值以及代表无返回值的void类型都用一个大写字符来表示
Java类型 | class描述符 |
---|---|
byte | B |
char | C |
double | D |
float | F |
int | I |
long | J |
short | S |
boolean | Z |
void | V |
对象类型 | L |
数组 | [ |
如:
String[] -> [Ljava/lang/String;
int[][] -> [[I;
- 方法
方式使用(), 按照参数列表,返回值的顺序表示。 例如:
void init() -> ()V
void test(object) -> (Ljava/lang/object;)V
String[] getArray(String s) -> (Ljava/lang/String;)[Ljava/lang/String;
ASM修改Class流程
ASM处理Class文件使用了生产-消费者模式,基本有三个部分组成:
- Reader, 如ClassReader, 读取class
- Adapter, 转换器,对class文件就行修改
- writer, 将修改后的class写入到文件
时序图:
5251070-4ba0865b79cc47f7.pngASM中提供一个ClassReader类,调用accept方法,接受一个实现了抽象类ClassVisitor的对象实例作为参数,然后依次调用ClassVisitor的各个方法。字节码空间上的偏移被转成各种visitXXX方法。使用者只需要在对应的的方法上进行需求操作即可,无需考虑字节偏移
这个过程中ClassReader可以看作是一个事件生产者,ClassWriter继承自ClassVisitor抽象类,负责将对象化的class文件内容重构成一个二进制格式的class字节码文件,ClassWriter可以看作是一个事件的消费者。
例如ClassVisitor类的结构:
public abstract class ClassVisitor {
public ClassVisitor(int api);
public ClassVisitor(int api, ClassVisitor cv);
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces);
public void visitSource(String source, String debug);
public void visitOuterClass(String owner, String name, String desc);
AnnotationVisitor visitAnnotation(String desc, boolean visible);
public void visitAttribute(Attribute attr);
public void visitInnerClass(String name, String outerName, String innerName, int access);
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value);
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions);
void visitEnd();
}
访问一个class文件的顺序为:
visitvisitSource -> visitOuterClass -> isitAnnotation | visitAttribute -> visitInnerClass | visitField | visitMethod -> visitEnd
使用:
ClassPrinter cp = new ClassPrinter();
ClassReader cr = new ClassReader("java.lang.Runnable");
cr.accept(cp, 0);
输出:
java/lang/Runnable extends java/lang/Object {
run()V
}
各个 ClassVisitor通过职责链 (Chain-of-responsibility) 模式,可以非常简单的封装对字节码的各种修改,而无须关注字节码的字节偏移,因为这些实现细节对于用户都被隐藏了,用户要做的只是覆写相应的 visit 函数。
ClassAdaptor类实现了 ClassVisitor接口所定义的所有函数,当新建一个 ClassAdaptor对象的时候,需要传入一个实现了ClassVisitor接口的对象,作为职责链中的下一个访问者 (Visitor),这些函数的默认实现就是简单的把调用委派给这个对象,然后依次传递下去形成职责链。当用户需要对字节码进行调整时,只需从 ClassAdaptor类派生出一个子类,覆写需要修改的方法,完成相应功能后再把调用传递下去。这样,用户无需考虑字节偏移,就可以很方便的控制字节码。
每个 ClassAdaptor类的派生类可以仅封装单一功能,比如删除某函数、修改字段可见性等等,然后再加入到职责链中,这样耦合更小,重用的概率也更大,但代价是产生很多小对象,而且职责链的层次太长的话也会加大系统调用的开销,用户需要在低耦合和高效率之间作出权衡。用户可以通过控制职责链中 visit 事件的过程。
ASM 的最终的目的是生成可以被正常装载的 class 文件,因此其框架结构为客户提供了一个生成字节码的工具类: ClassWriter。它实现了 ClassVisitor接口,而且含有一个 toByteArray()函数,返回生成的字节码的字节流,将字节流写回文件即可生产调整后的 class 文件。一般它都作为职责链的终点,把所有 visit 事件的先后调用(时间上的先后),最终转换成字节码的位置的调整(空间上的前后)
使用举例
- 删除类的字段、方法、指令
删除类的字段、方法、指令:只需在职责链传递过程中中断委派,不访问相应的 visit 方法即可,比如删除方法时只需直接返回 null,而不是返回由 visitMethod方法返回的 MethodVisitor对象
例如删除类里面的test方法
class RemoveTestClassAdapter extends ClassAdapter {
public DelLoginClassAdapter(ClassVisitor cv) {
super(cv);
}
public MethodVisitor visitMethod(final int access, final String name,
final String desc, final String signature, final String[] exceptions) {
if (name.equals("test")) {
return null;
}
return cv.visitMethod(access, name, desc, signature, exceptions);
}
}
- 修改类、字段、方法的名字或修饰符
只需要在对应的Adapter 中替换调用参数
class ModifyClassAdapter extends ClassAdapter {
public AccessClassAdapter(ClassVisitor cv) {
super(cv);
}
public FieldVisitor visitField(final int access, final String name,
final String desc, final String signature, final Object value) {
int privateAccess = Opcodes.ACC_PRIVATE;
return cv.visitField(privateAccess, name, desc, signature, value);
}
}
- 在方法执行时插入代码
class addCodeClassAdapter extends ClassAdapter {
//Responsechain 的下一个 ClassVisitor,这里cv是ClassWriter,
public AddSecurityCheckClassAdapter(ClassVisitor cv) {
// 负责改写后代码的输出
super(cv);
}
public MethodVisitor visitMethod(final int access, final String name,
final String desc, final String signature, final String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions);
MethodVisitor wrappedMv = mv;
if (mv != null) {
if (name.equals("test")) {
wrappedMv = new TestMethodAdapter(mv);
}
}
return wrappedMv;
}
}
class TestMethodAdapter extends MethodAdapter {
public AddSecurityCheckMethodAdapter(MethodVisitor mv) {
super(mv);
}
public void visitCode() {
visitMethodInsn(Opcodes.INVOKESTATIC, "com/test/TestHelper”,
"test”, "()V");
}
}
visitMethodInsn就是访问一个方法的指令,Opcodes.INVOKESTATIC:调用静态方法,后面参数分别是类名,方法名和方法参数
- 添加继承
class ModifyClassAdapter extends ClassAdapter {
public AccessClassAdapter(ClassVisitor cv) {
super(cv);
}
public void visit(final int version, final int access, final String name,
final String signature, final String superName,
final String[] interfaces) {
String enhancedName = "Test$Enhanced"; // 改变类命名
enhancedSuperName = “Test”; // 改变父类
super.visit(version, access, enhancedName, signature,
enhancedSuperName, interfaces);
}
}
修改构造函数
public MethodVisitor visitMethod(final int access, final String name,
final String desc, final String signature, final String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
MethodVisitor wrappedMv = mv;
if (mv != null) {
if (name.equals("<init>")) {
wrappedMv = new ChangeChildConstructorMethodAdapter(mv,
enhancedSuperName);
}
}
return wrappedMv;
}
class ChangeChildConstructorMethodAdapter extends MethodAdapter {
private String superClassName;
public ChangeToChildConstructorMethodAdapter(MethodVisitor mv,
String superClassName) {
super(mv);
this.superClassName = superClassName;
}
public void visitMethodInsn(int opcode, String owner, String name,
String desc) {
// 调用父类的构造函数时
if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) {
owner = superClassName;
}
super.visitMethodInsn(opcode, owner, name, desc);// 改写父类为 superClassName
}
}
在java中使用修改的类:
首先当然要实现一个ClassLoader
class TestClassLoader extends ClassLoader {
public Class defineClassFromClassFile(String className,
byte[] classFile) throws ClassFormatError {
return defineClass("Test$Enhanced", classFile, 0,
classFile.length());
}
}
然后使用ClassLoader加载该类
private static Class testClass;
public Test testEnhancedt() throws ClassFormatError,
InstantiationException, IllegalAccessException {
if (null == testClass) {
ClassReader cr = new ClassReader("Test");
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw);
cr.accept(classAdapter, ClassReader.SKIP_DEBUG);
byte[] data = cw.toByteArray();
secureAccountClass = classLoader.defineClassFromClassFile(
"Test$Enhanced",data);
}
return (Test) testClass.newInstance();
}
Android中使用ASM
android 一般在gradle 插件中使用ASM
class TestPlugin implements Plugin<Project>{
@Override
void apply(Project project) {
//在App module使用Transform
if (project.plugins.hasPlugin(AppPlugin)) {
TestTransform transform = new TestTransform(project)
android.registerTransform(transform)
}
}
}
Android中添加了Transform,Transform允许 第三方插件在class文件转为为dex文件前操作编译好的class文件,这个API的目标是简化自定义类操作,而不必处理Task。
Transform的结构
class TestTransform extends Transform{
@Override
String getName() {
return "Test"
}
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
@Override
boolean isIncremental() {
return false
}
@Override
void transform(TransformInvocation transformInvocation) throws TransformException,
InterruptedException, IOException {
}
}
InputType是指输入的类型,即要扫面的文件类型,支持CLASS和RESOURCES
Scopes是作用域,SCOPE_FULL_PROJECT即整个project
isIncremental是否是增量任务
transform 就是执行修改class的关键方法
TransformInvocation,看名字就知道,就是变化的调用。里面包含以下关键信息:
Inputs:需要处理的输入,有两种类型JarInput和DirectoryInput, JarInput就是要处理jar中的class,
DirectoryInput就是在文件夹下的class
ReferencedInputs: 仅仅是引用的输入
OutputProvider: 输出提供器,即将输入转换为输出的过程
修改的class替换
class是以文件的形式存在的,ASM本质只是修改了class文件的内容,要使修改生效,还需要一个文件替换的过程。要替换文件要先找到要修改的class文件对象或路径。 获取方法:
File destFile = transformInvocation.outputProvider.getContentLocation(
destName, jarInput.contentTypes, jarInput.scopes, Format.JAR)
Format.JAR是在JAR中的位置,若要获取文件夹下class的位置传入Fommat.DIRECTORY
jar 处理
jar其实也是ZIP,使用java JarFile处理
def jar = new JarFile(src)
Enumeration enumeration = jar.entries()
while (enumeration.hasMoreElements()) {
JarEntry jarEntry = (JarEntry) enumeration.nextElement()
String entryName = jarEntry.getName()
/**
* entryName就是jar下的class名称,安装需求处理即可
*/
…
jar.close()
}
DirectoryInput就简单了,遍历文件夹的class文件即可
修改Class文件
ClassReader cr = new ClassReader(new FileInputStream(srcFile))
TestClassVisitor cv = new TestClassVisitor()
cr.accept(cv, 0)