AndFix热修复原理分析与手写实现

什么是AndFix?

AndFix是阿里推出的热修复框架,热修复是针对线上的出现的轻量级bug,在不进行版本更新的情况下进行修复

优点

无需更新版本,即时生效。更新体积小

缺点

版本和厂商手机不兼容问题

原理

由补丁类的classLoader加载补丁类,在native层针对不同Android架构中的不同的ArtMethod结构调用对应的replaceMethod方法按照定义好的ArtMethod结构一一替换方法的所有信息如所属类、访问权限、代码内存地址等。

手写实现

1.自定义注解,将类名和包名传给

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface MethodReplace {

    String className();

    String methodName();

2.将自己打好的补丁(dex文件),下载到本地,将补丁中的方法替换之前的方法

public class DexManager {

    private static final DexManager ourInstance = new DexManager();

    public static DexManager getInstance() {

        return ourInstance;

    }

    private DexManager() {

    }

    private Context mContext;

    /**

    * 首先要调用该方法

    *

    * @param context

    */

    public void setContext(Context context) {

        this.mContext = context;

    }

    /**

    * 加载dex文件

    *

    * @param file 修复好的补丁包(dex文件)

    */

    public void loadDex(File file) {

        try {

            DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),

                    new File(mContext.getCacheDir(), "opt").getAbsolutePath(),

                    Context.MODE_PRIVATE);

            Enumeration<String> entries = dexFile.entries();

            while (entries.hasMoreElements()) {

                //获取类名

                String className = entries.nextElement();

                //加载类

//                Class<?> aClass = Class.forName(className);//不适用!只能加载安装了的App中的class

                Class fixedClass = dexFile.loadClass(className, mContext.getClassLoader());

                if (null != fixedClass){

                    fixClass(fixedClass);

                }

            }

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

    /**

    * 修复class(里面的method)

    * 核心

    * @param fixedClass

    */

    private void fixClass(Class fixedClass) {

        //获取methods

        Method[] fixedMethods = fixedClass.getDeclaredMethods();

        for (Method fixMethod : fixedMethods) {

            //

            MethodReplace methodReplace = fixMethod.getAnnotation(MethodReplace.class);

            if (null == methodReplace){

                continue;

            }

            //找到注解了(要修复的方法)

            String className = methodReplace.className();

            String methodName = methodReplace.methodName();

            if (!TextUtils.isEmpty(className)&&!TextUtils.isEmpty(methodName)){

                try {

                    //获取带bug的方法所在的class

                    Class<?> bugClass = Class.forName(className);

                    //获取带bug的方法

                    Method bugMethod = bugClass.getDeclaredMethod(methodName,

                            fixMethod.getParameterTypes());

                    //fixMethod + bugMethod

                    //接下来替换对应ArtMethod结构体成员

                    replace(bugMethod, fixMethod);

                } catch (Exception e) {

                    e.printStackTrace();

                }

            }

        }

    }

    private native void replace(Method bugMethod,Method fixMethod);

    //不要忘记

    static {

        System.loadLibrary("native-lib");

    }

}

3.利用native层,将ArtMethod 结构调用对应的replaceMethod方法按照定义好的ArtMethod结构,替换方法的所有的内容

#include <jni.h>

#include <string>

#include "art_5_1.h"

/**

* 底层核心代码

* 替换ArtMethod结构体中的字段

* Method>ArtMethod>获取字段

*/

extern "C"

JNIEXPORT void JNICALL

Java_com_netease_andfix_DexManager_replace(JNIEnv *env, jobject thiz, jobject bug_method,

                                          jobject fix_method) {

    //获取带bug的Method的ArtMethod

    art::mirror::ArtMethod *bugArtMethod = reinterpret_cast<art::mirror::ArtMethod *>(env->FromReflectedMethod(

            bug_method));

    //获取修复好的Method的ArtMethod

    art::mirror::ArtMethod *fixArtMethod = reinterpret_cast<art::mirror::ArtMethod *>(env->FromReflectedMethod(

            fix_method));

    //size_t artMethodSize = aMethod-bMethod;

    //memcpy(bugArtMethod,fixArtMethod , artMethodSize);

    reinterpret_cast<art::mirror::Class*>(fixArtMethod->declaring_class_)->class_loader_ =

            reinterpret_cast<art::mirror::Class*>(bugArtMethod->declaring_class_)->class_loader_; //for plugin classloader

    reinterpret_cast<art::mirror::Class*>(fixArtMethod->declaring_class_)->clinit_thread_id_ =

            reinterpret_cast<art::mirror::Class*>(bugArtMethod->declaring_class_)->clinit_thread_id_;

    reinterpret_cast<art::mirror::Class*>(fixArtMethod->declaring_class_)->status_ = reinterpret_cast<art::mirror::Class*>(bugArtMethod->declaring_class_)->status_-1;

    //for reflection invoke

    reinterpret_cast<art::mirror::Class*>(fixArtMethod->declaring_class_)->super_class_ = 0;

    bugArtMethod->declaring_class_ = fixArtMethod->declaring_class_;

    bugArtMethod->dex_cache_resolved_types_ = fixArtMethod->dex_cache_resolved_types_;

    bugArtMethod->access_flags_ = fixArtMethod->access_flags_ | 0x0001;

    bugArtMethod->dex_cache_resolved_methods_ = fixArtMethod->dex_cache_resolved_methods_;

    bugArtMethod->dex_code_item_offset_ = fixArtMethod->dex_code_item_offset_;

    bugArtMethod->method_index_ = fixArtMethod->method_index_;

    bugArtMethod->dex_method_index_ = fixArtMethod->dex_method_index_;

    bugArtMethod->ptr_sized_fields_.entry_point_from_interpreter_ =

            fixArtMethod->ptr_sized_fields_.entry_point_from_interpreter_;

    bugArtMethod->ptr_sized_fields_.entry_point_from_jni_ =

            fixArtMethod->ptr_sized_fields_.entry_point_from_jni_;

    bugArtMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_ =

            fixArtMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_;

    //小米5:5.1系统,改了ArtMethod结构体字段, 增加了一个number字段

//    bugArtMethod->declaring_class_ = fixArtMethod->declaring_class_;

    ////  在内存上是下面:

    ////    (uint32_t*)(bugArtMethod + 0) = (uint32_t*)(fixArtMethod + 0);

    //    //小米5,第一个字段增加为 number后, 49行代码实际执行的操作是:

//    bugArtMethod->number = fixArtMethod->declaring_class_;

}

附:android 5.0jni源码

#include <string.h>

#include <jni.h>

#include <stdio.h>

#include <fcntl.h>

#include <dlfcn.h>

#include <stdint.h>    /* C99 */

namespace art {

    namespace mirror {

        class Object {

        public:

            // The number of vtable entries in java.lang.Object.

            static constexpr size_t kVTableLength = 11;

            static uint32_t hash_code_seed;

            uint32_t klass_;

            uint32_t monitor_;

        };

        class Class : public Object {

        public:

            // Interface method table size. Increasing this value reduces the chance of two interface methods

            // colliding in the interface method table but increases the size of classes that implement

            // (non-marker) interfaces.

            static constexpr size_t kImtSize = 64;    //IMT_SIZE;

            // defining class loader, or NULL for the "bootstrap" system loader

            uint32_t class_loader_;

            // For array classes, the component class object for instanceof/checkcast

            // (for String[][][], this will be String[][]). NULL for non-array classes.

            uint32_t component_type_;

            // DexCache of resolved constant pool entries (will be NULL for classes generated by the

            // runtime such as arrays and primitive classes).

            uint32_t dex_cache_;

            // Short cuts to dex_cache_ member for fast compiled code access.

            uint32_t dex_cache_strings_;

            // static, private, and <init> methods

            uint32_t direct_methods_;

            // instance fields

            //

            // These describe the layout of the contents of an Object.

            // Note that only the fields directly declared by this class are

            // listed in ifields; fields declared by a superclass are listed in

            // the superclass's Class.ifields.

            //

            // All instance fields that refer to objects are guaranteed to be at

            // the beginning of the field list.  num_reference_instance_fields_

            // specifies the number of reference fields.

            uint32_t ifields_;

            // The interface table (iftable_) contains pairs of a interface class and an array of the

            // interface methods. There is one pair per interface supported by this class.  That means one

            // pair for each interface we support directly, indirectly via superclass, or indirectly via a

            // superinterface.  This will be null if neither we nor our superclass implement any interfaces.

            //

            // Why we need this: given "class Foo implements Face", declare "Face faceObj = new Foo()".

            // Invoke faceObj.blah(), where "blah" is part of the Face interface.  We can't easily use a

            // single vtable.

            //

            // For every interface a concrete class implements, we create an array of the concrete vtable_

            // methods for the methods in the interface.

            uint32_t iftable_;

            // Descriptor for the class such as "java.lang.Class" or "[C". Lazily initialized by ComputeName

            uint32_t name_;

            // Static fields

            uint32_t sfields_;

            // The superclass, or NULL if this is java.lang.Object, an interface or primitive type.

            uint32_t super_class_;

            // If class verify fails, we must return same error on subsequent tries.

            uint32_t verify_error_class_;

            // Virtual methods defined in this class; invoked through vtable.

            uint32_t virtual_methods_;

            // Virtual method table (vtable), for use by "invoke-virtual".  The vtable from the superclass is

            // copied in, and virtual methods from our class either replace those from the super or are

            // appended. For abstract classes, methods may be created in the vtable that aren't in

            // virtual_ methods_ for miranda methods.

            uint32_t vtable_;

            // Access flags; low 16 bits are defined by VM spec.

            uint32_t access_flags_;

            // Total size of the Class instance; used when allocating storage on gc heap.

            // See also object_size_.

            uint32_t class_size_;

            // Tid used to check for recursive <clinit> invocation.

            pid_t clinit_thread_id_;

            // ClassDef index in dex file, -1 if no class definition such as an array.

            // TODO: really 16bits

            int32_t dex_class_def_idx_;

            // Type index in dex file.

            // TODO: really 16bits

            int32_t dex_type_idx_;

            // Number of instance fields that are object refs.

            uint32_t num_reference_instance_fields_;

            // Number of static fields that are object refs,

            uint32_t num_reference_static_fields_;

            // Total object size; used when allocating storage on gc heap.

            // (For interfaces and abstract classes this will be zero.)

            // See also class_size_.

            uint32_t object_size_;

            // Primitive type value, or Primitive::kPrimNot (0); set for generated primitive classes.

            uint32_t primitive_type_;

            // Bitmap of offsets of ifields.

            uint32_t reference_instance_offsets_;

            // Bitmap of offsets of sfields.

            uint32_t reference_static_offsets_;

            // State of class initialization.

            int32_t status_;

            // TODO: ?

            // initiating class loader list

            // NOTE: for classes with low serialNumber, these are unused, and the

            // values are kept in a table in gDvm.

            // InitiatingLoaderList initiating_loader_list_;

            // The following data exist in real class objects.

            // Embedded Imtable, for class object that's not an interface, fixed size.

            // ImTableEntry embedded_imtable_[0];

            // Embedded Vtable, for class object that's not an interface, variable size.

            // VTableEntry embedded_vtable_[0];

            // Static fields, variable size.

            // uint32_t fields_[0];

            // java.lang.Class

            static void *java_lang_Class_;

        };

        class ArtField : public Object {

        public:

            uint32_t declaring_class_;

            int32_t access_flags_;

            int32_t field_dex_idx_;

            int32_t offset_;

        };

        //art 可以采用 解释模式 或者 AOT模式执行。

        //解释模式就是取出dex code,逐条解释执行。这个时候回取这个方法的entry_point_from_interpreter_然后跳转执行。

        //AOT模式是”Ahead of time”,在安装时将dex code 优化成机器码,运行时直接执行机器码执行。调用这个方法时会调用entry_point_from_quick_compiled_code_,然后跳转执行。

        class ArtMethod : public Object {

        public:

//            uint32_t number;//小米5加的字段

            // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".

            // The class we are a part of.

            uint32_t declaring_class_;

            // Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.

            //这是一个存放ArtMethod*的指针数组,通过它可以获得ArtMethod所在dex所有Method对应的ArtMethod*

            uint32_t dex_cache_resolved_methods_;

            // Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.

            uint32_t dex_cache_resolved_types_;

            // Access flags; low 16 bits are defined by spec.

            //可以理解为该函数的标志位,如函数为public,private,static,native等。

            uint32_t access_flags_;

            /* Dex file fields. The defining dex file is available via declaring_class_->dex_cache_ */

            // Offset to the CodeItem.

            //里面指向code_item指针,code_item存储的实际是dex当中的字节码.其用处本来是适配dalvik解释器,

            //即无法编译成机器码的,用解释器来执行。

            //最终可得到code_item地址,里面存储方法指令。但需先获取dex头地址

            uint32_t dex_code_item_offset_;

            // Index into method_ids of the dex file associated with this method.

            //方法索引,主要作为寻址替换用

            uint32_t dex_method_index_;

            /* End of dex file fields. */

            // Entry within a dispatch table for this method. For static/direct methods the index is into

            // the declaringClass.directMethods, for virtual methods the vtable and for interface methods the

            // ifTable.

            uint32_t method_index_;

            // Fake padding field gets inserted here.

            // Must be the last fields in the method.

            struct PtrSizedFields {

                // Method dispatch from the interpreter invokes this pointer which may cause a bridge into

                // compiled code.

                //通过interpreter方式调用方法 解释执行入口。

                void *entry_point_from_interpreter_;

                // Pointer to JNI function registered to this method, or a function to resolve the JNI function.

                //用于存储jni方法信息。

                void *entry_point_from_jni_;

                // Method dispatch from quick compiled code invokes this pointer which may cause bridging into

                // portable compiled code or the interpreter.

                //这里就是常见的ART Hook方法为,替换方法的入口点,

                // 替换后的入口点,会重新准备栈和寄存器,执行hook的方法。

                // 注意,这里指向的是汇编代码,运行的是已经预处理过的机器码。

                void *entry_point_from_quick_compiled_code_;

            } ptr_sized_fields_;

            static void *java_lang_reflect_ArtMethod_;

        };

    }

}


cmakeList

# For more information about using CMake with Android Studio, read the

# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC

# or SHARED, and provides the relative paths to its source code.

# You can define multiple libraries, and CMake builds them for you.

# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.

        native-lib

        # Sets the library as a shared library.

        SHARED

        # Provides a relative path to your source file(s).

        native-lib.cpp)

# Searches for a specified prebuilt library and stores the path as a

# variable. Because CMake includes system libraries in the search path by

# default, you only need to specify the name of the public NDK library

# you want to add. CMake verifies that the library exists before

# completing its build.

find_library( # Sets the name of the path variable.

        log-lib

        # Specifies the name of the NDK library that

        # you want CMake to locate.

        log)

# Specifies libraries CMake should link to your target library. You

# can link multiple libraries, such as libraries you define in this

# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.

        native-lib

        # Links the target library to the log library

        # included in the NDK.

        ${log-lib})

示例

public class MainActivity extends AppCompatActivity {

    private Caculator caculator;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        caculator = new Caculator();

    }

    public void caculator(View view) {

        caculator.caculator(this);

    }

    public void fix(View view) {

        DexManager.getInstance().setContext(this);

        DexManager.getInstance().loadDex(

                new File(Environment.getExternalStorageDirectory(), "out.dex"));

    }

}

总结

AndFix  目前官方只支持到7.0。轻量级,性能损耗低,不许冷启动就能生效。而且由于厂商会修改ArtMethod,所以只支持原生系统,如果商用的话不支持选择AndFix

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343