关于java:百度App性能优化工具篇-Thor原理及实践

5次阅读

共计 14654 个字符,预计需要花费 37 分钟才能阅读完成。

01 背景

App 开发过程中,如果遇到一些疑难问题或者性能问题(如低端机卡顿),因为没法拿到更多零碎的无效信息很难无效定位。这时,Hook 不失为一种好的解决方案,Hook 技术是在程序运行的过程中,动静的批改代码,植入本人的代码逻辑,批改原有程序执行流程的技术。Hook 技术有如下几点能力:

耗时监控】在代码前后动静插入 Trace 打点,统计耗时;

性能监控】IO 监控、内存监控、View 绘制监控、大图检测等;

平安审查】Hook 敏感 API(例如定位),用以平安评审;

逆向和脱壳】对病毒样本进行逆向和脱壳剖析;

Hook 技术由来已久,目前业界 Java 和 Native Hook 都有不少优良的开源框架,然而如果咱们须要将 Hook 能力应用到线上,都或多或少有些问题,例如兼容性、稳定性、动态性等等。

鉴于此,咱们开发了一套 Thor 容器框架,提供规范的 Hook 接口,升高学习老本,同时将开源框架依照接口适配成插件动静下发按需装置,保障 Hook 能力的齐备和轻量性,并且后续呈现更加优良以及自研的框架的能够无缝的接入和 Hook 能力拓展,并且不须要下层业务代码和插件进行适配,保障兼容性。

02 现状

Android 零碎的编程语言次要分为 Java 和 C /C++,Hook 方向也次要分为 Native 和 Java Hook 两种,其中 Native Hook 原理也次要分为 PLT / Inline Hook 两大类,而后 Java Hook 也分为替换入口点 Hook(Replace Entrypoint Hook)和类 Inline Hook 两大类。

Native 办法执行流程大略如下:

Native 办法执行过程中会先通过 PLT 表找到 GOT 表中函数全局偏移地址,而后执行其机器码指令,PLT Hook 次要是指通过批改 GOT 中函数的全局偏移地址来达到 Hook 的目标,代表框架如:xHook、bHook 等;Inline Hook 则次要是指间接将函数执行的机器码指令进行批改和指令修复来达到 Hook 的目标,代表框架如:Android-Inline-Hook 等。

GOT(Global Offset Table):全局偏移表用于记录在 ELF 文件中所用到的共享库中符号的相对地址。

PLT(Procedure Linkage Table):过程链接表的作用是将地位无关的符号转移到相对地址。当一个内部符号被调用时,PLT 去援用 GOT 中的其符号对应的相对地址,而后转入并执行。

Java 办法执行流程大略如下:

Java 办法执行过程中会通过办法在虚拟机中对应的构造 Method 或 ArtMethod 构造体中的入口点(Entrypoint),来找到对应的字节码 / 机器码指令执行。替换入口点 Hook(Replace Entrypoint Hook)是指替换 Method/ArtMethod 中的入口点来达到 Hook 的目标,代表框架如:Xposed、Dexposed、YAHFA 等;类 Inline Hook 是指将入口点对应的字节码 / 机器码进指令进行批改和指令修复来达到 Hook 的目标,代表框架如:Epic 等,因为安卓虚拟机的 JIT/AOT 机制的存在,函数执行地址可能会进行各种变动,所以通常会将字节码强行编译成机器码,而后对立通过批改机器码指令来 Hook。

**2.1 常见 Native Hook 框架

**2.1.1 xHook 框架

xHook 框架通过 PLT Hook 计划来实现的,PLT Hook 是通过间接批改 GOT 表,使得在调用该共享库的函数时跳转到的是用户自定义的 Hook 性能代码。流程如下:

理解 PLT Hook 的原理之后,晓得该 Hook 形式有如下特点:

  • 因为批改的是 GOT 表中的数据,因而批改后,所有对该函数进行调用的中央就都会被 Hook 到。这个成果的影响范畴是该 PLT 和 GOT 所处的整个 so 库。
  • PLT 与 GOT 表中仅仅蕴含本 ELF 须要调用的共享库函数我的项目,因而不在 PLT 表中的函数无奈 Hook 到(比方非 export 导出函数就无奈 Hook 到)。

**2.1.1 Andorid-Inline-Hook 框架

