美文网首页
热修复三:Native底层替换实现热修复

热修复三:Native底层替换实现热修复

作者: ChiangCMBA | 来源:发表于2018-09-30 17:23 被阅读0次

使用到的知识点:

  • 1 注解
  • 2 类加载机制
  • 3 反射
  • 4 so插件化原理
  • 5 native层本地方法替换

原理:找到出现异常的方法,然后,在native层将已修复的方法(fixedMethod)替换出现异常的方法(wrongMethod)。

1.如何找到出现异常的方法?

首先,已修复的方法,是由服务器下发下来的。通过自定义注解,在已修复的方法中添加了要修复的错误方法的信息。其中包括错误方法所在的类信息,错误方法的方法名。
接下来需要通过DexFile 加载,已修复的dex文件。加载成功以后,dexFile.entries()遍历DexFile中的entries得到dex中的类的类名。
再通过dexFile.loadClass加载类。类加载成功以后,再通过反射遍历类中的方法,得到Replace注解信息,Replace注解中就包含了,异常方法wrongMethod的类和方法名信息。

自定义注解Replace.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Replace {
    String clazz();
    String method();
}
从服务器下发下来的,已修复的方法,具体在已修复的dex文件中。
public class Calculator {
    @Replace(clazz = "com.fmtech.fmphix.Calculator", method = "calculate")
    public int calculate(){
        int i = 100;
        int j = 10;
        return i/j;
    }
}

2.native层如何替换?

在dalvik虚拟机和art虚拟机中是两种不同的替换方案。
(1).在dalvik虚拟机中
需要将异常方法部分属性替换为已修复的方法的属性。这种方法太依赖于底层Method的数据结构。
(2).在art虚拟机中
直接将异常方法整体替换为已修复的方法即可(即ArtMethod内存拷贝整体替换)。为什么可以这样做呢?
因为在Art虚拟机中,一个类中的方法在底层相应的内存分布是连续的。所以,只要知道wrongMethod的地址,和对应ArtMethod的结构体大小size就可以直接进行内存拷贝,以达到热修复的目的。

jclass jclazz = env->FindClass("com/fmtech/fmphix/NativeStructsModel");
    size_t methodF1Id = (size_t)env->GetStaticMethodID(jclazz, "f1", "()V");
    size_t methodF2Id = (size_t)env->GetStaticMethodID(jclazz, "f2", "()V");

    int size = methodF2Id - methodF1Id;
    memcpy(wrongMethod, rightMethod, size);

==================

Java层代码DexManager

public class DexManager {
    private static DexManager sInstance = new DexManager();
    private Context mContext;

    public static DexManager getInstance(){
        return sInstance;
    }

    public void setContext(Context context){
        mContext = context;
    }

