阿里系的底层替换虚拟机的方法指针实现热修复(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);
好了,通过虚拟机底层实现热修复过程分析完毕。如果大家有问题或建议,欢迎交流。