在上一篇我們學習了如何在AS中創建Xposed模塊,本篇來分析下官方教程中redClock的實現原理。本系列文章基於version-51
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { if(!lpparam.packageName.equals("com.android.systemui")) return; XposedBridge.log("we are in systemui !"); findAndHookMethod("com.android.systemui.statusbar.policy.Clock", lpparam.classLoader, "updateClock", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { super.beforeHookedMethod(param); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { TextView tv = (TextView) param.thisObject; String text = tv.getText().toString(); tv.setText(text + ")"); tv.setTextColor(Color.RED); } }); }
上面的代碼可以將原先在狀態欄的時鍾文本顏色變成紅色,且在后面加")"。看下圖:
主要的實現代碼在findAndHookMethod函數中,查看函數定義:
findAndHookMethod:
——>findMethodExact(clazz,methodName,paramterClasses);
——>XposedBridge.hookMethod(method,callback);
先看findMethodExact,
代碼很簡單就是要得到methodName在android中對應的函數對象,根據findAndHookMethod的參數得到字符串sb(格式參考注釋行),用sb在methodCache這個hashMap查找有沒有對應的method;若沒有則根據methodName和parameterTypes利用getDeclaredMethod得到對應的method。
public static Method findMethodExact(Class<?> clazz, String methodName, Class... parameterTypes) { StringBuilder sb = new StringBuilder(clazz.getName()); sb.append('#'); sb.append(methodName); sb.append(getParametersString(parameterTypes)); sb.append("#exact"); // sb = com.android.systemui.statusbar.policy.Clock#updateClock(參數1,參數2)#exact String fullMethodName = sb.toString(); Method e; //methodCache鍵值對存放fullMethodName,method對象 if(methodCache.containsKey(fullMethodName)) { e = (Method)methodCache.get(fullMethodName); if(e == null) { throw new NoSuchMethodError(fullMethodName); } else { return e; } } else { try { e = clazz.getDeclaredMethod(methodName, parameterTypes); e.setAccessible(true); methodCache.put(fullMethodName, e); return e; } catch (NoSuchMethodException var6) { methodCache.put(fullMethodName, (Object)null); throw new NoSuchMethodError(fullMethodName); } } }
看來重點在XposedBridge.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: " + hookMethod.toString()); } else if(hookMethod.getDeclaringClass().isInterface()) { throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString()); } else if(Modifier.isAbstract(hookMethod.getModifiers())) { throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString()); } else { //上面代碼分步檢查hookMethod的類型 //else中的代碼得到hookMethod對應的鍵值對 boolean newMethod = false; Map declaringClass = sHookedMethodCallbacks; XposedBridge.CopyOnWriteSortedSet callbacks; synchronized(sHookedMethodCallbacks) { callbacks = (XposedBridge.CopyOnWriteSortedSet)sHookedMethodCallbacks.get(hookMethod); if(callbacks == null) { callbacks = new XposedBridge.CopyOnWriteSortedSet(); sHookedMethodCallbacks.put(hookMethod, callbacks); newMethod = true; } } callbacks.add(callback); //替換hookMehod的callbacks為callback,其實callback是存放的是hookMethod所有的callback,看定義:</span></span> final XposedBridge.CopyOnWriteSortedSet<XC_MethodHook> callbacks; //以上代碼就是為hookMethod建立對應的callback list //sHookedMethodCallbacks存放hookMethod和callback if(newMethod) { Class declaringClass1 = hookMethod.getDeclaringClass(); int slot = 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; } //以上代碼得到method的參數和返回值,在AdditionalHookInfo下使用 //把callback、method參數、method返回值匯總在AdditionalHookInfo類下 XposedBridge.AdditionalHookInfo additionalInfo = new XposedBridge.AdditionalHookInfo(callbacks, parameterTypes, returnType, (XposedBridge.AdditionalHookInfo)null); //本地函數在libxposed_dalvik.cpp hookMethodNative(hookMethod, declaringClass1, slot, additionalInfo); } callback.getClass(); return new Unhook(callback, hookMethod); //為callback綁定hookMthod } }
上面亂七八糟的走了這么多,登記了2個hashmap{(fullMethodName,method對象),(hookmethod,callback)}。看來java層只是管理這些結構並沒有實質性的操作,進入native代碼----Xposed.cpp:
//參數:reflectedMethodIndirect==>hookmethod,declaredClassIndirect==>hookmethod所在的類 // slot==>slot,additionalInfoIndirect==>結構體包含callback、parameterTypes、returnType void XposedBridge_hookMethodNative(JNIEnv* env, jclass clazz, jobject reflectedMethodIndirect, jobject declaredClassIndirect, jint slot, jobject additionalInfoIndirect) { // Usage errors? if (declaredClassIndirect == NULL || reflectedMethodIndirect == NULL) { dvmThrowIllegalArgumentException("method and declaredClass must not be null"); return; } // Find the internal representation of the method //獲得dalvik中的classObject對象 ClassObject* declaredClass = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), declaredClassIndirect); //獲得dalvik中的Method,被dalvik執行的函數體不同於java層的Method,位於Object.h Method* method = dvmSlotToMethod(declaredClass, slot); if (method == NULL) { dvmThrowNoSuchMethodError("Could not get internal representation for method"); return; } //若method已被hook則直接返回 if (isMethodHooked(method)) { // already hooked return; } // Save a copy of the original method and other hook info XposedHookInfo* hookInfo = (XposedHookInfo*) calloc(1, sizeof(XposedHookInfo)); memcpy(hookInfo, method, sizeof(hookInfo->originalMethodStruct)); hookInfo->reflectedMethod = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(reflectedMethodIndirect)); hookInfo->additionalInfo = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(additionalInfoIndirect)); // Replace method with our own code 將method替換成我們自己的代碼 //設置method->accessFlags = ACC_NATIVE;表示method為native代碼 //下面幾行代碼都是為這行代碼作補充 //For a native method, we compute the size of the argument list, //and set "insSize" and "registerSize" equal to it. SET_METHOD_FLAG(method, ACC_NATIVE); //給method添加callback函數表示已被hooked method->nativeFunc = &hookedMethodCallback; //原本的insns中存放的是dex指令,現變為hookinfo為hookedMethodCallback准備 method->insns = (const u2*) hookInfo; method->registersSize = method->insSize; method->outsSize = 0; if (PTR_gDvmJit != NULL) { // reset JIT cache char currentValue = *((char*)PTR_gDvmJit + MEMBER_OFFSET_VAR(DvmJitGlobals,codeCacheFull)); if (currentValue == 0 || currentValue == 1) { MEMBER_VAL(PTR_gDvmJit, DvmJitGlobals, codeCacheFull) = true; } else { ALOGE("Unexpected current value for codeCacheFull: %d", currentValue);
代碼主要的就是紅色標注的,它將Method標為native code。dalvik虛擬機在執行Method時,則會直接調用其成員變量hookedMethodCallback執行。注意,這個時候已經改變了原本的Method的執行步驟了(Xposed在此刻覺醒啦啦啦)。看下面dalvik代碼,/dalvik/vm/interp/Stack.c
void dvmCallMethodV(Thread* self, const Method* method, Object* obj, bool fromJni, JValue* pResult, va_list args) { ...... if (dvmIsNativeMethod(method)) { TRACE_METHOD_ENTER(self, method); /* * Because we leave no space for local variables, "curFrame" points * directly at the method arguments. */ (*method->nativeFunc)(self->curFrame, pResult, method, self); TRACE_METHOD_EXIT(self, method); } else {</span> //這里是在Inter.cpp中直接解析method dvmInterpret(self, method, pResult); } ...... }
這一路走來感覺有點偏離主線類,回看下主題,在執行完 findAndHookMethod("com.android.systemui.statusbar.policy.Clock", lpparam.classLoader,"updateClock", new XC_MethodHook() );后。updateClock(hookmethod)的accessflag = ACC_NATIVE,dalvik在執行updateClock方法時發現其為native code,則執行nativeFunc 函數體即hookedMethodCallback。ok,找到"元凶"了,繼續看代碼:
/* This is called when a hooked method is executed. */ void hookedMethodCallback(const u4* args, JValue* pResult, const Method* method, ::Thread* self) { ...... //call the Java handler function</span> JValue result; dvmCallMethod(self, xposedHandleHookedMethod, NULL, &result, originalReflected, (int) original, additionalInfo, thisObject, argsArray); ...... }
hookedMethodCallback函數中主要是調用dvmCallMethod去執行xposedHandleHookedMethod,而xposedHandleHookedMethod是classXposedBridge里的handleHookedMethod方法。ok,重頭戲來了
private static Object handleHookedMethod(Member method, int originalMethodId, Object additionalInfoObj, Object thisObject, Object[] args) throws Throwable { XposedBridge.AdditionalHookInfo additionalInfo = (XposedBridge.AdditionalHookInfo)additionalInfoObj; if(disableHooks) { try { //hook不使能,執行原method return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes, additionalInfo.returnType, thisObject, args); ...... } } else { /得到hookmethod的callback,在之前的XposedBridge.hookMethod中為callbacks添加了callback Object[] callbacksSnapshot = additionalInfo.callbacks.getSnapshot(); int callbacksLength = callbacksSnapshot.length; if(callbacksLength == 0) { //hookmethod的callback為空,hooked無意義,執行原method try { return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes, additionalInfo.returnType, thisObject, args); ...... } } else { ...... do { label65: { try { ((XC_MethodHook)callbacksSnapshot[beforeIdx]).beforeHookedMethod(param); } catch (Throwable var18) { log(var18); param.setResult((Object)null); param.returnEarly = false; break label65; } if(param.returnEarly) { ++beforeIdx; break; } } ++beforeIdx; //hookmethod有幾個callback就循環幾次 } while(beforeIdx < callbacksLength); if(!param.returnEarly) { try { //在beforeHookedMethod后執行原method param.setResult(invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args)); } catch (InvocationTargetException var16) { param.setThrowable(var16.getCause()); } } int afterIdx = beforeIdx - 1; do { Object lastResult = param.getResult(); Throwable lastThrowable = param.getThrowable(); try { ((XC_MethodHook)callbacksSnapshot[afterIdx]).afterHookedMethod(param); } catch (Throwable var17) { log(var17); if(lastThrowable == null) { param.setResult(lastResult); } else { param.setThrowable(lastThrowable); } } --afterIdx; } while(afterIdx >= 0); ...... } } }
別看handleHookedMethod代碼老長了,其實它很單純。
第一步:是否需要執行callback,否則直接執行原method,gameover;
第二步:執行callbacks里的beforeHookedMethod方法,有幾個callback執行幾次beforeHookedMethod;
第三步:執行原method;
第四步:執行callbacks里的afterHookedMethod方法,類同beforeHookedMethod。
需要注意的是如果method有多個callback,其beforeHookedMethod和afterHookedMethod執行順序:
A1.before->A2.before->原method->A2.after->A1.after,也是蠻符合客觀規律的嘛。
好,關於findAndHookMethod()函數也算是從上倒下看了個遍,但你上面添這么多代碼是算怎么回事呢?下面就簡單總結下羅:
好了,本篇就先這樣吧,太長了也不好看。下篇再分析下其余枝節:
1 handleLoadPackage 怎么生效
2 dalvik層如何回到java層(數據類型如何回傳)
3 XC_MethodHook中會執行原來的java函數體,如何執行;
其他想到再分析啰,大家也可以對上述執行流程提問,我們一起探討。
參考資料:
版權聲明:本文為博主原創文章,未經博主允許不得轉載。