Alibaba-Dexposed Bug框架原理及源码解析
来源:互联网 发布:事业单位选调考试软件 编辑:程序博客网 时间:2024/06/05 20:18
Alibaba的AndFix热修复:
Alibaba-AndFix Bug热修复框架的使用
Alibaba-AndFix Bug热修复框架原理及源码解析
上一篇中已经介绍了Alibaba-Dexposed框架在线热补丁修复的使用 ,这篇主要是了解框架的原理和源码解析。
原理:
在Dalvik虚拟机下,主要是通过改变一个方法对象方法在Dalvik虚拟机中的定义来实现,具体做法就是将该方法的类型改变为Native并且将这个方法的实现链接到一个通用的Native Dispatch方法上。这个 Dispatch方法通过JNI回调到Java端的一个统一处理方法,最后在统一处理方法中调用before, after函数来实现AOP。在Art虚拟机上目前也是通过改变一个 ArtMethod的入口函数来实现。
在宿主项目的Application需要调用以下方法来判断手机是否支持Dexposed框架:
DexposedBridge.canDexposed(this);
canDexposed方法源码:
public static synchronized boolean canDexposed(Context context) { return !DeviceCheck.isDeviceSupport(context)?false:loadDexposedLib(context); }
可以看到,第一判断了机型是否支持,如果支持就加载lib文件。
DeviceCheck.isDeviceSupport()源码:
public static synchronized boolean isDeviceSupport(Context context) { boolean var2; try { if(!isCheckedDeviceSupport) { if(isDalvikMode() && isSupportSDKVersion() && !isX86CPU() && !isYunOS()) { isDeviceSupportable = true; return isDeviceSupportable; } isDeviceSupportable = false; return isDeviceSupportable; } var2 = isDeviceSupportable; } finally { Log.d("hotpatch", "device support is " + isDeviceSupportable + "checked" + isCheckedDeviceSupport); isCheckedDeviceSupport = true; } return var2; }
判断机型,主要判断的有是否是Dalvik虚拟机、sdk版本、是否是x86cpu架构、是否是YunOS系统。
loadDexposedLib加载lib的源码:
private static boolean loadDexposedLib(Context context) { try { if(VERSION.SDK_INT != 10 && VERSION.SDK_INT != 9) { if(VERSION.SDK_INT > 19) { System.loadLibrary("dexposed_l"); } else { System.loadLibrary("dexposed"); } } else { System.loadLibrary("dexposed2.3"); } return true; } catch (Throwable var2) { return false; } }
根据sdk的不同版本加载不同的so文件。
以上仅是判断当然机型是否支持Dexposed框架的运行环境。
接下,就是对Dexposed的使用原理进行源码分析:
在上一篇提到,当加载补丁文件时,会扫描补丁文件中实现IPatch接口的所有的类。
IPatch定义如下:
public interface IPatch { void handlePatch(PatchParam var1) throws Throwable;}
就是说,修复bug的处理只能在handlePatch方法中实现。
官网也只提供了2种实现方式:
第一:
// Target class, method with parameter types, followed by the hook callback (XC_MethodHook). DexposedBridge.findAndHookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodHook() { // To be invoked before Activity.onCreate(). @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { // "thisObject" keeps the reference to the instance of target class. Activity instance = (Activity) param.thisObject; // The array args include all the parameters. Bundle bundle = (Bundle) param.args[0]; Intent intent = new Intent(); // XposedHelpers provide useful utility methods. XposedHelpers.setObjectField(param.thisObject, "mIntent", intent); // Calling setResult() will bypass the original method body use the result as method return value directly. if (bundle.containsKey("return")) param.setResult(null); } // To be invoked after Activity.onCreate() @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { XposedHelpers.callMethod(param.thisObject, "sampleMethod", 2); } });
第二:
DexposedBridge.findAndHookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { // Re-writing the method logic outside the original method context is a bit tricky but still viable. ... } });
调用的接口是相同的,只不过传递的回调接口不同。
第一种是在方法前后执行做一些处理,第二种就是直接把方法进行替换。
在这里,我们就重点看findAndHookMethod方法,跟着此方法追踪源码:
public static Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) { if(parameterTypesAndCallback.length != 0 && parameterTypesAndCallback[parameterTypesAndCallback.length - 1] instanceof XC_MethodHook) { XC_MethodHook callback = (XC_MethodHook)parameterTypesAndCallback[parameterTypesAndCallback.length - 1]; Method m = XposedHelpers.findMethodExact(clazz, methodName, parameterTypesAndCallback);//根据Java的反射机制获取到Method对象 Unhook unhook = hookMethod(m, callback);//见下方代码分析 if(!(callback instanceof XC_MethodKeepHook) && !(callback instanceof XC_MethodKeepReplacement)) { ArrayList var6 = allUnhookCallbacks; synchronized(allUnhookCallbacks) { allUnhookCallbacks.add(unhook); } } return unhook; } else { throw new IllegalArgumentException("no callback defined"); } }
hookMethod方法源码:
public static Unhook hookMethod(Member hookMethod, XC_MethodHook callback) { if(!(hookMethod instanceof Method) && !(hookMethod instanceof Constructor)) { throw new IllegalArgumentException("only methods and constructors can be hooked"); } else { boolean newMethod = false; Map declaringClass = hookedMethodCallbacks; DexposedBridge.CopyOnWriteSortedSet callbacks; synchronized(hookedMethodCallbacks) { callbacks = (DexposedBridge.CopyOnWriteSortedSet)hookedMethodCallbacks.get(hookMethod); //如果没有修复此方法,就创建一个回调接口的集合 if(callbacks == null) { callbacks = new DexposedBridge.CopyOnWriteSortedSet(); hookedMethodCallbacks.put(hookMethod, callbacks); newMethod = true; } } callbacks.add(callback); if(newMethod) {//如果是新方法,获取方法的参数列表和返回值 Class declaringClass1 = hookMethod.getDeclaringClass(); int slot = runtime == 1?XposedHelpers.getIntField(hookMethod, "slot"):0; Class[] parameterTypes; Class returnType; if(hookMethod instanceof Method) { parameterTypes = ((Method)hookMethod).getParameterTypes(); returnType = ((Method)hookMethod).getReturnType(); } else { parameterTypes = ((Constructor)hookMethod).getParameterTypes(); returnType = null; } DexposedBridge.AdditionalHookInfo additionalInfo = new DexposedBridge.AdditionalHookInfo(callbacks, parameterTypes, returnType, (DexposedBridge.AdditionalHookInfo)null); //调用Native方法,接口在下方 hookMethodNative(hookMethod, declaringClass1, slot, additionalInfo); } callback.getClass(); return new Unhook(callback, hookMethod);//返回一个Unhook实例对象 } }
hookMethodNative Native方法生命:
private static synchronized native void hookMethodNative(Member var0, Class<?> var1, int var2, Object var3);
Dalvik虚拟机的Native方法实现:
hookMethodNative Native层的代码实现:
static void com_taobao_android_dexposed_DexposedBridge_hookMethodNative(JNIEnv* env, jclass clazz, jobject reflectedMethodIndirect, jobject declaredClassIndirect, jint slot, jobject additionalInfoIndirect) {s // Usage errors? if (declaredClassIndirect == NULL || reflectedMethodIndirect == NULL) { dvmThrowIllegalArgumentException("method and declaredClass must not be null"); return; } // Find the internal representation of the method ClassObject* declaredClass = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), declaredClassIndirect); Method* method = dvmSlotToMethod(declaredClass, slot);//把Java的Method映射为Native Method if (method == NULL) { dvmThrowNoSuchMethodError("could not get internal representation for method"); return; } if (dexposedIsHooked(method)) {//判断此方法是否已经被hook(钩) // already hooked return; } // Save a copy of the original method and other hook info DexposedHookInfo* hookInfo = (DexposedHookInfo*) calloc(1, sizeof(DexposedHookInfo));//新申请一块内存 //备份method对象到hookInfo中 memcpy(hookInfo, method, sizeof(hookInfo->originalMethodStruct)); hookInfo->reflectedMethod = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(reflectedMethodIndirect));//把方法的实现指向native方法的实现,指针替换 hookInfo->additionalInfo = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(additionalInfoIndirect)); // Replace method with our own code SET_METHOD_FLAG(method, ACC_NATIVE);//把method对象方法属性设置成native方法 method->insns = (const u2*) hookInfo;//把备份的method数据挂在这里传递数据 method->registersSize = method->insSize; method->outsSize = 0; if (PTR_gDvmJit != NULL) { // reset JIT cache MEMBER_VAL(PTR_gDvmJit, DvmJitGlobals, codeCacheFull) = true; }}
method->nativeFunc = &dexposedCallHandler;//链接到Native方法的实现
当虚拟机调用到这个存在bug的方法时就会调用这个Native方法:
dexposedCallHandler方法源码:
static void dexposedCallHandler(const u4* args, JValue* pResult, const Method* method, ::Thread* self) { if (!dexposedIsHooked(method)) { dvmThrowNoSuchMethodError("could not find Dexposed original method - how did you even get here?"); return; } DexposedHookInfo* hookInfo = (DexposedHookInfo*) method->insns; Method* original = (Method*) hookInfo; Object* originalReflected = hookInfo->reflectedMethod; Object* additionalInfo = hookInfo->additionalInfo; // convert/box arguments const char* desc = &method->shorty[1]; // [0] is the return type. Object* thisObject = NULL; size_t srcIndex = 0; size_t dstIndex = 0; // for non-static methods determine the "this" pointer if (!dvmIsStaticMethod(original)) { thisObject = (Object*) args[0]; srcIndex++; } ArrayObject* argsArray = dvmAllocArrayByClass(objectArrayClass, strlen(method->shorty) - 1, ALLOC_DEFAULT); if (argsArray == NULL) { return; } while (*desc != '\0') { char descChar = *(desc++); JValue value; Object* obj; switch (descChar) { case 'Z': case 'C': case 'F': case 'B': case 'S': case 'I': value.i = args[srcIndex++]; obj = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar)); dvmReleaseTrackedAlloc(obj, self); break; case 'D': case 'J': value.j = dvmGetArgLong(args, srcIndex); srcIndex += 2; obj = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar)); dvmReleaseTrackedAlloc(obj, self); break; case '[': case 'L': obj = (Object*) args[srcIndex++]; break; default: ALOGE("Unknown method signature description character: %c\n", descChar); obj = NULL; srcIndex++; } dexposedSetObjectArrayElement(argsArray, dstIndex++, obj); } // call the Java handler function JValue result; //调用了Java层的方法 dvmCallMethod(self, dexposedHandleHookedMethod, NULL, &result, originalReflected, (int) original, additionalInfo, thisObject, argsArray); dvmReleaseTrackedAlloc((Object *)argsArray, self); // exceptions are thrown to the caller if (dvmCheckException(self)) { return; } // return result with proper type ClassObject* returnType = dvmGetBoxedReturnType(method); if (returnType->primitiveType == PRIM_VOID) { // ignored } else if (result.l == NULL) { if (dvmIsPrimitiveClass(returnType)) { dvmThrowNullPointerException("null result when primitive expected"); } pResult->l = NULL; } else { if (!dvmUnboxPrimitive((Object *)result.l, returnType, pResult)) { dvmThrowClassCastException(((Object *)result.l)->clazz, returnType); } }}
这个方法主要就是调用Java的方法,实现调度。
调用的Java的方法是:
private static Object handleHookedMethod(Member method, int originalMethodId, Object additionalInfoObj, Object thisObject, Object[] args) throws Throwable { AdditionalHookInfo additionalInfo = (AdditionalHookInfo) additionalInfoObj; Object[] callbacksSnapshot = additionalInfo.callbacks.getSnapshot(); final int callbacksLength = callbacksSnapshot.length; if (callbacksLength == 0) { try { return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes, additionalInfo.returnType, thisObject, args); } catch (InvocationTargetException e) { throw e.getCause(); } } MethodHookParam param = new MethodHookParam(); param.method = method; param.thisObject = thisObject; param.args = args; // call "before method" callbacks int beforeIdx = 0; do { try { ((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param); } catch (Throwable t) { log(t); // reset result (ignoring what the unexpectedly exiting callback did) param.setResult(null); param.returnEarly = false; continue; } if (param.returnEarly) { // skip remaining "before" callbacks and corresponding "after" callbacks beforeIdx++; break; } } while (++beforeIdx < callbacksLength); // call original method if not requested otherwise if (!param.returnEarly) { try { param.setResult(invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args)); } catch (InvocationTargetException e) { param.setThrowable(e.getCause()); } } // call "after method" callbacks int afterIdx = beforeIdx - 1; do { Object lastResult = param.getResult(); Throwable lastThrowable = param.getThrowable(); try { ((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param); } catch (Throwable t) { DexposedBridge.log(t); // reset to last result (ignoring what the unexpectedly exiting callback did) if (lastThrowable == null) param.setResult(lastResult); else param.setThrowable(lastThrowable); } } while (--afterIdx >= 0); // return if (param.hasThrowable()) throw param.getThrowable(); else return param.getResult(); }
这个方法里面了实现了调度机制,调用回调接口的方法和备份的Java方法。
本质上仍然是寻找被挂钩函数的 Method 结构体,将Method属性改为native ,然后对其成员 nativeFunc,
registersize 等进行赋值,其中 insns 成员保存了挂钩的详细信息。所有被挂钩的函数,其nativeFunc都赋值为 dexposedCallHandler 函数,该函数最终执行 XposedBridge.class 里的 handleHookedMethod 。 handleHookedMethod 寻找dexposed模块及dexposed框架调用 findAndHookMethod 注册的 before,after
函数,如果有,就执行,再通过invokeOriginalMethodNative 执行挂钩前函数。
MethodHookParam.thisObject:这个类的一个实例
MethodHookParam.args:用于传递被注入函数的所有参数
MethodHookParam.setResult:用于修改原函数调用的结果,如果在beforeHookedMethod回调函数中调用setResult,可以阻止对原函数的调用。但是如果有返回值的话仍然需要通过hook处理器进行return操作。
Art虚拟机的Native方法实现
static void com_taobao_android_dexposed_DexposedBridge_hookMethodNative( JNIEnv* env, jclass, jobject java_method, jobject, jint, jobject additional_info) { ScopedObjectAccess soa(env); art::Thread* self = art::Thread::Current(); jobject javaArtMethod = env->GetObjectField(java_method, WellKnownClasses::java_lang_reflect_AbstractMethod_artMethod); ArtMethod* method = soa.Decode<mirror::ArtMethod*>(javaArtMethod); LOG(INFO) << "dexposed: >>> hookMethodNative " << method << " " << PrettyMethod(method); EnableXposedHook(env, method, additional_info); }
EnableXposedHook:
static void EnableXposedHook(JNIEnv* env, ArtMethod* art_method, jobject additional_info) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { LOG(INFO) << "dexposed: >>> EnableXposedHook" << art_method << " " << PrettyMethod(art_method); if (dexposedIsHooked(art_method)) { // Already hooked return; }// else if (UNLIKELY(art_method->IsXposedOriginalMethod())) {// // This should never happen// ThrowIllegalArgumentException(nullptr, StringPrintf("Cannot hook the method backup: %s", PrettyMethod(art_method).c_str()).c_str());// return;// } ScopedObjectAccess soa(env); // Create a backup of the ArtMethod object ArtMethod* backup_method = down_cast<ArtMethod*>(art_method->Clone(soa.Self())); // Set private flag to avoid virtual table lookups during invocation backup_method->SetAccessFlags(backup_method->GetAccessFlags() /*| kAccXposedOriginalMethod*/); // Create a Method/Constructor object for the backup ArtMethod object jobject reflect_method; if (art_method->IsConstructor()) { reflect_method = env->AllocObject(WellKnownClasses::java_lang_reflect_Constructor); } else { reflect_method = env->AllocObject(WellKnownClasses::java_lang_reflect_Method); } env->SetObjectField(reflect_method, WellKnownClasses::java_lang_reflect_AbstractMethod_artMethod, env->NewGlobalRef(soa.AddLocalReference<jobject>(backup_method))); // Save extra information in a separate structure, stored instead of the native method DexposedHookInfo* hookInfo = reinterpret_cast<DexposedHookInfo*>(calloc(1, sizeof(DexposedHookInfo))); hookInfo->reflectedMethod = env->NewGlobalRef(reflect_method); hookInfo->additionalInfo = env->NewGlobalRef(additional_info); hookInfo->originalMethod = backup_method; jstring shorty = (jstring)env->GetObjectField(additional_info,additionalhookinfo_shorty_field); hookInfo->shorty = env->GetStringUTFChars(shorty, 0); LOG(INFO) << "dexposed: >>> EnableXposedHook shorty:" << hookInfo->shorty;#if PLATFORM_SDK_VERSION < 22 art_method->SetNativeMethod(reinterpret_cast<uint8_t *>(hookInfo));#else art_method->SetEntryPointFromJni(reinterpret_cast<void *>(hookInfo));#endif art_method->SetEntryPointFromQuickCompiledCode(GetQuickDexposedInvokeHandler());// art_method->SetEntryPointFromInterpreter(art::artInterpreterToCompiledCodeBridge); // Adjust access flags art_method->SetAccessFlags((art_method->GetAccessFlags() & ~kAccNative) /*| kAccXposedHookedMethod*/); }
通过:
art_method->SetAccessFlags((art_method->GetAccessFlags()&~kAccNative)/| kAccXposedHookedMethod/);
art_method->SetEntryPointFromQuickCompiledCode(GetQuickDexposedInvokeHandler());
同样也是把把实现指向native方法实现调度机制来达到目的。
- Alibaba-Dexposed Bug框架原理及源码解析
- Alibaba-Dexposed Bug框架原理及源码解析
- Alibaba-Dexposed Bug框架原理及源码解析
- Alibaba-AndFix Bug热修复框架原理及源码解析
- Alibaba-AndFix Bug热修复框架原理及源码解析
- alibaba dexposed初步解析
- AndFix Bug热修复框架原理及源码解析
- AndFix Bug热修复框架原理及源码解析
- Alibaba-Dexposed框架在线热补丁修复的使用
- Alibaba-Dexposed框架在线热补丁修复的使用
- Alibaba-Dexposed框架在线热补丁修复的使用
- alibaba dexposed使用须知
- Andfix热修复框架原理及源码解析-上篇
- Andfix热修复框架原理及源码解析-下篇
- Android-AndFix 热修复框架原理及源码解析
- Andfix热修复框架原理及源码解析-上篇
- Andfix热修复框架原理及源码解析-下篇
- Android中免Root实现Hook的Dexposed框架实现原理解析以及如何实现应用的热修复
- Linux socket 学习
- 解析XML几种方式
- CentOS 6.3 手动rpm包安装gcc、g++
- ReactNativeiOS(二)读书记录 6UI组件
- 两种方式实现页面数据绑定
- Alibaba-Dexposed Bug框架原理及源码解析
- Django学习记录之Django 1.8 教程(我只是官网的搬运工)Tutorial Part 2
- 第14周项目1-(1)验证折半查找算法
- Android WebView Cookie的相关设置和自定义错误页面加载
- 备用自动更新列表
- 通过Filter解决跨域问题,可以跨多个域,域可以通过@Value注解取
- 2.4信道复用技术
- mysql存储过程动态执行sql
- 追梦人