从AndFix源码看Android热修复

前几篇文章介绍过注入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);
好了,通过虚拟机底层实现热修复过程分析完毕。如果大家有问题或建议,欢迎交流。

发布了63 篇原创文章 · 获赞 179 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/u011686167/article/details/79319227