Inline Hook 的原理则是间接批改函数在.text 理论执行的机器码来实现 Hook,不仅对所有 SO 失效,还能 Hook 非 export 导出函数,补齐了 PLT Hook 办法的有余。流程如下:

然而因为你间接批改的是机器码指令,因为指令架构版本的差别以及后续要进行指令修复,容易有兼容性的问题。

**2.2 常见 Java Hook 框架

**2.2.1 Dexposed 框架

Dexposed 框架只反对 Dalvik 虚拟机,此虚拟机通过 Method 构造体中 accessFlags 字段来判断以后办法是字节码还是机器码。该框架通过批改 accessFlags 字段为 ACC\_NATIVE,将 Java 原办法注册为 Native 办法,调用时会先调用 Native Hook 办法,再反射调用 Java 原办法来实现 Hook 的目标,流程图如下所示:

**2.2.2 Epic 框架

Epic 框架则是在 Dexposed 的根底上,减少了对 ART 虚拟机 Hook 的能力。因为 ART 虚拟机的复杂性(AOT 和 JIT),Java 代码执行的入口可能随时都在变动,如果通过 ArtMethod 中的 entry\_point\_from\_quick\_compiled\_code\_字段入口进行 Hook,可能会产生不可预期的解体。Epic 则是在 Wißfeld, Marvin 的论文 ArtHook: Callee-side Method Hook Injection on the New Android Runtime ART 根底上做了实现,大略思路是把 entry\_point\_from\_quick\_compiled\_code\_指向的机器码地址(未编译的字节码也会强制编译成机器码,相似于 Inline Hook)进行批改,跳转到跳板代码,而后通过跳转代码进行散发,调用 Hook 办法之后再调用原办法,来达到 Hook 的目标。流程图如下:

**2.3 常见框架比照

通过剖析和比照可知,开源框架存在比拟典型的几个问题如下:

  • Hook 能力不齐备:无奈同时满足所有的 Hook 场景(Java Hook 和 Native Hook);
  • 兼容性问题:因为现有框架可能存在各种各样的稳定性问题,导致如果后续替换 Hook 框架,则所有的业务 Hook 逻辑都要批改存在兼容性问题;
  • 不反对动静 Hook:只能将代码内置到主包中,没法动静下发装置实现动静 Hook;
  • 没有容错机制:大部分框架都有稳定性问题且没有容灾机制,如果导致利用解体,会导致灾难性的结果。

03 计划选型

从现有情况来看,如果同时须要 Java/Native Hook 的能力,那么至多须要集成两个框架,业务代码也只能在主包中编写,减少包体积。其次如果替换应用更加优良或者自研的框架时,所有的业务代码也要跟着批改,学习和适配兼容的老本微小。最初 Hook 框架导致的解体,因为没有动静能力和容灾机制也只能从新公布利用和铺渠道,影响用户体验。

尽管每个框架都有各自的一些问题,然而要求咱们从头开始开发一款同时反对 Java 和 Native Hook 的框架,没有稳定性问题并且兼容所有安卓版本、轻量且容灾的框架,反复造轮子并且 ROI 太低,所以咱们要开发本人的一套容器框架,取短处补短板,充分利用好已有的框架来实现目标。

百度 App 作为超级 App,自身就是一个航空母舰,容器框架要在其上线至多须要达到以下几点要求:

  • 齐备性:须要反对所有的 Hook 能力(Java 和 Native Hook),可能笼罩所有代码范畴;
  • 兼容性:插件保障向后兼容,即便替换底层 Hook 框架,业务齐全无感知,不须要重新学习和适配新的 Hook 框架;
  • 轻量动态性:体积要尽量保障轻量,这对于手百尤为重要,并且反对通过云控下发的形式动静装置运行;
  • 容灾性:产生间断启动解体时能够自敞开复原,不会继续影响线上用户。

04 Thor 揭秘

为了满足上述要求,咱们开发了 Thor 容器框架,提供规范的 Hook 接口,蕴含 Java 和 Native Hook 接口,业务方不须要关怀底层实现逻辑(如同虚构文件系统 VFS),只须要理解如何应用这些接口即可,极大的升高学习接入老本。同时将稳固的开源框架依照接口适配成插件,将这些 Hook 能力进行形象,按需动静的装置加载,保障 Hook 能力的齐备性和轻量性。并且后续呈现更加优良以及自研的框架的能够无缝的接入,对下层也是无感知的,不须要下层业务代码和插件进行适配,很好的保障了兼容性。

