• Xposed学习二:实现机制


    在上一篇我们学习了如何在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函数体,如何执行;

    其他想到再分析啰,大家也可以对上述执行流程提问,我们一起探讨。


    参考资料:

    1 Xposed框架Java部分

    2 Dalvik虚拟机的运行过程分析

    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    读书笔记 effective c++ Item 32 确保public继承建立“is-a”模型
    读书笔记 effective c++ Item 31 把文件之间的编译依赖降到最低
    读书笔记 effective c++ Item 30 理解内联的里里外外 (大师入场啦)
    程序猿开发语言投票
    读书笔记 effective c++ Item 29 为异常安全的代码而努力
    读书笔记 effective c++ Item 28 不要返回指向对象内部数据(internals)的句柄(handles)
    C++ 11和C++98相比有哪些新特性
    读书笔记 effective c++ Item 27 尽量少使用转型(casting)
    如何一步一步用DDD设计一个电商网站(七)—— 实现售价上下文
    如何一步一步用DDD设计一个电商网站(六)—— 给购物车加点料,集成售价上下文
  • 原文地址:https://www.cnblogs.com/vendanner/p/xposed.html
Copyright © 2020-2023  润新知