    public void loadDexFile(File file){
        if(null == file || !file.exists()){
            return;
        }

        try {
            DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(), new File(mContext.getCacheDir(), "opt").getAbsolutePath(), Context.MODE_PRIVATE);
            if(null == dexFile){
                return;
            }
            Enumeration<String> entries = dexFile.entries();
            while(entries.hasMoreElements()){
                String className = entries.nextElement();
//                Class clazz = Class.forName(className);//Only available for loaded class;
                Class clazz = dexFile.loadClass(className, mContext.getClassLoader());
                if(null != clazz){
                    fixClazz(clazz);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void fixClazz(Class fixedClazz){
        if(null == fixedClazz){
            return;
        }
        Method[] methods = fixedClazz.getDeclaredMethods();
        for(Method rightMethod:methods){
            Replace replace = rightMethod.getAnnotation(Replace.class);
            if(null == replace){
                continue;
            }
            String wrongClassName = replace.clazz();
            String wrongMethodName = replace.method();
            try {
                Class clazz = Class.forName(wrongClassName);
                //Find the wrong method
                Method wrongMethod = clazz.getDeclaredMethod(wrongMethodName, rightMethod.getParameterTypes());
                if(Build.VERSION.SDK_INT <= 18){
                    replace(Build.VERSION.SDK_INT, wrongMethod, rightMethod);
                }else{
                    replaceArt(Build.VERSION.SDK_INT, wrongMethod, rightMethod);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
    }

    public native void replace(int sdkVersionCode, Method wrongMethod, Method rightMethod);
    public native void replaceArt(int sdkVersionCode, Method wrongMethod, Method rightMethod);

}

Native层代码实现

#include <jni.h>
#include <string>
#include "dalvik.h"
#include "art_7_0.h"
//#include "art_method.h"//For Art 6.0

typedef Object *(*DvmDecodeIndirectRef)(void *thread, jobject jobj);

typedef void *(*DvmThreadSelf)();

DvmDecodeIndirectRef dvmDecodeIndirectRef;
DvmThreadSelf dvmThreadSelf;

extern "C" {

JNIEXPORT void JNICALL
Java_com_fmtech_fmphix_DexManager_replace(JNIEnv *env, jobject instance, jint sdkVersionCode,
                                          jobject wrongMethodObj, jobject rightMethodObj) {
    //找到Java方法在Native对应的Method结构体
    Method *wrongMethod = (Method *) env->FromReflectedMethod(wrongMethodObj);
    Method *rightMethod = (Method *) env->FromReflectedMethod(rightMethodObj);

    /*接下来操作的主要目的:修改rightMethod第一个成员变量ClassObject的status为CLASS_INITIALIZED*/
    /**
     * 通过函数dlopen打开指定的动态链接库,得到句柄后,再通过dlsym函数获取动态库中的函数地址,
     * 获得函数地址后进行调用
     */
    void *dvm_handle = dlopen("libdvm.so", RTLD_NOW);

    dvmDecodeIndirectRef = (DvmDecodeIndirectRef) dlsym(dvm_handle, sdkVersionCode > 10
                                                                    ? "_Z20dvmDecodeIndirectRefP6ThreadP8_jobject"
                                                                    :
                                                                    "dvmDecodeIndirectRef");

    dvmThreadSelf = (DvmThreadSelf) dlsym(dvm_handle, sdkVersionCode > 10 ? "_Z13dvmThreadSelfv"
                                                                          : "dvmThreadSelf");

    jclass methodClazz = env->FindClass("java/lang/reflect/Method");
    jmethodID rightMethodId = env->GetMethodID(methodClazz, "getDeclaringClass",
                                               "()Ljava/lang/Class;");

    jobject obj = env->CallObjectMethod(rightMethodObj, rightMethodId);

    ClassObject *clazz = (ClassObject *) dvmDecodeIndirectRef(dvmThreadSelf(), obj);
    clazz->status = CLASS_INITIALIZED;

    wrongMethod->accessFlags |= ACC_PUBLIC;
    wrongMethod->methodIndex = rightMethod->methodIndex;
    wrongMethod->jniArgInfo = rightMethod->jniArgInfo;
    wrongMethod->registersSize = rightMethod->registersSize;
    wrongMethod->outsSize = rightMethod->outsSize;

    //方法参数原型
    wrongMethod->prototype = rightMethod->prototype;
    wrongMethod->insns = rightMethod->insns;
    wrongMethod->nativeFunc = rightMethod->nativeFunc;

    jthrowable exc = env->ExceptionOccurred();
    if (exc) {
        /* We don't do much with the exception, except thatwe print a debug message for it, clear it, andthrow a new exception. */
        jclass newExcCls;
        env->ExceptionDescribe();
        env->ExceptionClear();
        newExcCls = env->FindClass("java/lang/IllegalArgumentException");
        if (newExcCls == NULL) {
            /* Unable to find the exception class, give up. */
            return;
        }
        env->ThrowNew(newExcCls, "thrown from C code");
    }

}

JNIEXPORT void JNICALL
Java_com_fmtech_fmphix_DexManager_replaceArt(JNIEnv *env, jobject instance, jint sdkVersionCode,
                                             jobject wrongMethodObj, jobject rightMethodObj) {
    art::mirror::ArtMethod *wrongMethod = (art::mirror::ArtMethod *) env->FromReflectedMethod(
            wrongMethodObj);
    art::mirror::ArtMethod *rightMethod = (art::mirror::ArtMethod *) env->FromReflectedMethod(
            rightMethodObj);

    /*方式一、ArtMethod内存拷贝整体替换*/
    jclass jclazz = env->FindClass("com/fmtech/fmphix/NativeStructsModel");
    size_t methodF1Id = (size_t)env->GetStaticMethodID(jclazz, "f1", "()V");
    size_t methodF2Id = (size_t)env->GetStaticMethodID(jclazz, "f2", "()V");

    int size = methodF2Id - methodF1Id;
    memcpy(wrongMethod, rightMethod, size);

//    /*方式二、ArtMethod属性替换*/
//    //方法所在类(即可以将一个类的方法指向另一个类的方法)
//    wrongMethod->declaring_class_ = rightMethod->declaring_class_;
//
//    /****Art 6.0 Start*****/
//    //快捷访问方式
////    wrongMethod->dex_cache_resolved_methods_ = rightMethod->dex_cache_resolved_methods_;
//    //
////    wrongMethod->dex_cache_resolved_types_ = rightMethod->dex_cache_resolved_types_;
//    /****Art 6.0 End*****/
//
//    //索引偏移量
//    wrongMethod->dex_code_item_offset_ = rightMethod->dex_code_item_offset_;
//    //方法索引
//    wrongMethod->method_index_ = rightMethod->method_index_;
//    //
//    wrongMethod->dex_method_index_ = rightMethod->dex_method_index_;
//
//    /****Art 6.0 Start*****/
//    //ArtMethod方法入口
////    wrongMethod->ptr_sized_fields_.entry_point_from_interpreter_ = rightMethod->ptr_sized_fields_.entry_point_from_interpreter_;
//    /****Art 6.0 End*****/
//
//    wrongMethod->ptr_sized_fields_.entry_point_from_jni_ = rightMethod->ptr_sized_fields_.entry_point_from_jni_;
//
//    //Art机器模式的入口
//    wrongMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_ = rightMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_;
//
//    /****Art 7.0 Start*****/
//    wrongMethod->ptr_sized_fields_.entry_point_from_jni_ = rightMethod->ptr_sized_fields_.entry_point_from_jni_;
//    wrongMethod->ptr_sized_fields_.dex_cache_resolved_types_ = rightMethod->ptr_sized_fields_.dex_cache_resolved_types_;
//    wrongMethod->ptr_sized_fields_.dex_cache_resolved_methods_ = rightMethod->ptr_sized_fields_.dex_cache_resolved_methods_;
//    wrongMethod->hotness_count_ = rightMethod->hotness_count_;
//    /****Art 7.0 End*****/

}

}

说明:关于android-4.0.4_r1\dalvik\vm\Jni.cpp#dvmDecodeIndirectRef()函数;
通过dlopen动态链接so,调用dvmDecodeIndirect的依据是:DVM的源码位于dalvik/目录下,其中dalvik/vm目录下的内容是DVM的具体实现部分,它会被编译成libdvm.so;dalvik/libdex会被编译成libdex.a静态库,作为dex工具使用。

/*
 * Convert an indirect reference to an Object reference.  The indirect
 * reference may be local, global, or weak-global.
 *
 * If "jobj" is NULL, or is a weak global reference whose reference has
 * been cleared, this returns NULL.  If jobj is an invalid indirect
 * reference, kInvalidIndirectRefObject is returned.
 *
 * Note "env" may be NULL when decoding global references.
 */
Object* dvmDecodeIndirectRef(Thread* self, jobject jobj) {
      ......
}

【相关源码】FMPhix

相关文章

网友评论

      本文标题:热修复三:Native底层替换实现热修复

      本文链接:https://www.haomeiwen.com/subject/vtnwoftx.html