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'`

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

指定了实现类MatrixPlugin

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去配置扩展属性,代码如:

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步:

第一步:解析
/**
 * 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已经被混淆过了,混淆后插桩避免插桩导致某些编译器优化失效。

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。

第二步:收集
/**
 * step 2	[Collection]
 */
val methodCollector = MethodCollector(executor, mappingCollector, methodId, config,collectedMethodMap)
methodCollector.collect(dirInputOutMap.keys, jarInputOutMap.keys)




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

//不需要把这个类的整个结构读取进来,就可以用流的方法来处理字节码文件
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

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

@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

第三步:插桩
/**
 * 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进行真正的插桩了

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

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
/**
 * 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
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


APM框架Matrix源码分析——字节码插桩之扩展网络监控

本篇文章主要介绍如何基于ASM + OkHttp3 + 自定义拦截器来实现网络请求的监控

1.插桩位置

我们需要我们自定义监控网络Interceptor插入到合适的位置。先来分析下拦截器的处理流程:

//RealCall.java
private Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  //自定义拦截器
  interceptors.addAll(client.interceptors());
  interceptors.add(retryAndFollowUpInterceptor);
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  interceptors.add(new CacheInterceptor(client.internalCache()));
  interceptors.add(new ConnectInterceptor(client));
  if (!retryAndFollowUpInterceptor.isForWebSocket()) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(
      retryAndFollowUpInterceptor.isForWebSocket()));
  //链条对象Chain
  Interceptor.Chain chain = new RealInterceptorChain(
      interceptors, null, null, null, 0, originalRequest);
  //执行下一个拦截器
  return chain.proceed(originalRequest);
}

interceptor.png

通过五大拦截器完成整个请求过程,责任链模式—> Request请求,经过5个拦截器,发送到最下边返回响应Response,再发送到最上边。这样自定义的拦截器既可以拿到请求数据,又可以拿到响应数据。

我们需要把自定义的拦截器插到client.interceptors()

public class OkHttpClient implements Cloneable, Call.Factory {
  //OkHttpClient中的成员变量interceptors,用来存放自定义拦截器
  final List<Interceptor> interceptors;
  //这个就是上述client.interceptors(),是OkHttpClient中的成员变量interceptors
  public List<Interceptor> interceptors() {
    return interceptors;
  }
  //OkHttpClient构造函数
  public OkHttpClient() {
    this(new Builder());
  }
	
  private OkHttpClient(Builder builder) {
    //传入了Builder的成员变量interceptors
    this.interceptors = Util.immutableList(builder.interceptors);
    //...
  }
  
  public static final class Builder {
    //Builder的成员变量interceptors
    final List<Interceptor> interceptors = new ArrayList<>();
    
    public Builder() {
      //...
      //插桩点:往interceptors中添加自定义拦截器
    }
    
    Builder(OkHttpClient okHttpClient) {
      //...
      //插桩点:往interceptors中添加自定义拦截器
    }
    
  }
}

client.interceptors()方法返回了OkHttpClient中的成员变量interceptors(用来存放自定义拦截器),OkHttpClient构造的时候被传入了Builder的成员变量interceptors,所有我们只需要在Builder的两个构造函数的最后一行把自定义拦截器添加到Builder的成员变量interceptors即可。

2.自定义监控网络Interceptor
public class NetWorkInterceptor implements Interceptor {
  
  @Override
  public Response intercept(Chain chain) throws IOException {
      //记录开始时间
      long startNs = System.currentTimeMillis();
      //从链表缓冲池中取OkHttpDataReplay(存每个网络请求的信息),没有则new一个
      replay = OkHttpDataReplay.create()
      //记录开始时间
      replay.startNs = startNs;
      Request request = chain.request();
      //记录请求数据,如url、上行数据大小等
      recordRequest(request);
    	
      Response response;
      try {
        response = chain.proceed(request);
      } catch (IOException e) {
        throw e;
      }
      //记录耗时
      replay.costTime = System.currentTimeMillis() - startNs;
      //记录响应数据,如响应码,下行数据大小等
      recordResponse(response);
      //收集数据分析
      doReplayCopy(replay);
      //处理完成回收,用于复用
      replay.recycle();
  }
}

通过自定义拦截器的方式,记录请求数据和响应数据。

为了减少字节码操作,定义一个工具类协助AMS进行代码插入,传入Builder的成员变量interceptors,判断自定义监控网络Interceptor是否已经添加过,没有则添加:

public class OkHttpUtils {
    public static void insertToOkHttpClientBuilder(List<Interceptor> interceptors) {
        try {
            //判断是否插入过自定义监控网络Interceptor
            boolean hasAddNetWorkInterceptor = false;
            for (Interceptor interceptor : interceptors) {
                if (interceptor instanceof NetWorkInterceptor) {
                    hasAddNetWorkInterceptor = true;
                    break;
                }
            }
            //没有则插入,避免重复
            if (!hasAddNetWorkInterceptor) {
                interceptors.add(new NetWorkInterceptor());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
3.插桩

先要判断当前访问的类是不是OkHttpClient的内部类Builder,接着使用Okhttp3MethodAdapter去处理插桩

@Override
public MethodVisitor visitMethod(int access, String name, String desc,
                                 String signature, String[] exceptions) {
    MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
    //判断当前访问的类是不是OkHttpClient的内部类Builder
    if ("okhttp3/OkHttpClient\$Builder".equals(className)) {
      //返回一个Okhttp3MethodAdapter去处理插桩
      return new Okhttp3MethodAdapter(name, api, access, desc, methodVisitor);
    }
    return super.visitMethod(access, name, desc, signature, exceptions);
}

Okhttp3MethodAdapter

class Okhttp3MethodAdapter(private val methodName: String, api: Int, access: Int, private val desc: String, mv: MethodVisitor?) : LocalVariablesSorter(api, access, desc, mv) {

    override fun visitInsn(opcode: Int) {
        //在okhttp3.OkHttpClient$Builder构造函数的最后一行插入insertToOkHttpClientBuilder
        if (isReturn(opcode) && isOkhttpClientBuild(methodName, desc)) {
            //加载this
            mv.visitVarInsn(ALOAD, 0)
            //访问Builder的interceptors
            mv.visitFieldInsn(GETFIELD, "okhttp3/OkHttpClient\$Builder", "interceptors", "Ljava/util/List;")
            //调用OkHttpUtils的静态方法insertToOkHttpClientBuilder
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/tencent/matrix/trace/okhttp3/OkHttpUtils", "insertToOkHttpClientBuilder", "(Ljava/util/List;)V", false)
        }
        super.visitInsn(opcode)
    }

    private fun isReturn(opcode: Int): Boolean {
        return ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW)
    }
  
  private fun isOkhttpClientBuild(methodName: String, methodDesc: String): Boolean {
            return ("<init>" == methodName && ("()V" == methodDesc || "(Lokhttp3/OkHttpClient;)V" == methodDesc))
        }
}

也可以继承AdviceAdapter在方法出口onMethodExit插入

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值