您的位置:首页 > 其它

手写Andfix热修复(Dalvik篇)

2017-09-04 11:39 253 查看

基本流程

将修复好的方法加上一个自定义注解

将所有修复好的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 JNICALL
Java_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);
}


修复完成。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: