前几篇文章介绍过注入Dex实现热修复:http://blog.csdn.net/u011686167/article/details/78966936 。现在探讨阿里系的底层替换虚拟机的方法指针实现热修复。Android系统中存在两种虚拟机:dalvik和art。5.0版本以前是dalvik,而5.0版本后官方建议使用art。
dalvik虚拟机引用的头文件结构:
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".
*/
int vtableCount;
struct Method** vtable;
} ClassObject;
typedef struct Method {
struct ClassObject *clazz;
u4 accessFlags;
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).
*/
DexProto prototype;
/* short-form method descriptor string */
const char* shorty;
/*
* The remaining items are not used for abstract or native methods.
*/
/* 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.
*/
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.
*/
bool noRef;
} Method;
art虚拟机引用的头文件:
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:
enum Status {
kStatusRetired = -2, // Retired, should not be used. Use the newly cloned one instead.
kStatusError = -1,
kStatusNotReady = 0,
kStatusIdx = 1, // Loaded, DEX idx in super_class_type_idx_ and interfaces_type_idx_.
kStatusLoaded = 2, // DEX idx values resolved.
kStatusResolving = 3, // Just cloned from temporary class object.
kStatusResolved = 4, // Part of linking.
kStatusVerifying = 5, // In the process of being verified.
kStatusRetryVerificationAtRuntime = 6, // Compile time verification failed, retry at runtime.
kStatusVerifyingAtRuntime = 7, // Retrying verification at runtime.
kStatusVerified = 8, // Logically part of linking; done pre-init.
kStatusInitializing = 9, // Class init in progress.
kStatusInitialized = 10, // Ready to go.
kStatusMax = 11,
};
uint32_t annotation_type_;
// Defining class loader, or null for the "bootstrap" system loader.
uint32_t class_loader_;
// For array classes, the component class object for instanceof/checkcast
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_;
// 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_;
// The superclass, or null if this is java.lang.Object or a primitive type.
uint32_t super_class_;
// If class verify fails, we must return same error on subsequent tries.
uint32_t verify_error_;
// Virtual method table (vtable), for use by "invoke-virtual".
uint32_t vtable_;
// Access flags; low 16 bits are defined by VM spec.
uint32_t access_flags_;
// Short cuts to dex_cache_ member for fast compiled code access.
uint64_t dex_cache_strings_;
// ArtFields are allocated as a length prefixed ArtField array, and not an array of pointers to
// ArtFields.
uint64_t ifields_;
uint64_t methods_;
// Static fields length-prefixed array.
uint64_t sfields_;
// Class flags to help speed up visiting object references.
uint32_t class_flags_;
// Total size of the Class instance; used when allocating storage on gc heap.
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.
int32_t dex_class_def_idx_;
// Type index in dex file.
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.
uint32_t object_size_;
// The lower 16 bits contains a Primitive::Type value. The upper 16
// bits contains the size shift of the primitive type.
uint32_t primitive_type_;
// Bitmap of offsets of ifields.
uint32_t reference_instance_offsets_;
// State of class initialization.
Status status_;
// The offset of the first virtual method that is copied from an interface.
uint16_t copied_methods_offset_;
// The offset of the first declared virtual methods in the methods_ array.
uint16_t virtual_methods_offset_;
static uint32_t java_lang_Class_;
};
class ArtField {
public:
uint32_t declaring_class_;
uint32_t access_flags_;
uint32_t field_dex_idx_;
uint32_t offset_;
};
class ArtMethod {
public:
// Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
uint32_t declaring_class_;
// Access flags; low 16 bits are defined by spec.
uint32_t access_flags_;
/* Dex file fields. The defining dex file is available via declaring_class_->dex_cache_ */
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.
uint16_t method_index_;
// The hotness we measure for this method. Incremented by the interpreter.
uint16_t hotness_count_;
// Fake padding field gets inserted here.
struct PtrSizedFields {
// Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
ArtMethod** dex_cache_resolved_methods_;
// Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
void* dex_cache_resolved_types_;
// Pointer to JNI function registered to this method, or a function to resolve the JNI function,
// or the profiling data for non-native methods, or an ImtConflictTable.
void* entry_point_from_jni_;
// Method dispatch from quick compiled code invokes this pointer which may cause bridging into
// the interpreter.
void* entry_point_from_quick_compiled_code_;
} ptr_sized_fields_;
};
}
}
dalvik初始化,加载dvm动态库:
JNIEXPORT jboolean JNICALL
Java_com_frank_fix_MainActivity_dalvikSetup(
JNIEnv* env, int apilevel) {
//加载dvm动态库
void* dvm_hand = dlopen("libdvm.so", RTLD_NOW);
if (dvm_hand) {
dvmDecodeIndirectRef_fnPtr = (dvmDecodeIndirectRef_func) dvm_dlsym(dvm_hand,
apilevel > 10 ?
"_Z20dvmDecodeIndirectRefP6ThreadP8_jobject" :
"dvmDecodeIndirectRef");
if (!dvmDecodeIndirectRef_fnPtr) {
return JNI_FALSE;
}
dvmThreadSelf_fnPtr = (dvmThreadSelf_func) dvm_dlsym(dvm_hand,
apilevel > 10 ? "_Z13dvmThreadSelfv" : "dvmThreadSelf");
if (!dvmThreadSelf_fnPtr) {
return JNI_FALSE;
}
jclass clazz = env->FindClass("java/lang/reflect/Method");
jClassMethod = env->GetMethodID(clazz, "getDeclaringClass",
"()Ljava/lang/Class;");
return JNI_TRUE;
} else {
return JNI_FALSE;
}
}
dalvik的方法替换,实现热修复:
JNIEXPORT void JNICALL
Java_com_frank_fix_MainActivity_dalvikReplaceMethod(
JNIEnv* env, jobject src, jobject dest) {
//调用替换后的method
jobject clazz = env->CallObjectMethod(dest, jClassMethod);
ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr(
dvmThreadSelf_fnPtr(), clazz);
//class的状态设置为已经初始化
clz->status = CLASS_INITIALIZED;
//分别得到原method和待替换的method
Method* meth = (Method*) env->FromReflectedMethod(src);
Method* target = (Method*) env->FromReflectedMethod(dest);
//替换access flag
meth->accessFlags |= ACC_PUBLIC;
meth->methodIndex = target->methodIndex;
meth->jniArgInfo = target->jniArgInfo;
meth->registersSize = target->registersSize;
meth->outsSize = target->outsSize;
meth->insSize = target->insSize;
//替换方法的prototype
meth->prototype = target->prototype;
meth->insns = target->insns;
meth->nativeFunc = target->nativeFunc;
}
dalvik的field flag替换:
JNIEXPORT void JNICALL
Java_com_frank_fix_MainActivity_dalvikSetFieldFlag(JNIEnv* env, jobject field) {
Field* dalvikField = (Field*) env->FromReflectedField(field);
dalvikField->accessFlags = dalvikField->accessFlags & (~ACC_PRIVATE)
| ACC_PUBLIC;
}
art的方法替换,实现热修复:
JNIEXPORT void JNICALL
Java_com_frank_fix_MainActivity_artFix(
JNIEnv *env, jobject jclazz, jobject src, jobject dest) {
//分别得到原method和待替换的method
art::mirror::ArtMethod* smeth = (art::mirror::ArtMethod*) env->FromReflectedMethod(src);
art::mirror::ArtMethod* dmeth = (art::mirror::ArtMethod*) env->FromReflectedMethod(dest);
reinterpret_cast<art::mirror::Class*>(dmeth->declaring_class_)->clinit_thread_id_ =
reinterpret_cast<art::mirror::Class*>(smeth->declaring_class_)->clinit_thread_id_;
reinterpret_cast<art::mirror::Class*>(dmeth->declaring_class_)->super_class_ = 0;
//替换class
smeth->declaring_class_ = dmeth->declaring_class_;
//替换access flag
smeth->access_flags_ = dmeth->access_flags_ | 0x0001;
smeth->dex_code_item_offset_ = dmeth->dex_code_item_offset_;
smeth->dex_method_index_ = dmeth->dex_method_index_;
//替换method
smeth->method_index_ = dmeth->method_index_;
smeth->hotness_count_ = dmeth->hotness_count_;
smeth->ptr_sized_fields_.dex_cache_resolved_methods_ =
dmeth->ptr_sized_fields_.dex_cache_resolved_methods_;
smeth->ptr_sized_fields_.dex_cache_resolved_types_ =
dmeth->ptr_sized_fields_.dex_cache_resolved_types_;
smeth->ptr_sized_fields_.entry_point_from_jni_ =
dmeth->ptr_sized_fields_.entry_point_from_jni_;
smeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_ =
dmeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_;
}
art替换field flag:
JNIEXPORT void JNICALL
Java_com_frank_fix_MainActivity_setFieldFlag(JNIEnv* env, jobject field) {
art::mirror::ArtField* artField =
(art::mirror::ArtField*) env->FromReflectedField(field);
artField->access_flags_ = artField->access_flags_ & (~0x0002) | 0x0001;
}
提供一个测试类,一个是存在bug的方法,一个是修复后的方法:
/**
* 模拟有bug的方法
* @return "hello"
*/
public String sayHello(){
Log.i("Test", "hello");
return "hello";
}
/**
* 修复bug后的方法
* @return "goodbye"
*/
public String sayGoodbye(){
Log.i("Test", "goodbye");
return "goodbye";
}
以art虚拟机热修复为例:
private void doFix(){
try {
Class<?> clazz = Class.forName("com.frank.fix.Test");
Object instance = clazz.newInstance();
Method helloMethod = clazz.getDeclaredMethod("sayHello");
helloMethod.setAccessible(true);
//调用存在bug的方法
helloMethod.invoke(instance);
Method goodbyeMethod = clazz.getDeclaredMethod("sayGoodbye");
goodbyeMethod.setAccessible(true);
goodbyeMethod.invoke(instance);
//调用art虚拟机替换方法,实现热修复
artFix(helloMethod, goodbyeMethod);
//调用修复后的方法,查看结果
helloMethod.invoke(instance);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
public native void artFix(Method src, Method method);
好了,通过虚拟机底层实现热修复过程分析完毕。如果大家有问题或建议,欢迎交流。