美文网首页
AndFix热修复原理分析与手写实现

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

作者: 佼佼者Mr | 来源:发表于2020-04-30 19:26 被阅读0次

    什么是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

    相关文章

      网友评论

          本文标题:AndFix热修复原理分析与手写实现

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