**4.1 Thor 整体构造

**4.1.1 Thor 架构图

  • 撑持业务:撑持了低端机、隐衷合规、OOM 和流水线等多个业务;
  • Thor 形象层:次要蕴含 Java / Native Hook 和 Thor Module 的业务模块等形象层接口;
  • 应用层插件:蕴含了 SP、IO、线程、内存等根底插件或者业务相干插件,其适配实现了 Thor Module 的业务模块接口;
  • 实现层插件 :Epic(Java Hook)、xHook(PLT Hook)、Android-Inline-Hook(Inline Hook) 或者自研等插件,其适配实现了 Java / Native Hook 接口;
  • Thor 框架
  • 插件模块:反对自主开发插件,反对插件热插拔,能够通过内置或云控动静下发,即时失效;保护和调度插件的生命周期;
  • 沙盒模块:反对在沙盒过程装置插件,不影响主过程,重启失效;
  • 校验模块:反对对插件进行平安校验,保障插件起源安全性;
  • 插件治理界面:反对对已有插件动静装置和卸载的管制治理界面。

Thor 实现层插件和 Thor 应用层插件都是 apk 的模式存在,然而也能够以组件源码的模式集成打包到宿主中。

**4.2 Thor 外围劣势

**4.2.1 易用性

Thor 只开发形象层接口,底层实现对业务是不可见的,不须要重复学习,这样最大水平的保障了易用性。Java/Native Hook 都提供了规范的接口供业务方应用,接口如下:

  • Java Hook 接口(Thor 提供 Java Hook 能力的接口)
public interface IHookEntity {
    ......
    /**
     * Hook 指定的办法
     *
     * @param hookedMethod 待 Hook 的办法 Method 对象
     * @param hookCallback Hook 回调{@link IHookCallback}
     */
    void hookMethod(Member hookedMethod, IHookCallback hookCallback);
    
    ......
}
如果是 Java Hook 应用方只须要间接应用该接口的能力即可;如果是能力提供方,则须要将 Java Hook 能力注入到 Thor 形象层的 Java Hook 接口实现中。
  • Native Hook 接口(Thor 提供 Native Hook 能力的接口,蕴含 PLT Hook 和 Inline Hook)
