Andfix基础实现请见Android_热修复_Andfix原理分析
前言
Andfix存在版本兼容问题,已停止更新,后续Sopfix未开源(5000用户以内免费)
Andfix存在兼容性的原因:
Andfix是通过虚拟机类加载过程中对方法表的ArtMethod
结构体的操作,实现对方法的替换,方法表是由虚拟机创建的,但是虚拟机的代码不是一成不变的,每次Android发布新版本,Google工程师都会对虚拟机的代码进行优化修改,最具典型的是Android5.0以前才用的是dalvik虚拟机
,5.0以后是art虚拟机
另外还有在Android 8.0
以后,Android源码中针对被虚拟机频繁调用的方法增加了一个FastNative
注解,这个注解是用来通知虚拟机层,这个修改主要就是针对ArtMethod结构体
,在ArtMethod结构体
增加了一个fast
成员变量,这个成员变量的作用是,当方法压栈(Java栈)时,维持一个队列去缓存,虚拟机调用频率比较高的方法,其最终目的是为了提升Android的运行速度。
这么说可能有点抽象,我们举个具体的栗子:
在上文中我们提到在native层对ArtMethod结构体
操作实现方法的替换,其中有个变量叫做method_index_
(方法索引),其
在6.0的源码中是这样声明的uint32_t method_index_
在8.0的源码中是这样声明的uint16_t method_index_
我们可以看到我们可以看到method_index_
由32位改成了16位,如果我们用上文中的方式运行在8.0手机上进行替换,显而易见会出现问题,如图:
要注意的一点是:无论我们写的是空方法,还是方法中实现了很多逻辑(写了很多代码),在虚拟机中生成的
ArtMethod结构体
大小都是一致的!
所以Andfix必须进行版本适配,替换方式与版本一一对应
所以Andfix的兼容实现就是对每个版本的Android系统,创建一个头文件,将Android源码中的对ArtMethod结构体声明的头文件
对copy进来就可以了,仅将对ArtMethod结构体
声明的部分copy就可以,也就是抄Android中的源码,事实上Andfix中就是这么做的
我们可以看到其分为了art虚拟机
和dalvik虚拟机
,其中art虚拟机
中又区分了诸多版本,再看下源码中替换操作之前执行的判断:
extern void __attribute__ ((visibility ("hidden"))) art_replaceMethod(
JNIEnv* env, jobject src, jobject dest) {
if (apilevel > 23) {
replace_7_0(env, src, dest);
} else if (apilevel > 22) {
replace_6_0(env, src, dest);
} else if (apilevel > 21) {
replace_5_1(env, src, dest);
} else if (apilevel > 19) {
replace_5_0(env, src, dest);
}else{
replace_4_4(env, src, dest);
}
}
extern void __attribute__ ((visibility ("hidden"))) art_setFieldFlag(
JNIEnv* env, jobject field) {
if (apilevel > 23) {
setFieldFlag_7_0(env, field);
} else if (apilevel > 22) {
setFieldFlag_6_0(env, field);
} else if (apilevel > 21) {
setFieldFlag_5_1(env, field);
} else if (apilevel > 19) {
setFieldFlag_5_0(env, field);
}else{
setFieldFlag_4_4(env, field);
}
}
很显然,Andfix源码就是区分了各个版本,然后执行对应的操作,其中当apilevel <= 19
的情况,使用同一种方案处理,这是因为对应版本的虚拟机源码中还没有ArtMethod结构体
的概念(ArtMethod结构体是在art虚拟机之后推出的),所有Java方法在虚拟机中执行时候全部交给一个叫做_Z20dvmDecodeIndirectRefP6ThreadP8_jobject
的方法统一处理,我们先看下这种方式,Andfix是如何实现热更新的:
apilevel <= 19
其实在apilevel <= 19的情况下,Andfix采用的类似于Xposed的方式,在dalvik虚拟机
中,dalvik虚拟机的代码会打包成libdvm.so
动态库文件
我们在linux
环境下创建一个.c文件里面只包含一个函数如下:
//linux下使用gcc命令打包成so文件
//gcc -fPIC -shared Calculator.c -o libcalculator.so
//如果没有gcc环境,执行"yum install gcc"安装
int add(int a, int b ){
return a*b;
}
我们假设它就是libdvm.so
中的_Z20dvmDecodeIndirectRefP6ThreadP8_jobject
方法,所有Java方法都交由它处理,我们将其打包成.so文件,然后hook
住该方法
这里简单啰嗦一下hook
的原理,有兴趣的可以去了解一下Xposed
的实现原理~
假设我们上面打包生成的libcalculator.so
是Android系统的.so文件,我们可以写另一个.so文件去打开系统的.so文件去hook其中的方法,创建一个main.c文件,代码如下:
#include
#include
#include
#仅声明一个类型,与libcalculato.c中的中的add方法一致(参数,返回值都要一致),
typedef int (*ADD)(int,int);
int main(){
#打开libcalculator.so文件
void *handle=dlopen("./libcalculator.so" ,RTLD_LAZY);
ADD add=NULL;
#链接libcalculator.so中的add方法,dlopen和dlsym函数是系统的函数
*(void **)(&add)=dlsym(handle,"add");
int result=add(2,5);
printf("%d\n",result);
}
我们将main.c打包成可执行文件(gcc命令: gcc -rdynamic -o main main.c -ldl),然后./main
就可以看到执行结果,这就证明我们可以通过自己的so文件,打开系统的so文件,并且hook其中的方法,在方法前后都可以加自己的逻辑
Andfix源码中的做法也是如此,我们可以看到,Andfix加载libdvm.so
,然后判断执行下载的修复方法:
extern jboolean __attribute__ ((visibility ("hidden"))) dalvik_setup(
JNIEnv* env, int apilevel) {
void* dvm_hand = dlopen("libdvm.so", RTLD_NOW);
if (dvm_hand) {
dvmDecodeIndirectRef_fnPtr = dvm_dlsym(dvm_hand,
apilevel > 10 ?
"_Z20dvmDecodeIndirectRefP6ThreadP8_jobject" :
"dvmDecodeIndirectRef");
if (!dvmDecodeIndirectRef_fnPtr) {
return JNI_FALSE;
}
dvmThreadSelf_fnPtr = dvm_dlsym(dvm_hand,
apilevel > 10 ? "_Z13dvmThreadSelfv" : "dvmThreadSelf");
if (!dvmThreadSelf_fnPtr) {
return JNI_FALSE;
}
jclass clazz = env->FindClass("java/lang/reflect/Method");
jClassMethod = env->GetMethodID(clazz, "getDeclaringClass",
"()Ljava/lang/Class;");
return JNI_TRUE;
} else {
return JNI_FALSE;
}
}
Sophix简单分析
Sophix不是开源项目,是收费项目(5000用户以上),Sophix是对Andfix的优化升级,优雅的处理了Andfix的兼容问题
Sophix是非开源项目,所以我们只做简单分析,如上所述,Andfix在对ArtMethod结构体
操作的时候,由于Android源码每个版本都不同,可能会导致替换的时候发生偏移,或者内存溢出,所以Sophix采用的方式是动态计算ArtMethod
结构体的大小,手动对齐,保证对ArtMethod结构体中的成员替换时,不会发生内存的偏移或溢出,可以理解为:每次手动对齐时候使用的内存大小是根据不同手机动态计算的
换种方式来讲,可以理解为,Sophix构建了一个基础的ArtMethod结构体
,它的大小比Android源码中的ArtMethod结构更小,因为在内存中每个ArtMethod是存在数组中的,它的内存是连续
的,所以当我们将所有要替换的成员替换掉之后,相比源码中的ArtMethod来讲,Sophix的每个ArtMethod当前所占中的内存会更小,然后通过声明一些变量的方式占用这些内存,动态与Android源码中的内存对齐,很抽象,简单看张图:
因为Sophix代码不开源,能了解的信息大概都是在17年阿里云栖大会及之后的技术分享和文档,有兴趣的小伙伴可以自行度娘(关键词已提供)~