2.17 Matrix源码解析
2.13.1 IO Canary
- 流程图
-
找到Hook函数, 通过PLT Hook方案 (ELF Hook方案), hook掉posix的
open
、read
、write
、close
函数 - 检查场景
- 检测主线程IO, 检测条件
- 操作线程为主线程
- 连续读写耗时超过一定阈值或单次
write\read
耗时超过一定阈值
- 读写Buffer过小, buffer过小导致read/write过多, 检测条件
- buffer小于一定阈值
- read/write的次数超过一定的阈值
- 重复读
- 同一线程读某个文件的次数超过阈值(5次)
- 检测主线程IO, 检测条件
- Closeable Leak监控(文件、Cursor等未及时close引起的泄露)
-
例
FileInputStream
在创建和close
调用CloseGuard#open
、CloseGuard#close
、CloseGuard#warnIfOpen
方法, 在CloseGuard#warnIfOpen
中调用Reporter#report()
public void warnIfOpen() { if (allocationSite == null || !ENABLED) { return; } ... REPORTER.report(message, allocationSite); }
- 利用反射将
CloseGuard#warnIfOpen
中的ENABLED
置为true - 利用动态代理, 将
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官网)
- 编译期的任务
- 运行期
- 函数执行前后都会调用
MethodBeat#i/o()
, 再将method id
, 及时间offset
放到long类型变量中 - 通过
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
未被回收则说明存在泄露