其他
百度App性能优化工具篇 - Thor原理及实践
The following article is from 百度App技术 Author Lwan
GEEK TALK
01
背景
GEEK TALK
02
现状
GOT(Global Offset Table):全局偏移表用于记录在 ELF 文件中所用到的共享库中符号的绝对地址。 PLT(Procedure Linkage Table):过程链接表的作用是将位置无关的符号转移到绝对地址。当一个外部符号被调用时,PLT 去引用 GOT 中的其符号对应的绝对地址,然后转入并执行。
丨2.1 常见Native Hook框架
丨2.1.1 xHook框架
由于修改的是GOT表中的数据,因此修改后,所有对该函数进行调用的地方就都会被Hook到。这个效果的影响范围是该PLT和GOT所处的整个so库。 PLT与GOT表中仅仅包含本ELF需要调用的共享库函数项目,因此不在PLT表中的函数无法Hook到(比如非export导出函数就无法Hook到)。
丨2.1.1 Andorid-Inline-Hook框架
丨2.2 常见Java Hook框架
丨2.2.1 Dexposed框架
丨2.2.2 Epic框架
丨2.3 常见框架对比
Hook能力不完备:无法同时满足所有的Hook场景(Java Hook和Native Hook); 兼容性问题:由于现有框架可能存在各种各样的稳定性问题,导致如果后续替换Hook框架,则所有的业务Hook逻辑都要修改存在兼容性问题; 不支持动态Hook:只能将代码内置到主包中,没法动态下发安装实现动态Hook; 没有容错机制:大部分框架都有稳定性问题且没有容灾机制,如果导致应用崩溃,会导致灾难性的后果。
GEEK TALK
03
方案选型
完备性:需要支持所有的Hook能力(Java和Native Hook),能够覆盖所有代码范围; 兼容性:插件保证向后兼容,即使替换底层Hook框架,业务完全无感知,不需要重新学习和适配新的Hook框架; 轻量动态性:体积要尽量保证轻量,这对于手百尤为重要,并且支持通过云控下发的方式动态安装运行; 容灾性:发生连续启动崩溃时可以自关闭恢复,不会持续影响线上用户。
GEEK TALK
04
Thor揭秘
丨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 易用性
Java Hook接口(Thor提供Java Hook能力的接口)
public interface IHookEntity {
......
/**
* Hook指定的方法
*
* @param hookedMethod 待Hook的方法Method对象
* @param hookCallback Hook回调{@link IHookCallback}
*/
void hookMethod(Member hookedMethod, IHookCallback hookCallback);
......
}
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;
};
Thor Module接口(Thor提供的业务模块接口)
public abstract class ThorModule implements IThorModule {
/**
* 调度插件的加载生命周期
*/
public abstract void handleLoadModule();
/**
* 宿主通知和更新插件配置信息生命周期
*/
public void onPluginFuncControl(PluginFuncInfo pluginFuncInfo) {
}
}
丨4.2.2 完备性
Thor的 Java Hook 能力(类Xposed API)
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);
/**
* 寻找方法并将其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);
......
}
/**
* 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);
}
/**
* 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 能力
thor_abstract *impl = reinterpret_cast<thor_abstract *>(nativePtr);
// plt hook open
thor->thor_plt_hook(so_name, "open", (void *) ProxyOpen, (void **) &original_open);
// inline hook puts
impl->thor_inline_hook((void *) puts, (void *) new_puts, (void **) &origin_puts);
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;
}
// 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;
}
Thor容器框架会对插件进行v2签名校验,保证插件来源的安全性; 解析插件中的清单文件存储为info对象,包含插件包名、插件入口类、ABI List、插件版本等等; 对插件中的SO进行释放,不然classloader会找不到插件中的SO; 创建自定义的ThorClassLoader进行插件类加载,会先加载插件中的类再加载宿主中的类,部分代码如下:
/**
* 先加载Thor插件中的类,再加载宿主中的类
*
* @param name
*/
@Override
protected 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中,等待后续插件管理模块调度相应的生命周期。
如果上层的业务层插件先安装,底层实现层插件后安装的情况怎么办?
Android 8.0的classloader有namespace机制,不同的classloader加载相同SO,会有多份SO在内存中,这个时候如何将插件中Native Hook能力传递给Thor抽象层呢?
丨4.2.4 容灾性
Thor容器框架在及时性要求不高的情况下,支持沙盒进程安装。如果安装过程中发生了崩溃,不会影响主进程,用户无感知,并且会自动回滚插件进行止损; Thor容器框架会结合安全模式,可以监控连续启动崩溃次数,如果超过阈值,就自动关闭Thor框架,快速自恢复及时止损; 通过百度内部的性能平台监控Thor相关崩溃,可以通过云控动态关闭Thor框架。
GEEK TALK
05
业务实践案例
// 原始被方法函数指针
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);}
}
丨5.2 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);
......
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;
};
丨5.3 隐私合规插件
// hook getDeviceId
ThorHook.findAndHookMethod(TelephonyManager.class, "getDeviceId", new IHookCallbackImpl(), String.class);
丨5.4 内存插件
结合线下流水线的能力,每天自动编译打包内置内存插件,然后在真机上使用monkey随机测试,在内存水位高时dump hprof文件,并进行裁剪(通过PLT Hook write方法实现,参考Tailor在hprof文件写入过程中过滤不需要的信息),最后自动分析出内存泄漏问题和大对象问题,自动分发问题给相应的业务同学进行修改,将内存问题前置,防止问题上线影响用户体验。 结合线上Thor丰富的云控能力,动态下发到OOM用户手机上开启内存监控能力,然后回捞上报相关的数据进行问题分析、分发,解决线下不易复现的线上OOM崩溃问题。
GEEK TALK
06
总结
END
相关链接:
[1] Dexposed链接:https://github.com/alibaba/dexposed
[2] ArtHook论文链接:http://publications.cispa.saarland/143/
[3] Epic链接:https://github.com/tiann/epic
[4] xHook链接:https://github.com/iqiyi/xHook
[5] Android-Inline-Hook链接:https://github.com/ele7enxxh/Android-Inline-Hook
[6] Tailor链接:https://github.com/bytedance/tailor
[7] Matrix链接:https://github.com/Tencent/matrix/
推荐阅读: