2.17 Matrix源码解析

2 minute read

2.13.1 IO Canary

  • 流程图 流程图
  • 找到Hook函数, 通过PLT Hook方案 (ELF Hook方案), hook掉posix的openreadwriteclose函数

  • 检查场景
    1. 检测主线程IO, 检测条件
      • 操作线程为主线程
      • 连续读写耗时超过一定阈值或单次write\read耗时超过一定阈值
    2. 读写Buffer过小, buffer过小导致read/write过多, 检测条件
      • buffer小于一定阈值
      • read/write的次数超过一定的阈值
    3. 重复读
      • 同一线程读某个文件的次数超过阈值(5次)
  • Closeable Leak监控(文件、Cursor等未及时close引起的泄露)
    • FileInputStream在创建和close调用CloseGuard#openCloseGuard#closeCloseGuard#warnIfOpen方法, 在CloseGuard#warnIfOpen中调用Reporter#report()

      public void warnIfOpen() {
          if (allocationSite == null || !ENABLED) {
          return;
          }
          ...
          REPORTER.report(message, allocationSite);
      }
      
      1. 利用反射将CloseGuard#warnIfOpen中的ENABLED置为true
      2. 利用动态代理, 将REPORTER替换成指定proxy
  // Matrix流程
  1. IOCanaryPlugin#start(), 调用#initDetectorsAndHookers(), 初始化hooker, 包括使用PLT hook, hook掉posix的`open`、`read`、`write`、`close`函数和CloseGuard#REPORTER
  2. IOCanaryJniBridge#install() {
      doHook();
    }
    // 直接找到jni接口
    private static native boolean doHook();
    Java_com_tencent_matrix_iocanary_core_IOCanaryJniBridge_doHook(JNIEnv *env, jclass type) {
        for (int i = 0; i < TARGET_MODULE_COUNT; ++i) {
                // hook掉libopenjdkjvm.so、libjavacore.so、libopenjdk.so
                const char* so_name = TARGET_MODULES[i];
                ...

                loaded_soinfo* soinfo = elfhook_open(so_name);
                ...

                // hook open接口
                elfhook_replace(soinfo, "open", (void*)ProxyOpen, (void**)&original_open);
                elfhook_replace(soinfo, "open64", (void*)ProxyOpen64, (void**)&original_open64);

                bool is_libjavacore = (strstr(so_name, "libjavacore.so") != nullptr);
                if (is_libjavacore) {
                    if (!elfhook_replace(soinfo, "read", (void*)ProxyRead, (void**)&original_read)) {
                        ...
                        if (!elfhook_replace(soinfo, "__read_chk", (void*)ProxyRead, (void**)&original_read)) {
                            ...
                            return false;
                        }
                    }
                    if (!elfhook_replace(soinfo, "write", (void*)ProxyWrite, (void**)&original_write)) {
                        ...
                        if (!elfhook_replace(soinfo, "__write_chk", (void*)ProxyWrite, (void**)&original_write)) {
                            ...
                            return false;
                        }
                    }
                }

                // hook close接口
                elfhook_replace(soinfo, "close", (void*)ProxyClose, (void**)&original_close);
                elfhook_close(soinfo);
            }
            return true;
    }

  3. CloseGuardHooker#hook() {
      ...
      boolean hookRet = tryHook();
      ...
    }

    private boolean tryHook() {
        ...
            // 反射CloseGuard.class
            Class<?> closeGuardCls = Class.forName("dalvik.system.CloseGuard");
            Class<?> closeGuardReporterCls = Class.forName("dalvik.system.CloseGuard$Reporter");
            Method methodGetReporter = closeGuardCls.getDeclaredMethod("getReporter");
            Method methodSetReporter = closeGuardCls.getDeclaredMethod("setReporter", closeGuardReporterCls);
            Method methodSetEnabled = closeGuardCls.getDeclaredMethod("setEnabled", boolean.class);

            // 获取CloseGuard#Repoeter实例
            sOriginalReporter = methodGetReporter.invoke(null);

            methodSetEnabled.invoke(null, true);

            // open matrix close guard also
            MatrixCloseGuard.setEnabled(true);

            ClassLoader classLoader = closeGuardReporterCls.getClassLoader();
            ...

            // 通过反射调用CloseGuard#setReporter, 更改Reporter
            methodSetReporter.invoke(null, Proxy.newProxyInstance(classLoader,
                new Class<?>[]{closeGuardReporterCls},
                new IOCloseLeakDetector(issueListener, sOriginalReporter)));

            return true;
        ...
    }

