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

相关推荐
coderlin_7 小时前
BI布局拖拽 (1) 深入react-gird-layout源码
android·javascript·react.js
2501_915918417 小时前
Fiddler中文版全面评测:功能亮点、使用场景与中文网资源整合指南
android·ios·小程序·https·uni-app·iphone·webview
wen's9 小时前
React Native安卓刘海屏适配终极方案:仅需修改 AndroidManifest.xml!
android·xml·react native
编程乐学10 小时前
网络资源模板--基于Android Studio 实现的聊天App
android·android studio·大作业·移动端开发·安卓移动开发·聊天app
EndingCoder10 小时前
搜索算法在前端的实践
前端·算法·性能优化·状态模式·搜索算法
没有了遇见12 小时前
Android 通过 SO 库安全存储敏感数据,解决接口劫持问题
android
hsx66612 小时前
使用一个 RecyclerView 构建复杂多类型布局
android
hsx66612 小时前
利用 onMeasure、onLayout、onDraw 创建自定义 View
android
守城小轩12 小时前
Chromium 136 编译指南 - Android 篇:开发工具安装(三)
android·数据库·redis
whysqwhw12 小时前
OkHttp平台抽象机制分析
android