天天看点

Alibaba-Dexposed Bug框架原理及源码解析

Alibaba的AndFix热修复:

Alibaba-AndFix Bug热修复框架的使用

Alibaba-AndFix Bug热修复框架原理及源码解析

上一篇中已经介绍了Alibaba-Dexposed框架在线热补丁修复的使用 ,这篇主要是了解框架的原理和源码解析。

Alibaba-Dexposed Bug框架原理及源码解析

原理:

在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 !=  && VERSION.SDK_INT != ) {
                if(VERSION.SDK_INT > ) {
                    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[];
                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", );
            }
        });
           

第二:

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 !=  && parameterTypesAndCallback[parameterTypesAndCallback.length - ] instanceof XC_MethodHook) {
            XC_MethodHook callback = (XC_MethodHook)parameterTypesAndCallback[parameterTypesAndCallback.length - ];
            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 == ?XposedHelpers.getIntField(hookMethod, "slot"):;
                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;
    }
}
           

当虚拟机调用到这个存在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[]; // [0] is the return type.
    Object* thisObject = NULL;
    size_t srcIndex = ;
    size_t dstIndex = ;

    // for non-static methods determine the "this" pointer
    if (!dvmIsStaticMethod(original)) {
        thisObject = (Object*) args[];
        srcIndex++;
    }

    ArrayObject* argsArray = dvmAllocArrayByClass(objectArrayClass, strlen(method->shorty) - , 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 += ;
            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 == ) {
            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 = ;
        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 - ;
        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 >= );

        // 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(, 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, );
      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方法实现调度机制来达到目的。