Android_Andfix兼容和Sophix简单分析

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手机上进行替换,显而易见会出现问题,如图:

method_index_

要注意的一点是:无论我们写的是空方法,还是方法中实现了很多逻辑(写了很多代码),在虚拟机中生成的ArtMethod结构体大小都是一致的!

所以Andfix必须进行版本适配,替换方式与版本一一对应

所以Andfix的兼容实现就是对每个版本的Android系统,创建一个头文件,将Android源码中的对ArtMethod结构体声明的头文件对copy进来就可以了,仅将对ArtMethod结构体声明的部分copy就可以,也就是抄Android中的源码,事实上Andfix中就是这么做的

我们可以看下Andfix源码中的兼容
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.jpg

因为Sophix代码不开源,能了解的信息大概都是在17年阿里云栖大会及之后的技术分享和文档,有兴趣的小伙伴可以自行度娘(关键词已提供)~

你可能感兴趣的:(Android_Andfix兼容和Sophix简单分析)