欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

阿里系的底层替换虚拟机的方法指针实现热修复(Android)

程序员文章站 2022-04-27 10:19:13
前几篇文章介绍过注入Dex实现热修复。现在探讨阿里系的底层替换虚拟机的方法指针实现热修复。Android系统中存在两种虚拟机:dalvik和art。5.0版本以前是dalvik,而...

前几篇文章介绍过注入Dex实现热修复。现在探讨阿里系的底层替换虚拟机的方法指针实现热修复。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  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  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. "" 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  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(dmeth->declaring_class_)->clinit_thread_id_ =
            reinterpret_cast(smeth->declaring_class_)->clinit_thread_id_;
    reinterpret_cast(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);

好了,通过虚拟机底层实现热修复过程分析完毕。如果大家有问题或建议,欢迎交流。