struct thor_abstract {// 函数定义:PLT Hook 实现框架的函数指针    // lib_name            被 Hook 的 so 库的名称    // symbol              被 Hook 的函数名    // hook_func           Hook 办法    // backup_func         原办法备份(函数指针)int (*thor_plt_hook)(const char *lib_name, const char *symbol, void *hook_func, void **backup_func);    // 函数定义:Inline Hook 实现框架的函数指针    // target_func         原办法    // hook_func           Hook 办法    // backup_func         原办法备份(函数指针)int (*thor_inline_hook)(void *target_func, void *hook_func, void **backup_func);    // PLT Hook 二期(新增接口,反对批量 plt hook)struct thor_plt_ext *plt_ext;};
如果是 Nava Hook 应用方只须要间接应用该接口的能力即可;如果是能力提供方,则须要将 Nava Hook 能力注入到 Thor 形象层的 Native Hook 接口实现中。
  • Thor Module 接口(Thor 提供的业务模块接口)
public abstract class ThorModule implements IThorModule {/**     * 调度插件的加载生命周期     */    public abstract void handleLoadModule();    /**     * 宿主告诉和更新插件配置信息生命周期     */    public void onPluginFuncControl(PluginFuncInfo pluginFuncInfo) {}}

次要提供给业务模块应用,如果须要应用 Hook 能力,间接在 handleLoadModule 子类实现中调用 Thor 的各个 Hook 能力即可(不是必须应用的,Thor 作为容器框架只是额定提供了 Hook 的能力而已)。

丨 4.2.2 齐备性

该框架同时反对 Java / Native Hook 的能力,具备齐备的 Hook 能力。上大节解说了提供给业务方的 Java/Native Hook 和 Thor Module 业务模块等形象层接口,底层实现则依据接口进行适配之后,通过动态代码依赖注入或动静模块加载注入到形象层实现中,这样 Thor 就具备了齐备的 Hook 能力。
Thor 的 Java Hook 能力(类 Xposed API)
Hook Handler#dispatchMessage 办法,代码如下:

ThorHook.findAndHookMethod(Handler.class, "dispatchMessage", new IHookCallback() {@Override    public void beforeHookedMethod(IHookParam param) {Message msg = (Message) param.getArguments()[0];        Log.d(TAG, ">>>>>>>>>>>dispatchMessage:" + msg);    }        @Override    public void afterHookedMethod(IHookParam param) {Log.d(TAG, "<<<<<<<<<<<<dispatchMessage:");    }}, Message.class);

持续看 Thor#findAndHookMethod 的逻辑,代码如下:

/**  * 寻找办法并将其 Hook,最初一个参数必须是 Hook 办法的回调  *  * @param clazz          Hook 办法所在类的类名称  * @param methodName     Hook 办法名  * @param hookCallback   回调{@link IHookCallback}  * @param parameterTypes Hook 办法的参数类型  */public static void findAndHookMethod(Class<?> clazz, String methodName,                                     IHookCallback hookCallback, Class<?>... parameterTypes) {......                                     Method methodExact = ThorHelpers.findMethodExact(clazz, methodName, parameterTypes);    hookMethod(methodExact, hookCallback);    ......}

ThorHook#findAndHookMethod 通过类的类类型、函数名和参数,找到相应的 Method,再调用 ThorHook#hookMethod 进行 Hook,持续看如下代码:

/** * Hook 指定的办法 * * @param hookedMethod 待 Hook 的办法 Method 对象 * @param hookCallback Hook 回调{@link IHookCallback} */public static void hookMethod(Member hookedMethod, IHookCallback hookCallback) throws HookMethodException {......    CallbacksHandler callbacksHandler;    synchronized (sHookedMethodCallbacks) {callbacksHandler = sHookedMethodCallbacks.get(hookedMethod);        if (callbacksHandler == null) {// 未 Hook 过的 Method            callbacksHandler = new CallbacksHandler();            callbacksHandler.register(hookCallback);            sHookedMethodCallbacks.put(hookedMethod, callbacksHandler);        } else {// Hook 过的 Method,只须要注册回调即可            callbacksHandler.register(hookCallback);            return;        }    }        ThorManager.getInstance().getHookEntity().hookMethod(hookedMethod, callbacksHandler);}

多个业务方如果 Hook 了同一个 java 办法,会被加到缓存中,Hook 回调的时候再一一进行散发;持续能够看到 hookMethod 最初调用到了 getHookEntity#hookMethod 办法,最终会调用到具体 Java Hook 框架实现的 hookMethod 办法,例如 Epic 的适配代码如下:

/** * Epic 框架适配类 */public class EpicHookEntity implements IHookEntity {@Override    public void hookMethod(Member hookedMethod, final IHookCallback hookCallback) {// Epic Hook 办法回调        XC_MethodHook xc_methodHook = new XC_MethodHook() {@Override            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {// 将 Epic Hook 办法信息包装成形象层 Hook 办法信息                IHookParam hookParam = new EpicHookParam(param);                if (hookCallback != null) {// 调用 before 回调                    hookCallback.beforeHookedMethod(hookParam);                }            }            @Override            protected void afterHookedMethod(MethodHookParam param) throws Throwable {// 将 Epic Hook 办法信息包装成形象层 Hook 办法信息                IHookParam hookParam = new EpicHookParam(param);                if (hookCallback != null) {// 调用 after 回调                    hookCallback.afterHookedMethod(hookParam);                }            }        };        // Epic Hook Method        DexposedBridge.hookMethod(hookedMethod, xc_methodHook);    }}

Thor 的 Native Hook 能力
应用 PLT Hook 对应 SO 所在 PLT 表的 open 函数,Inline Hook puts 办法,局部代码如下:

thor_abstract *impl = reinterpret_cast<thor_abstract *>(nativePtr);// plt hook openthor->thor_plt_hook(so_name, "open", (void *) ProxyOpen, (void **) &original_open);// inline hook putsimpl->thor_inline_hook((void *) puts, (void *) new_puts, (void **) &origin_puts);

依据 4.2.1 中的 Native Hook 接口可知,thor_plt_hook 和 thor_inline_hook 成员都是函数指针,指针只有指向真正的 Native Hook 能力,代码才会失效,所以相应的 Hook 框架也须要依据 Native Hook 接口进行适配,例如 xHook 适配 PLT Hook 局部代码如下:

thor_abstract *thor = reinterpret_cast<thor_abstract *>(nativePtr);// plt hook 函数指针赋值 thor->thor_plt_hook = xhook_impl_plt_hook;.....// xhook 适配局部代码 int xhook_impl_plt_hook(const char *so_name, const char *symbol, void *new_func, void **old_func) {void *soinfo = xhook_elf_open(so_name);    if (!soinfo) {return -1;}    if (xhook_hook_symbol(soinfo, symbol, new_func, old_func) != 0) {return -2;}    xhook_elf_close(soinfo);    return 0;}

Android-Inline-Hook 适配 Inline Hook 接口局部示例代码如下:

// inline hook 函数指针赋值 thor->thor_inline_hook = impl_inline_hook;// andorid-inline-hook 适配局部代码 int impl_inline_hook(void *target_func, void *new_func, void **old_func) {if (registerInlineHook((uint32_t) target_func, (uint32_t) new_func, (uint32_t **) old_func)) {return -1;}    if (inlineHook((uint32_t) target_func) != ELE7EN_OK) {return -2;}    return 0;}

咱们在应用这些底层 Hook 框架适配组件(插件)的过程中,也遇到了一些问题,例如 Epic 在 Hook Handler#dispatchMessage 的过程中,会产生不合乎预期的解体,然而在进一步调研了 SandHook 能够解决该问题之后,马上就适配了 SandHook 的实现来解决问题,业务方的代码不须要做任何批改和适配,再例如 xHook 的作者新写了一款 PLT Hook 框架 bHook,解决了 xHook 的一些问题(例如增量 Hook,unHook 能力等等),咱们也很快跟进对 bHook 框架进行了调研和适配,同样业务方也是无感知的,这两个例子从侧面佐证了 Thor 容器框架具备良好的兼容性和可扩展性。
同时同学们可能会有如下纳闷,如果 Hook 框架出问题,难道只能去找更好的开源计划进行适配吗?有没有银弹呢?这其实就回到了计划选型时所说的,因为安卓的碎片化和复杂性,从头开始开发一款同时反对 Java 和 Native Hook 的框架,没有稳定性问题并且兼容所有安卓版本、轻量且容灾的框架,反复造轮子并且 ROI 太低,所以咱们要开发本人的一套容器框架,取短处补短板,充分利用好已有的框架来实现目标,当然也不排除在所有开源计划都不满足的状况下,进行深度二次开发或者自研底层 Hook 框架,不过这些对业务代码都不可见,不须要批改适配。

丨 4.2.3 轻量动态性

百度 App 作为一个航母级利用,对于包体积大小还是比拟敏感的,依据 Google Store 的数据,包体积每减少 6M,就升高 1% 的转化率,影响微小,所以 Thor 容器框架要尽可能的做到轻量,基于此,咱们须要把业务代码做成动静加载的插件,甚至是底层适配的 Hook 实现也要做成动静可加载的插件。
业务代码能够不在宿主中编写,只在插件代码中编写,而后将生成的插件动静下发到手机上,再通过插件加载模块动静加载失效。例如:在须要监控利用 IO 的状况下,下发 IO 插件和 xHook 插件到手机上安装,Hook IO 操作(例如:open、read、write 等),将不合理的 IO 操作上报给平台,同时在不须要监控的时候动静卸载敞开即可。
插件动静加载失效的大抵流程如下:
Thor 容器框架会对插件进行 v2 签名校验,保障插件起源的安全性;解析插件中的清单文件存储为 info 对象,蕴含插件包名、插件入口类、ABI List、插件版本等等;对插件中的 SO 进行开释,不然 classloader 会找不到插件中的 SO; 创立自定义的 ThorClassLoader 进行插件类加载,会先加载插件中的类再加载宿主中的类,局部代码如下:

/** * 先加载 Thor 插件中的类,再加载宿主中的类 * * @param name */@Overrideprotected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {Class<?> clazz = null;    try {        // 加载插件中的类        clazz = findOwnClass(name);    } catch (ClassNotFoundException ignored) {// ignored}    if (clazz != null) {return clazz;}    // 加载宿主中的类    return mHostClassLoader.loadClass(name);}

实例化插件入口类并判断其接口类型,注入相应的能力到 Thor 形象层:
如果是 Java Hook 接口实现类,则注入 Java Hook 实例能力给 Thor 形象层;如果是 Native Hook 接口实现类,则注入 Native Hook 实例能力给 Thor 形象层;如果是 Thor Module 业务接口实现类,则将业务实例存储到 map 中,期待后续插件治理模块调度相应的生命周期。
大略流程图如下:

这里大家可能会有以下疑难:
如果下层的业务层插件先装置,底层实现层插件后装置的状况怎么办?

Thor 有一个 pending 模式会等到实现层装置失效之后,业务层的逻辑再开始执行失效;

Android 8.0 的 classloader 有 namespace 机制,不同的 classloader 加载雷同 SO,会有多份 SO 在内存中,这个时候如何将插件中 Native Hook 能力传递给 Thor 形象层呢?

通过翻看源码,Binder 调用中的 Parcel 类领有 Native 对象的内存指针,所以咱们也借鉴雷同的办法,将 Native 对象内存指针地址通过 Java 层进行传递,而后应用领有雷同内存布局的 struct 构造体进行转换,这样就能够拿到 Native Hook 实现了。

丨 4.2.4 容灾性

Hook 技术毕竟是一个比拟 hack 的技术,谁也无奈保障百分百的兼容和稳定性,所以咱们要针对这种非凡的解体状况进行兜底,将该框架可能造成的影响降到最低。目前有三个容灾能力:
Thor 容器框架在及时性要求不高的状况下,反对沙盒过程装置。如果装置过程中产生了解体,不会影响主过程,用户无感知,并且会主动回滚插件进行止损;Thor 容器框架会联合平安模式,能够监控间断启动解体次数,如果超过阈值,就主动敞开 Thor 框架,疾速自复原及时止损;通过百度外部的性能平台监控 Thor 相干解体,能够通过云控动静敞开 Thor 框架。
通过这三个容灾能力,根本可能保障百度 App 不会因为 Thor 容器框架产生大规模的解体影响用户体验,可能较好的治理危险。

05 业务实际案例

Thor 框架作为一套动静插件容器基础设施,真正让其起作用的是丰盛的插件生态(如 IO、内存、线程、隐衷等等),能够依据理论须要,大胆的施展设想,开发适宜业务场景的插件。目前该框架能够利用于线下 RD 开发、线下流水线和线上云控开启,因为篇幅限度,摘选其中一些案例讲述。

丨 5.1 线程插件

因为在开发过程中顺手就能够创立一个线程运行,也没有强制束缚应用线程池,这样会导致很多游离线程,线程过多不仅会进步内存应用导致 IO 放大和 OOM 解体,并且有频繁的上下文切换会导致启动和晦涩度问题。线程插件则通过 Thor 框架的 PLT Hook 能力 Hook libart.so 库中的 pthead_create 的函数,来监控线程的创立。外围代码如下:

// 原始被办法函数指针 static void *(*origin_pthread_create)(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) = NULL;// Hook 之后的 Proxy 办法 void *proxy_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) {......    // 调用原始办法    void *ret = origin_pthread_create(thread, attr, start_routine, arg);    // 打印 Java 堆栈    printJavaStackTrace();    return ret;}void doHook(long nativePtr){thor_abstract *impl = reinterpret_cast<thor_abstract *>(nativePtr);    // plt hook pthread_create    impl->thor_plt_hook("libart.so", "pthread_create", (void *) proxy_pthread_create, (void **) &origin_pthread_create);}}

Hook 实现之后,会在创立线程的过程中先调用 proxy_pthread_create 的代理办法再调用原始的创立线程办法,在代理办法中通过反射打印创立以后线程的 Java 堆栈。在百度 App 启动阶段通过线程插件监控记录发现有 100+ 个 SP 线程,和 50+ 非线程池治理的线程,重大影响启动速度和用户体验。帮助组内同学进行优化后(SP 迁徙 KV 组件,所有线程通过线程池治理),升高启动过程中线程数 100+,优化 TTI(Time To Interactive)1s+。

丨 5.2 IO 插件

因为在开发过程中有同学会把一些 IO 操作在主线程中执行,例如文件读写、网络传输,这样会导致主线程卡顿,影响启动速度、晦涩度等,即便是小文件也可能因为内存不足和磁盘有余等起因导致 IO 读写放大,从而导致长耗时的 IO,同时还有一些不合理的 IO,例如:读写 buffer 过小会导致频繁的零碎调用影响性能,以及反复读同一个文件等。IO 插件则通过 Thor 框架的 PLT Hook 能力 Hook IO 操作(open、read 和 write、close 等),用来记录监控主线程不合理的 IO。外围代码如下:

......thor->thor_plt_hook(so_name, "open", (void *) ProxyOpen, (void **) &original_open);thor->thor_plt_hook(so_name, "read", (void *) ProxyRead, (void **) &original_read);thor->thor_plt_hook(so_name, "write", (void *) ProxyWrite, (void **) &original_write);thor->thor_plt_hook(so_name, "close", (void *) ProxyClose, (void **) &original_close);......

调用 open 时会先调用 ProxyOpen,ProxyOpen 中会存储 fd(文件描述符)和 IOInfo 的 map 映射关系,后续的 ProxyRead、ProxyWrite 和 ProxyClose 则通过 fd 来欠缺 IOInfo 的信息,IOInfo 局部字段如下:

class IOInfo {public:        // 文件门路        const std::string file_path_;        // 开始工夫        int64_t start_time_μs_;        // buffer 大小        long buffer_size_ = 0;        // 间断读写次数        long max_continual_rw_cnt_ = 0;        // 文件大小        long file_size_ = 0;        // 总耗时        long total_cost_μs_ = 0;};

在最初文件 Close 的时候通过剖析 IOInfo 即可剖析出不合理的 IO 操作(例如主线程 IO 耗时过长、IO 的 buffer 过小(导致系统调用增多)、反复读等)。在百度 App 启动过程中通过 IO 插件监控记录发现有 20+ 不合理的 IO 操作,与各个业务方的同学进行协同和优化,最终启动速度 TTI 优化 400ms+,晋升了用户体验。

丨 5.3 隐衷合规插件

因为个人信息法的颁布,利用不能够在隐衷弹窗确认前获取用户个人信息,基于此,隐衷合规插件应用 Thor 框架的 Java Hook 的能力,监控记录隐衷弹窗前不合理的隐衷 API 调用(例如定位、WI-FI、蓝牙等等),局部代码如下:

// hook getDeviceIdThorHook.findAndHookMethod(TelephonyManager.class, "getDeviceId", new IHookCallbackImpl(), String.class);

隐衷合规插件联合了手百外部通用防劣化流水线的能力(这里不开展解说),每天主动编译打包内置隐衷合规插件,而后主动在真机上测试,监控记录隐衷弹框前的隐衷问题,最初主动剖析、散发问题卡片给相应的业务同学进行批改,无效的躲避了合规危险,避免被下架整改;

丨 5.4 内存插件

内存优化是性能和稳定性优化中的一大课题,如果内存占用过大,轻则导致频繁 GC 造成卡顿,重则内存溢出导致 OOM 利用解体。内存插件则通过 Thor 框架 PLT Hook 的能力,监控记录 Java 堆内存和 Native 内存(监控 malloc 和 free 等函数)。内存插件目前有两个应用场景:
联合线下流水线的能力,每天主动编译打包内置内存插件,而后在真机上应用 monkey 随机测试,在内存水位高时 dump hprof 文件,并进行裁剪(通过 PLT Hook write 办法实现,参考 Tailor 在 hprof 文件写入过程中过滤不须要的信息),最初主动剖析出内存透露问题和大对象问题,主动散发问题给相应的业务同学进行批改,将内存问题前置,避免问题上线影响用户体验。联合线上 Thor 丰盛的云控能力,动静下发到 OOM 用户手机上开启内存监控能力,而后回捞上报相干的数据进行问题剖析、散发,解决线下不易复现的线上 OOM 解体问题。

06 总结

Hook 这个话题由来以久,框架品种繁多,然而没有一款全面性、动态性以及兼容性好的框架,然而正是有这些优良的框架(Xposed、Dexposed、Epic、xHook 等),咱们能力学习和借鉴其优良的设计和理念,补齐有余,Thor 只是在这条路线上迈出了一小步,前面须要更加欠缺和夯实 Thor 基础设施,并且丰盛插件生态,在 Android 性能和稳定性治理上添砖加瓦。

——————END——————

相干链接:
[1] Dexposed 链接:https://github.com/alibaba/de…
[2] ArtHook 论文链接:http://publications.cispa.saa…
[3] Epic 链接:https://github.com/tiann/epic
[4] xHook 链接:https://github.com/iqiyi/xHook
[5] Android-Inline-Hook 链接:https://github.com/ele7enxxh/…
[6] Tailor 链接:https://github.com/bytedance/…
[7] Matrix 链接:https://github.com/Tencent/ma…

正文完
 0