DevSupport框架原理

Android ASM使用

2019-07-26  本文已影响0人  SimpleFunc

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

一个Java文件编译之后可能对应多个class文件。

字节码描述符

不同类型的Java类型使用不同的描述符

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文件使用了生产-消费者模式,基本有三个部分组成:

时序图:

5251070-4ba0865b79cc47f7.png

ASM中提供一个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)
上一篇下一篇

猜你喜欢

热点阅读