手写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); }
修复完成。
阅读全文
0 0
- 手写Andfix热修复(Dalvik篇)
- 手写Andfix热修复(Art篇)
- AndFix 热补丁修复
- AndFix 热补丁修复
- AndFix热修复实现
- AndFix热修复问题
- andfix 热补丁修复
- 阿里巴巴andfix热修复
- AndFix热修复笔记
- Android 热修复-AndFix
- AndFix热修复Demo
- Android热修复---AndFix
- AndFix热补丁修复
- AndFix热修复
- Android 热修复 AndFix
- Android 热修复AndFix
- 热修复 AndFix
- AndFix 热修复使用
- SVN安装与使用
- leetcode 284. Peeking Iterator
- GIT 的使用步骤笔记
- 算法面试100题——8.逻辑思维题
- Log4J 在系统运行时更改log4j的配置
- 手写Andfix热修复(Dalvik篇)
- Android学习笔记三十四之数据存储—SQLite数据库
- PHP正则表达式详解
- 【Linux】【驱动】ioctl介绍和应用场景
- 基础算法 :冒泡排序,选择排序。。。。
- 每日一记(2017/9/4 9:55)增加matlab的显示位数
- python基础-数学运算
- 剑指offer面试题20 顺时针打印矩阵
- 删除磁盘中某个EFI系统分区