手写Andfix热修复(Dalvik篇)

来源:互联网 发布:删除数据恢复软件 编辑:程序博客网 时间:2024/05/16 18:11

基本流程

  • 将修复好的方法加上一个自定义注解
  • 将所有修复好的class打包成dex
  • 从dex中取出修复好的method
  • 将class中出错的method的引用指向已修复的method

Andfix热修复的优点在于性能高,并且不需要重启APP,但是要注意,一点退出APP(结束dalvik)就需要重新修复。

开始撸码

创建一个新的项目,需要引入c++。创建一个错误的方法

package com.example.chauncey.ndk_lsn15_andfix;/** * Created by 45216 on 2017/9/2. */class Calculator {    int calculate() {        int a = 10;        int b = 0;        return a / b;    }}

创建一个自定义注解类

package com.example.chauncey.ndk_lsn15_andfix;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by 45216 on 2017/9/2. */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Replace {    String clazz();    String method();}

创建一个修复好的method,类名方法名一致

package com.example.chauncey.ndk_lsn15_andfix.Web;import com.example.chauncey.ndk_lsn15_andfix.Replace;/** * Created by 45216 on 2017/9/2. */class Calculator {//此处的参数分别是待修复的类和方法    @Replace(clazz = "com.example.chauncey.ndk_lsn15_andfix.Calculator", method = "calculate")    int calculate() {        int a = 10;        int b = 1;        return a / b;    }}

创建一个DexManager工具类
项目结构

build一下项目,提取已经编译好的class文件

将整个com文件夹提取出来,在这里我是放到桌面上的dx文件夹中,将除了Web文件夹以外的class文件全部删除,我们只需要将修复好的class打包成dex就行了。

使用Android sdk\build-tools\版本号\下的dx进行打包(我是偷懒,要不然放到系统环境变量里会好一些),以管理员身份运行cmd

E:\Android\android-sdk\build-tools\26.0.1>dx --dex --output C:\Users\45216\Desktop\dx\out.dex C:\Users\45216\Desktop\dx\

以上命令行中

  • –dex 打包成dex
  • –output 输出到指定位置

把dex文件放到设备上(模拟从服务器上下载),我放到了当前程序的cache文件夹下。

接下来是DexManger

public class DexManager {    private static final DexManager ourInstance = new DexManager();    //创建文件时勾选了单例模式,此处懒得改了    public void setContext(Context context) {        mContext = context;    }    private Context mContext;    public static DexManager getInstance() {        return ourInstance;    }    private DexManager() {    }    public void loadFile(File file) {        try {            if(!file.exists()){                return;            }            //专门用来处理Dex文件的类,在dalvik.system包下,毕竟Android4.4开始就已经使用了art虚拟机,所以这个类已经过时了            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 clazz = dexFile.loadClass(className, mContext.getClassLoader());                if (clazz != null) {                    fixClass(clazz);                }            }        } catch (IOException e) {            e.printStackTrace();        }    }    private void fixClass(Class clazz) {         //获取所有类中的所有方法        Method[] methods = clazz.getDeclaredMethods();        for (Method rightMethod : methods) {            Replace replace = rightMethod.getAnnotation(Replace.class);            //只需要带有Replace注解的方法            if (replace == null) {                continue;            }            String wClass = replace.clazz();            String wMethod = replace.method();            try {                //Class.forName只能获取已存在的类,上面的dexFile.loadClass则是获取不存在于当前系统中的类                Class wrongClass = Class.forName(wClass);                Method wrongMethod = wrongClass.getDeclaredMethod(wMethod, rightMethod.getParameterTypes());                replaceMethod(Build.VERSION.SDK_INT, wrongMethod, rightMethod);            } catch (Exception e) {                e.printStackTrace();            }        }    }    private native void replaceMethod(int sdkVersion, Method wrongMethod, Method rightMethod);}

在c++中进行方法的替换

首先要引入dalvik.h,但是引入时发现dalvik.h又引入了很多其它的头文件,所以在这里自己创建一个dalvik.h,因为Android4.4以下版本都是dalvik虚拟机,所以具体的实现已经集成好了。

以下是dalvik.h的具体内容

#include <string.h>#include <jni.h>#include <stdio.h>#include <fcntl.h>#include <dlfcn.h>#include <stdint.h>    /* C99 */typedef uint8_t u1;typedef uint16_t u2;typedef uint32_t u4;typedef uint64_t u8;typedef int8_t s1;typedef int16_t s2;typedef int32_t s4;typedef int64_t s8;/* * access flags and masks; the "standard" ones are all <= 0x4000 * * Note: There are related declarations in vm/oo/Object.h in the ClassFlags * enum. */enum {    ACC_PUBLIC = 0x00000001,       // class, field, method, ic    ACC_PRIVATE = 0x00000002,       // field, method, ic    ACC_PROTECTED = 0x00000004,       // field, method, ic    ACC_STATIC = 0x00000008,       // field, method, ic    ACC_FINAL = 0x00000010,       // class, field, method, ic    ACC_SYNCHRONIZED = 0x00000020,       // method (only allowed on natives)    ACC_SUPER = 0x00000020,       // class (not used in Dalvik)    ACC_VOLATILE = 0x00000040,       // field    ACC_BRIDGE = 0x00000040,       // method (1.5)    ACC_TRANSIENT = 0x00000080,       // field    ACC_VARARGS = 0x00000080,       // method (1.5)    ACC_NATIVE = 0x00000100,       // method    ACC_INTERFACE = 0x00000200,       // class, ic    ACC_ABSTRACT = 0x00000400,       // class, method, ic    ACC_STRICT = 0x00000800,       // method    ACC_SYNTHETIC = 0x00001000,       // field, method, ic    ACC_ANNOTATION = 0x00002000,       // class, ic (1.5)    ACC_ENUM = 0x00004000,       // class, field, ic (1.5)    ACC_CONSTRUCTOR = 0x00010000,       // method (Dalvik only)    ACC_DECLARED_SYNCHRONIZED = 0x00020000,       // method (Dalvik only)    ACC_CLASS_MASK = (ACC_PUBLIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT            | ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM),    ACC_INNER_CLASS_MASK = (ACC_CLASS_MASK | ACC_PRIVATE | ACC_PROTECTED            | ACC_STATIC),    ACC_FIELD_MASK = (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC            | ACC_FINAL | ACC_VOLATILE | ACC_TRANSIENT | ACC_SYNTHETIC            | ACC_ENUM),    ACC_METHOD_MASK = (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC            | ACC_FINAL | ACC_SYNCHRONIZED | ACC_BRIDGE | ACC_VARARGS            | ACC_NATIVE | ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC            | ACC_CONSTRUCTOR | ACC_DECLARED_SYNCHRONIZED),};typedef struct DexProto {    u4* dexFile; /* file the idx refers to */    u4 protoIdx; /* index into proto_ids table of dexFile */} DexProto;typedef void (*DalvikBridgeFunc)(const u4* args, void* pResult,        const void* method, void* self);struct Field {    void* clazz; /* class in which the field is declared */    const char* name;    const char* signature; /* e.g. "I", "[C", "Landroid/os/Debug;" */    u4 accessFlags;};struct Method;struct ClassObject;typedef struct Object {    /* ptr to class object */    struct ClassObject* clazz;    /*     * 类的加载过程     * A word containing either a "thin" lock or a "fat" monitor.  See     * the comments in Sync.c for a description of its layout.     */    u4 lock;} Object;struct InitiatingLoaderList {    /* a list of initiating loader Objects; grown and initialized on demand */    void** initiatingLoaders;    /* count of loaders in the above list */    int initiatingLoaderCount;};enum PrimitiveType {    PRIM_NOT = 0, /* value is a reference type, not a primitive type */    PRIM_VOID = 1,    PRIM_BOOLEAN = 2,    PRIM_BYTE = 3,    PRIM_SHORT = 4,    PRIM_CHAR = 5,    PRIM_INT = 6,    PRIM_LONG = 7,    PRIM_FLOAT = 8,    PRIM_DOUBLE = 9,}typedef PrimitiveType;enum ClassStatus {    CLASS_ERROR = -1,    CLASS_NOTREADY = 0, CLASS_IDX = 1, /* loaded, DEX idx in super or ifaces */    CLASS_LOADED = 2, /* DEX idx values resolved */    CLASS_RESOLVED = 3, /* part of linking */    CLASS_VERIFYING = 4, /* in the process of being verified */    CLASS_VERIFIED = 5, /* logically part of linking; done pre-init */    CLASS_INITIALIZING = 6, /* class init in progress */    CLASS_INITIALIZED = 7, /* ready to go */}typedef ClassStatus;typedef struct ClassObject {    struct Object o; // emulate C++ inheritance, Collin    /* leave space for instance data; we could access fields directly if we     freeze the definition of java/lang/Class */    u4 instanceData[4];    /* UTF-8 descriptor for the class; from constant pool, or on heap     if generated ("[C") */    const char* descriptor;    char* descriptorAlloc;    /* access flags; low 16 bits are defined by VM spec */    u4 accessFlags;    /* VM-unique class serial number, nonzero, set very early */    u4 serialNumber;    /* DexFile from which we came; needed to resolve constant pool entries */    /* (will be NULL for VM-generated, e.g. arrays and primitive classes) */    void* pDvmDex;    /* state of class initialization */    ClassStatus status;    /* if class verify fails, we must return same error on subsequent tries */    struct ClassObject* verifyErrorClass;    /* threadId, used to check for recursive <clinit> invocation */    u4 initThreadId;    /*     * Total object size; used when allocating storage on gc heap.  (For     * interfaces and abstract classes this will be zero.)     */    size_t objectSize;    /* arrays only: class object for base element, for instanceof/checkcast     (for String[][][], this will be String) */    struct ClassObject* elementClass;    /* arrays only: number of dimensions, e.g. int[][] is 2 */    int arrayDim;    PrimitiveType primitiveType;    /* superclass, or NULL if this is java.lang.Object */    struct ClassObject* super;    /* defining class loader, or NULL for the "bootstrap" system loader */    struct Object* classLoader;    struct InitiatingLoaderList initiatingLoaderList;    /* array of interfaces this class implements directly */    int interfaceCount;    struct ClassObject** interfaces;    /* static, private, and <init> methods */    int directMethodCount;    struct Method* directMethods;    /* virtual methods defined in this class; invoked through vtable */    int virtualMethodCount;    struct Method* virtualMethods;    /*     * 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.     */    int vtableCount;    struct Method** vtable;} ClassObject;typedef struct Method {    struct ClassObject *clazz;    u4 accessFlags;//u2 methodIndex   方法表里面的索引    u2 methodIndex;    u2 registersSize; /* ins + locals */    u2 outsSize;    u2 insSize;    /* method name, e.g. "<init>" or "eatLunch" */    const char* name;    /*     * Method prototype descriptor string (return and argument types).     *     * TODO: This currently must specify the DexFile as well as the proto_ids     * index, because generated Proxy classes don't have a DexFile.  We can     * remove the DexFile* and reduce the size of this struct if we generate     * a DEX for proxies.     */    DexProto prototype;    /* short-form method descriptor string */    const char* shorty;    /*     * The remaining items are not used for abstract or native methods.     * (JNI is currently hijacking "insns" as a function pointer, set     * after the first call.  For internal-native this stays null.)     */    /* the actual code */    u2* insns;    /* cached JNI argument and return-type hints */    int jniArgInfo;    /*     * Native method ptr; could be actual function or a JNI bridge.  We     * don't currently discriminate between DalvikBridgeFunc and     * DalvikNativeFunc; the former takes an argument superset (i.e. two     * extra args) which will be ignored.  If necessary we can use     * insns==NULL to detect JNI bridge vs. internal native.     */    DalvikBridgeFunc nativeFunc;#ifdef WITH_PROFILER    bool inProfile;#endif#ifdef WITH_DEBUGGER    short debugBreakpointCount;#endif    bool fastJni;    /*     * JNI: true if this method has no reference arguments. This lets the JNI     * bridge avoid scanning the shorty for direct pointers that need to be     * converted to local references.     *     * TODO: replace this with a list of indexes of the reference arguments.     */    bool noRef;} Method;

接下来是replace方法的具体实现

#include <jni.h>#include "dalvik.h"typedef Object *(*FindObject)(void *thread, jobject obj);typedef void *(*FindThread)();FindObject findObject;FindThread findThread;extern "C" {JNIEXPORT void JNICALLJava_com_example_chauncey_ndk_1lsn15_1andfix_DexManager_replaceMethod(JNIEnv *env,                                                                 jobject instance,                                                                jint sdk,                                                                jobject wrongMethod,                                                               jobject rightMethod) {    //将传入的方法转换层JNI的Method结构体    Method *wrong = (Method *) env->FromReflectedMethod(wrongMethod);    Method *right = (Method *) env->FromReflectedMethod(rightMethod);    //下一步  把right 对应Object   第一个成员变量ClassObject   status    //获取dalvik句柄    void *dvm_handle = dlopen("libdvm.so", RTLD_NOW);    //sdk 10前后需要不同的名字,具体原因不明。注意:这里编译器会报错,要求传三个参数,其实只要两个,不必理会    findObject = (FindObject) dlsym(dvm_hand, sdk > 10 ?                         "_Z20dvmDecodeIndirectRefP6ThreadP8_jobject":                                           "dvmDecodeIndirectRef");    findThread = (FindThread) dlsym(dvm_handle , sdk > 10 ? "_Z13dvmThreadSelfv" :     "dvmThreadSelf");    //获取 java中的Method类    jclass methodClazz = env->FindClass("java/lang/reflect/Method");    jmethodID rightMethodId = env->GetMethodID(methodClazz, "getDeclaringClass",                                               "()Ljava/lang/Class;");    jobject ndkObject = env->CallObjectMethod(rightMethod, rightMethodId);    //上面所有的操作都是为了下面做铺垫,只有将status 赋值为CLASS_INITIALIZED时才表示类已经加载完毕,其中的方法才可以调用    firstFiled->status = CLASS_INITIALIZED;    //将错误方法的引用全部替换成正确的方法    wrong->accessFlags |= ACC_PUBLIC;    wrong->methodIndex = right->methodIndex;    wrong->jniArgInfo = right->jniArgInfo;    wrong->registersSize = right->registersSize;    wrong->outsSize = right->outsSize;    wrong->prototype = right->prototype;    wrong->insns = right->insns;    wrong->nativeFunc = right->nativeFunc;}}


最后只需要

 File file = new File(this.getCacheDir(), "out.dex");        if (file.exists()) {            DexManager.getInstance().loadFile(file);            }

修复完成。