APM框架Matrix源码分析(七)字节码插桩

APM框架Matrix源码分析(七)字节码插桩

插件入口

Android Gradle Plugin入口:src/main/resources/META-INF/gradle-plugins

matrix-gradle-plugin对应目录下有个com.tencent.matrix-plugin.properties,表示插件名称:``com.tencent.matrix-plugin,使用的地方:apply plugin: 'com.tencent.matrix-plugin'`

groovy 复制代码
//文件名:com.tencent.matrix-plugin.properties  (.properties是后缀)
implementation-class=com.tencent.matrix.plugin.MatrixPlugin

指定了实现类MatrixPlugin

kotlin 复制代码
class MatrixPlugin : Plugin<Project> {
    //插件执行的入口
    override fun apply(project: Project) {
	     //添加Extension,用于提供给用户自定义配置选项
        val matrix = project.extensions.create("matrix", MatrixExtension::class.java)
        //matrix闭包下扩展一个trace对象
        val traceExtension = (matrix as ExtensionAware).extensions.create("trace", MatrixTraceExtension::class.java)
        val removeUnusedResourcesExtension = matrix.extensions.create("removeUnusedResources", MatrixRemoveUnusedResExtension::class.java)
				//必须是应用才去执行后面的插桩操作
        if (!project.plugins.hasPlugin("com.android.application")) {
            throw GradleException("Matrix Plugin, Android Application plugin required.")
        }
        //需要在project.afterEvaluate方法中获取扩展配置,因为apply plugin的执行时机早于扩展配置。
        project.afterEvaluate {
            //设置日志等级
            Log.setLogLevel(matrix.logLevel)
        }
        //进入MatrixTasksManager
        MatrixTasksManager().createMatrixTasks(
                project.extensions.getByName("android") as AppExtension,
                project,
                traceExtension,
                removeUnusedResourcesExtension
        )
    }
}

project.extensions.create 方法来获取在 matrix闭包中定义的内容并通过反射将闭包的内容转换成一个 MatrixExtension 对象(注意需要在project.afterEvaluate方法中获取扩展配置,因为apply plugin的执行时机早于扩展配置)。我们可以在app的build.gradle中使用matrix去配置扩展属性,代码如:

groovy 复制代码
apply plugin: 'com.tencent.matrix-plugin'
matrix {
  trace {
        //matrix配置插桩是否开启
        enable = true
        //方法映射
        baseMethodMapFile = "${project.projectDir}/matrixTrace/methodMapping.txt"
        //插桩黑名单
        blackListFile = "${project.projectDir}/matrixTrace/blackMethodList.txt"
    }
}

MatrixTasksManager().createMatrixTasks()会根据gradle版本创建两个不同的Transform:高版本MatrixTraceTransform和低版本MatrixTraceLegacyTransform,两个都会走核心类MatrixTrace的doTransform方法,分为关键的3步:

第一步:解析
kotlin 复制代码
/**
 * step 1 [Parse]
 */
//解析mapping文件和处理黑名单等
futures.add(executor.submit(ParseMappingTask(
  			mappingCollector, collectedMethodMap, methodId, config)))
for (file in classInputs) {
  	if (file.isDirectory) {
        //收集class文件
        futures.add(executor.submit(CollectDirectoryInputTask(params...)))
  	} else {
        //收集jar文件
        futures.add(executor.submit(CollectJarInputTask(params...)))
  	}
}

ParseMappingTask用来解析mapping.txt文件的,因为这个时候class已经被混淆过了,混淆后插桩避免插桩导致某些编译器优化失效。

kotlin 复制代码
val mappingFile = File(config.mappingDir, "mapping.txt")
if (mappingFile.isFile) {
    val mappingReader = MappingReader(mappingFile)
    //解析mapping
    mappingReader.read(mappingCollector)
}
//解析黑名单
val size = config.parseBlockFile(mappingCollector)
//处理已分配methodId的函数列表
getMethodFromBaseMethod(baseMethodMapFile, collectedMethodMap)

调用MappingReader去解析,得到类、方法在混淆前和混淆后的映射关系,保存在MappingCollector中。

创建 CollectDirectoryInputTask和CollectJarInputTask,支持增量处理,分别收集 class 和 jar文件到 map。

第二步:收集
kotlin 复制代码
/**
 * step 2	[Collection]
 */
val methodCollector = MethodCollector(executor, mappingCollector, methodId, config,collectedMethodMap)
methodCollector.collect(dirInputOutMap.keys, jarInputOutMap.keys)
java 复制代码
    public void collect(Set<File> srcFolderList, Set<File> dependencyJarList) throws ExecutionException, InterruptedException {
       	//...省略 class文件直接存classFileList,文件夹则递归取出class存classFileList
        
        for (File classFile : classFileList) {
             //针对每一个class文件执行CollectSrcTask
             futures.add(executor.submit(new CollectSrcTask(classFile)));
        }

        for (File jarFile : dependencyJarList) {
           //针对每一个jar文件执行CollectSrcTask
            futures.add(executor.submit(new CollectJarTask(jarFile)));
        }
					
        //...省略 futures等待任务完成等
        
      
        //不需要插桩的方法名写入ignoreMethodMapFilePath文件中
        saveIgnoreCollectedMethod(mappingCollector);
      
        // 将被插桩的方法名存入methodMapping.txt
        saveCollectedMethod(mappingCollector);

    }

CollectSrcTask和CollectJarTask逻辑相同,一个处理class,一个处理jar,使用了ASM

Java 复制代码
//不需要把这个类的整个结构读取进来,就可以用流的方法来处理字节码文件
is = new FileInputStream(classFile);
//读取已经编译好的.class文件
ClassReader classReader = new ClassReader(is);
//用于重新构建编译后的类,生成新的类的字节码文件
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
//对于字节码文件中不同的区域有不同的Visitor,如访问类的ClassVisitor
ClassVisitor visitor = new TraceClassAdapter(AgpCompat.getAsmApi(), classWriter);
classReader.accept(visitor, 0);

关键操作在TraceClassAdapter

java 复制代码
private class TraceClassAdapter extends ClassVisitor {
	     @Override
        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
            super.visit(version, access, name, signature, superName, interfaces);
            this.className = name;
            //接口或抽象类,记录isABSClass为true
            if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
                this.isABSClass = true;
            }
            //记录类与父类,方便分析继承关系
            collectedClassExtendMap.put(className, superName);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc,
                                         String signature, String[] exceptions) {
          //接口和抽象类不做处理
          if (isABSClass) {
                return super.visitMethod(access, name, desc, signature, exceptions);
            } else {
                //记录类中是否含有onWindowFocusChange方法(后续应用应用和页面启动分析)
                if (!hasWindowFocusMethod) {
                    hasWindowFocusMethod = isWindowFocusChangeMethod(name, desc);
                }
                //进入CollectMethodNode
                return new CollectMethodNode(className, access, name, desc, signature, exceptions);
            }
        }
    }

CollectMethodNode用来记录方法信息,在方法访问结束会调用visitEnd

java 复制代码
@Override
public void visitEnd() {
    super.visitEnd();
    //将方法信息封装成TraceMethod
    TraceMethod traceMethod = TraceMethod.create(0, access, className, name, desc);
    //是否构造方法
    if ("<init>".equals(name)) {
        isConstructor = true;
    }
    //判断是否需要插桩
    boolean isNeedTrace = isNeedTrace(configuration, traceMethod.className, mappingCollector);
    // 过滤一些简单的方法,空方法、get/set方法等
    if ((isEmptyMethod() || isGetSetMethod() || isSingleMethod())
            && isNeedTrace) {
        ignoreCount.incrementAndGet();
        //一些简单方法(即不需要插桩的方法)存collectedIgnoreMethodMap
        collectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod);
        return;
    }

    if (isNeedTrace && !collectedMethodMap.containsKey(traceMethod.getMethodName())) {
        traceMethod.id = methodId.incrementAndGet();
        //不在黑名单中(即需要插桩的方法)存collectedMethodMap
        collectedMethodMap.put(traceMethod.getMethodName(), traceMethod);
        incrementCount.incrementAndGet();
    } else if (!isNeedTrace && !collectedIgnoreMethodMap.containsKey(traceMethod.className)) {
        ignoreCount.incrementAndGet();
        //在黑名单中(即不需要插桩的方法)存collectedIgnoreMethodMap
        collectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod);
    }

}

第二步收集就是将class中需要插桩和不需要插桩分别存入两个map

第三步:插桩
kotlin 复制代码
/**
 * step 3	[Trace]
 */
val methodTracer = MethodTracer(executor, mappingCollector, config, methodCollector.collectedMethodMap, methodCollector.collectedClassExtendMap)
//合并src和jar
val allInputs = ArrayList<File>().also {
  	it.addAll(dirInputOutMap.keys)
  	it.addAll(jarInputOutMap.keys)
}
val traceClassLoader = TraceClassLoader.getClassLoader(project, allInputs)
//插桩
methodTracer.trace(dirInputOutMap, jarInputOutMap, traceClassLoader, skipCheckClass)

接下来进入MethodTracer进行真正的插桩了

java 复制代码
public void trace(Map<File, File> srcFolderList, Map<File, File> dependencyJarList, ClassLoader classLoader, boolean ignoreCheckClass) throws ExecutionException, InterruptedException {
       //...
       //src插桩
       traceMethodFromSrc(srcFolderList, futures, classLoader, ignoreCheckClass);
       //jar插桩
       traceMethodFromJar(dependencyJarList, futures, classLoader, ignoreCheckClass);
       //...
 }
    
    
private void traceMethodFromSrc(Map<File, File> srcMap, List<Future> futures, final ClassLoader classLoader, final boolean skipCheckClass) {
       if (null != srcMap) {
               for (Map.Entry<File, File> entry : srcMap.entrySet()) {
                    futures.add(executor.submit(new Runnable() {
                        @Override
                        public void run() {
                            //子线程执行插桩
                            innerTraceMethodFromSrc(entry.getKey(), entry.getValue(), classLoader, skipCheckClass);
                        }
                    }));
               }
        }
}

traceMethodFromSrc和traceMethodFromJar分别对src和jar插桩,以traceMethodFromSrc为例,会在子线程中执行innerTraceMethodFromSrc

java 复制代码
private void innerTraceMethodFromSrc(File input, File output, ClassLoader classLoader, boolean ignoreCheckClass) {
       //...
       is = new FileInputStream(classFile);
       ClassReader classReader = new ClassReader(is);
       ClassWriter classWriter = new TraceClassWriter(ClassWriter.COMPUTE_FRAMES, classLoader);
       ClassVisitor classVisitor = new TraceClassAdapter(AgpCompat.getAsmApi(), classWriter);
       classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
       is.close();
    
       byte[] data = classWriter.toByteArray();
    
       if (!ignoreCheckClass) {
            try {
                ClassReader cr = new ClassReader(data);
                ClassWriter cw = new ClassWriter(0);
                ClassVisitor check = new CheckClassAdapter(cw);
                cr.accept(check, ClassReader.EXPAND_FRAMES);
            } catch (Throwable e) {
                System.err.println("trace output ERROR : " + e.getMessage() + ", " + classFile);
                traceError = true;
            }
        }
        //...
    }
TraceClassAdapter
java 复制代码
/**
 * ClassVisitor用于访问和修改Java类文件中的字节码
 */
private class TraceClassAdapter extends ClassVisitor {
    /**
     * visit开始访问类文件时被调用的方法
     *
     * @param version 类文件的版本
     * @param access 类的访问修饰符,如ACC_PUBLIC, ACC_FINAL (see {@link Opcodes})
     * @param name 类的全名
     * @param signature 类的签名
     * @param superName 超类的全名
     * @param interfaces 接口的全名数组
     */
    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);
        this.className = name;
        this.superName = superName;
        //是否是Activity或Activity子类
        this.isActivityOrSubClass = isActivityOrSubClass(className, collectedClassExtendMap);
        //是否需要插桩
        this.isNeedTrace = MethodCollector.isNeedTrace(configuration, className, mappingCollector);
        //抽象类或者接口
        if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
            this.isABSClass = true;
        }
    }

    /**
     *  用于访问方法信息(单个方法)
     *
     * @param access 方法的访问修饰符. 
     * @param name 方法的名称.
     * @param desc 方法的描述符,描述了方法的参数和返回类型.
     * @param signature 方法的签名,通常用于泛型方法.
     * @param exceptions 方法可能抛出的异常类型数组.
     * @return MethodVisitor对象,负责处理方法的字节码
     */
    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
                                     String signature, String[] exceptions) {
        //是否有Activity的onWindowFocusChanged方法                             
        if (!hasWindowFocusMethod) {
            hasWindowFocusMethod = MethodCollector.isWindowFocusChangeMethod(name, desc);
        }
        //抽象类或接口不插桩
        if (isABSClass) {
            return super.visitMethod(access, name, desc, signature, exceptions);
        } else {
            //方法插桩
            MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
            return new TraceMethodAdapter(api, methodVisitor, access, name, desc, this.className,
                    hasWindowFocusMethod, isActivityOrSubClass, isNeedTrace);
        }
    }

    /**
     *  类访问结束
     */
    @Override
    public void visitEnd() {
        if (!hasWindowFocusMethod && isActivityOrSubClass && isNeedTrace) {
            //插入onWindowFocusChanged,并在其中加入自己的逻辑 AppMethodBeat.at
            insertWindowFocusChangeMethod(cv, className, superName);
        }
        super.visitEnd();
    }
}

ClassVisitor用于访问和修改Java类文件中的字节码

  1. visit方法开始访问类文件时被调用(做了一些基本判断,是否是Activity,是否需要插桩,判断抽象类和接口)
  2. visitMethod用于访问方法信息(是否是onWindowFocusChanged方法,过滤了抽象类和接口,方法插桩)
  3. visitEnd表示类访问结束(如果是Activity,访问结束了还没有onWindowFocusChanged方法则插入并加上AppMethodBeat.at()方法)。
TraceMethodAdapter
java 复制代码
private class TraceMethodAdapter extends AdviceAdapter {

    @Override
    protected void onMethodEnter() {
        //在方法入口处插入AppMethodBeat.i()
        TraceMethod traceMethod = collectedMethodMap.get(methodName);
        if (traceMethod != null) {
            traceMethodCount.incrementAndGet();
            mv.visitLdcInsn(traceMethod.id);
            mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "i", "(I)V", false);

            if (checkNeedTraceWindowFocusChangeMethod(traceMethod)) {
                //有onWindowFocusChanged方法则直接插入AppMethodBeat.at
                traceWindowFocusChangeMethod(mv, className);
            }
        }
    }


    @Override
    protected void onMethodExit(int opcode) {
        //在方法出口插入AppMethodBeat.o()
        TraceMethod traceMethod = collectedMethodMap.get(methodName);
        if (traceMethod != null) {
            traceMethodCount.incrementAndGet();
            mv.visitLdcInsn(traceMethod.id);
            mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "o", "(I)V", false);
        }
    }

}
小结

插件的实现类为MatrixPlugin,插桩任务在编译期间执行,混淆后插桩。 Transform主要分为三步:

1. 解析 : 根据mapping.txt和配置的blackMethodList.txt解析得到类、方法在混淆前和混淆后的映射关系,保存在MappingCollector中。

2. 收集 : 将方法信息封装成TraceMethod,把需要插桩和不需要插桩(过滤黑名单、空方法、get/set等简单方法)的方法存入两个map

3. 插桩 : 使用ASM在方法入口插入AppMethodBeat.i,方法出口插入AppMethodBeat.o,Activity的onWindowFocusChanged插入AppMethodBeat.at

相关推荐
雨白1 小时前
Android 自定义 View:从绘制基础到实战仪表盘与饼图
android
没有bug.的程序员1 小时前
GC 日志分析与调优:从日志到性能优化的实战指南
性能优化·gc·日志分析·gc调优
jiunian_cn1 小时前
【Linux】线程
android·linux·运维·c语言·c++·后端
Frank_HarmonyOS10 小时前
Android MVVM(Model-View-ViewModel)架构
android·架构
我科绝伦(Huanhuan Zhou)13 小时前
Linux服务器性能优化总结
linux·服务器·性能优化
PawSQL13 小时前
十年磨一剑!Apache Hive 性能优化演进全史(2013 - )
大数据·hive·性能优化
新子y14 小时前
【操作记录】我的 MNN Android LLM 编译学习笔记记录(一)
android·学习·mnn
lincats15 小时前
一步一步学习使用FireMonkey动画(1) 使用动画组件为窗体添加动态效果
android·ide·delphi·livebindings·delphi 12.3·firemonkey
想想吴16 小时前
Android.bp 基础
android·安卓·android.bp
黑夜照亮前行的路1 天前
JavaScript 性能优化实战技术指南
javascript·性能优化