一、 概述
经过实际项目大量测试验证,FastHook表现出了远超YAHFA的优异稳定性。用户反馈未出现Hook引发的稳定性问题、压力测试也未发生Hook引发的稳定问题。之所以FastHook拥有优异的稳定性,除了框架实现原理的优越性之外,还得益于FastHook出色的细节处理。
本文将通过FastHook实现原理优越性与一些出色的细节处理来解释为何FastHook拥有优异的稳定性,最后对比YAHFA框架。
二、先天优势
如果你还未了解FastHook,请移步FastHook——一种高效稳定、简洁易用的Android Hook框架。
FastHook相较YAHFA框架原理上最大的优势、也是最大的亮点便是:不需要备份原方法!不需要备份原方法!不需要备份原方法!
科学上有一个著名的“奥卡姆剃刀定律”,什么意思呢?如果一个现象有两个或者多个不同的理论解释,那么选最简单的那个。做Hook框架,也可以用剃刀定律来做指导:实现相同的功能,选对系统状态改动最小的。
“备份原方法”是一种隐患颇多的方式,引发了诸如方法解析出错、Moving GC空指针等问题。尽管其他框架通过一些手段来提高稳定性,比如保证方法不被再次解析、检查Moving GC是否移动了原方法相关对象等,但是这些都不是理论安全的,就像地上有个坑,你不去补上,而是让人不要去踩。
反观FastHook,Hook时对系统原有状态的改变是最小的。
- Inline模式改变的仅是几个字节的指令,因平台而异,不篡改任何方法。
- EntryPoint模式替换了方法EntryPoint,但是原方法将强制为解释执行,也可等价的看为未做修改。
简而言之,FastHook就是用Hook方法hook原方法,原方法hook Forward方法来实现最小改动hook。完美地从实现层面解决了YAHFA框架不能解决的问题,而且无需做一些其他操作,YAHFA框架都需要一些其他的操作来提高稳定性,而FastHook不需要做任何其他处理,更简洁、更优雅。
三、比其他框架更出色的细节处理
3.1 JIT状态检查
如果你看过YAHFA框架代码,你会发现没有一个框架做了JIT状态检查。JIT状态检查的目的是为了保证hook的安全性,但这也不是理论安全的,也无法做到理论安全。这是为什么呢?
3.1.1 Inline模式
如果原方法未编译则需要进行手动JIT编译。那么问题来了,什么时候编译才是安全的呢。下面列举出所有可能出现的情景:
- 原方法未进行JIT编译,此时手动JIT编译时安全的
- 原方法未进行JIT编译,即将进入编译等待队列或已进入编译等待队列,此时手动JIT编译是不安全的
- 原方法正在JIT编译,此时手动JIT编译是不安全的
- 原方法编译完成,此时手动编译是安全的
上述4中情景,其中2、3是不安全的。如果要保证手动JIT编译的安全性,必须做到以下两点:
- 禁止JIT编译,防止从1变化到2
- 能够判断2、3,当处于2、3状态时,等待其变化到4
现在来看看FastHook到底是怎么处理的
int CheckJitState(JNIEnv *env, jclass clazz, jobject target_method) {
void *art_method = (void *)(*env)->FromReflectedMethod(env, target_method);
//添加kAccCompileDontBother,禁止JIT、AOT编译
AddArtMethodAccessFlag(art_method, kAccCompileDontBother);
uint32_t hotness_count = GetArtMethodHotnessCount(art_method);
if(hotness_count >= kHotMethodThreshold) {
//hotness_count >= hot_threshold,肯定就不是1了,看看是2、3、4中的哪一个
long entry_point = (long)GetArtMethodEntryPoint(art_method);
if((void *)entry_point == art_quick_to_interpreter_bridge_) {
void *profiling = GetArtMethodProfilingInfo(art_method);
void *save_entry_point = GetProfilingSaveEntryPoint(profiling);
if(save_entry_point) {
//JIT垃圾回收会改变方法EntryPoint,虽然方法已经编译了,但是EntryPoint也可能是art_quick_to_interpreter_bridge
return kCompile;
}else {
//JIT状态保存在profiling中,通过其来判断是否是正在编译,如果不是可能是正在等待或者已经编译失败。
bool being_compiled = GetProfilingCompileState(profiling);
if(being_compiled) {
return kCompiling;
}else {
return kCompilingOrFailed;
}
}
}
return kCompile;
}else {
//hotness_count < hot_threshold,可能是1,也可能是2,即将进入编译等待队列,统一加一个增量,如果此时大于hot_threshold,就认为是2,反之是1
uint32_t assumed_hotness_count = hotness_count + kHotMethodMaxCount;
if(assumed_hotness_count > kHotMethodThreshold) {
return kCompiling;
}
}
return kNone;
}
class ProfilingInfo {
private:
ProfilingInfo(ArtMethod* method, const std::vector<uint32_t>& entries);
// Number of instructions we are profi