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