2.13.2 Trace Canary

  • 编译期
    • 编译期的任务transformClassesWithDexTask, 将全局class文件作为输入, 例如ASM工具, 在指定方法前后增加MethodBeat#i()和方法结束增加MethodBeat#o()
    • 流程图(参考Matrix官网) 流程图(参考Matrix官网)
  • 运行期
    1. 函数执行前后都会调用MethodBeat#i/o(), 再将method id, 及时间offset放到long类型变量中
    2. 通过Choreographer#postFrameCallback注册监听, 在doFrame()帧与帧时间差是否超过阈值, 超过则获取数组index前的所有数据上报, ANR则在doFrame()每一帧到来时重置定时器, 如果5s没cancel, 则dump ANR和buffer数据
  • Matrix源码流程 (还是从编译器和运行期分析)
    • 编译器

      
        MatrixTraceTransform#transform(TransformInvocation transformInvocation) {
            ...
            doTransform(transformInvocation); // hack
            ...
        }
      
        private void doTransform(TransformInvocation transformInvocation) throws ExecutionException, InterruptedException {
          ...
          /**
           * step 1, 收集jar
           */
          ...
          /**
           * step 2, 收集方法
           */
           ...
           /**
           * step 3, 在指定方法插入MethodBeat#i/o()
           */
           MethodTracer methodTracer = new MethodTracer(executor, mappingCollector, config, methodCollector.getCollectedMethodMap(), methodCollector.getCollectedClassExtendMap());
           methodTracer.trace(dirInputOutMap, jarInputOutMap);
        }
      
        public void trace(Map<File, File> srcFolderList, Map<File, File> dependencyJarList) throws ExecutionException, InterruptedException {
          ...
          // 跟踪src的方法, 逻辑在innerTraceMethodFromSrc(), 直接看innerTraceMethodFromSrc()
          traceMethodFromSrc(srcFolderList, futures);
          traceMethodFromJar(dependencyJarList, futures);
          ...
        }
      
        private void innerTraceMethodFromSrc(File input, File output) {
          // 通过ASM, 插入方法, 逻辑在TraceClassAdapter类中
          ClassReader classReader = new ClassReader(is);
          ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
          ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
          classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
          ...
          os.write(classWriter.toByteArray());
        }
      
         // TraceMethodAdapter.java
        protected void onMethodEnter() {
              ...
              // 进入指定方法插入AppMethodBeat#i()
              mv.visitLdcInsn(traceMethod.id);
              mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "i", "(I)V", false);
        }
      
        protected void onMethodExit(int opcode) {
          ...
          // 退出指定方法插入AppMethodBeat#o()
          mv.visitLdcInsn(traceMethod.id);
          mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "o", "(I)V", false);
        }
      
      
    • 运行期

      TracePlugin#start() {
        // 启动跟踪ANR
        anrTracer.onStartTrace();
        // 启动跟踪卡顿
        frameTracer.onStartTrace();
      }
    
      // AnrTracer
      public void dispatchBegin(long beginMs, long cpuBeginMs, long token) {
          ...
          // 每帧刷新时, remove anr dump 任务
          if (null != anrTask) {
              anrHandler.removeCallbacks(anrTask);
          }
          // anr dump 任务
          anrTask = new AnrHandleTask(AppMethodBeat.getInstance().maskIndex("AnrTracer#dispatchBegin"), token);
          ...
          // 每秒发送任务, 如果5秒后没有被remove
          anrHandler.postDelayed(anrTask, Constants.DEFAULT_ANR);
      }
    
      // FrameTracer
      public void doFrame(String focusedActivityName, long start, long end, long frameCostMs, long inputCostNs, long animationCostNs, long traversalCostNs) {
          // 调用IDoFrameListener#doFrameAsync, 默认IDoFrameListener即FPSCollector
          notifyListener(focusedActivityName, frameCostMs);
      }
    
      // FPSCollector
      public void doFrameAsync(String focusedActivityName, long frameCost, int droppedFrames) {
        ...
        // 判断执行耗时是否大于阈值, 如果是则上报
        if (item.sumFrameCost >= timeSliceMs) { // report
          map.remove(focusedActivityName);
          item.report();
        }
      }
    
    

2.13.3 Resource Canary

  • 实现原理类似LeakCanary, 在ActivityLifecycleCallbacks#onActivityDestroyed把Activity放到WeakReference, 在ActivityLifecycleCallbacks#onActivityStarted调用Runtime#gc()GC, 如果Activity未被回收则说明存在泄露

参考