AndFix实现原理详解[二]

作者: e0uoq | 来源:发表于2016-05-22 10:46 被阅读1295次

    实现原理核心代码详解(davlik部分)


    1、c++背景知识介绍

    • extern关键字

    extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量或函数时,在其它模块中寻找其定义

    //该方法声明在头文件
    extern void dalvik_setFieldFlag(JNIEnv* env, jobject field) {}
    

    • __attribute__关键字

    GNU C的一大特色就是__attribute__机制,__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute),语法格式:__attribute__ ( ( attribute-list ) )

    //设置dalvik_setup函数属性为hidden
    extern jboolean __attribute__ ((visibility ("hidden"))) dalvik_setup(JNIEnv* env, int apilevel) {}
    

    • dlopen函数
    • dlopen()函数以指定模式打开指定的动态链接库文件,并返回一个句柄给dlsym()的调用进程。
    • RTLD_NOW:需要在dlopen返回前,解析出所有未定义符号,如果解析不出来,在dlopen会返回NULL。
    • 句柄和指针的区别:我们调用句柄就是调用句柄所提供的服务,即句柄已经把它能做的操作都设定好了,我们只能在句柄所提供的操作范围内进行操作,但是普通指针的操作却多种多样,不受限制。
    //打开libdvm.so文件,并返还句柄给后面dlsym()使用
    void* dvm_hand = dlopen("libdvm.so", RTLD_NOW);
    

    • dlsym函数

    根据动态链接库操作句柄与符号,返回符号对应的地址,使用这个函数不但可以获取函数地址,也可以获取变量地址。

    //获取函数地址方法
    static void* dvm_dlsym(void *hand, const char *name) {
     //根据动态链接库操作句柄与符号,返回符号对应的地址。 
    void* ret = dlsym(hand, name); return ret; 
    }
    

    • typedef关键字>typedef关键字使用可自行了解,这里解释的是复杂的变量声明。结合下面代码分析说明
    typedef int (*dvmComputeMethodArgsSize_func)(void*);
    //首先找到变量名dvmComputeMethodArgsSize_func,
    //外面有一对圆括号,而且左边是一个*号,说明这它是一个指针;
    //然后跳出这个圆括号,先看右边又遇到圆括号,
    //说明(*dvmComputeMethodArgsSize_func)是一个函数
    //所以dvmComputeMethodArgsSize_func即是函数指针,
    //具有void*类型的形参,返回值类型为int。
    

    当调用了dlsym方法获取函数指针后继给它们做相应的引用。

    • 方法调用表各方法可到davlik源码查,
      这里有个比较方便的源码查寻:Android cross;
    符号标示 对应源码函数 函数功能
    dvmComputeMethodArgsSize int dvmComputeMethodArgsSize(const Method* method) 计算指定函数的参数个数方法.
    dvmCallMethod void dvmCallMethod(Thread* self, const Method* method, Object* obj, JValue* pResult, ...) dalvik执行指定函数的方法.
    dexProtoGetParameterCount size_t dexProtoGetParameterCount(const DexProto* pProto) 获取原型类型的参数个数.
    dvmAllocArrayByClass ArrayObject* dvmAllocArrayByClass(ClassObject* arrayClass,size_t length, int allocFlags) dalvik通过class申请数组函数.
    dvmWrapPrimitive、dvmBoxPrimitive DataObject* dvmBoxPrimitive(JValue value, ClassObject* returnType) 创建一个适配对象给一个原型数据类型,如果返回类型不是原型,仅仅把值强转为object返回.
    dvmFindPrimitiveClass ClassObject* dvmFindPrimitiveClass(char type) dalvik虚拟机查找原型类函数
    dvmReleaseTrackedAlloc void dvmReleaseTrackedAlloc(Object* obj, Thread* self) dalvik释放tracked内存
    dvmCheckException bool dvmCheckException(Thread* self) 检测异常
    dvmGetException Object* dvmGetException(Thread* self) 获取异常
    dvmFindArrayClass ClassObject* dvmFindArrayClass(const char* descriptor, Object* loader) 查找Class对象
    dvmCreateReflectMethodObject Object* dvmCreateReflectMethodObject(const Method* meth) 创建反射方法对象,即:java/lang/reflect/Method
    dvmGetBoxedReturnType ClassObject* dvmGetBoxedReturnType(const Method* meth) 获取封箱返回类型
    dvmUnwrapPrimitive、dvmUnboxPrimitive bool dvmUnboxPrimitive(Object* value, ClassObject* returnType,JValue* pResult) 获取原型类型
    dvmDecodeIndirectRef Object* dvmDecodeIndirectRef(Thread* self, jobject jobj) 转换一个间接引用为对象引用
    dvmThreadSelf Thread* dvmThreadSelf() 获得线程自身的ID

    具体方法代码详见方法源码附录
    大家读源码的时候可能注意到_Z20dvmDecodeIndirectRefP6ThreadP8_jobject类似函数还附加了些特殊符号,这是因为编译时区分不同版本,c++做了命名重载。

    2、字节码的文件结构

    • 字段访问标识

    • class 访问标识


      class.png
    • field访问标识


      field.png
    • method访问标识


      method.png
    • 字段的描述符

    character.png

    上面的当我们分析andfix时遇到符号查表对应即可。

    3、函数核心方法分析
    3.1 replaceMethod

    extern void __attribute__ ((visibility ("hidden"))) 
    dalvik_replaceMethod( JNIEnv* env, jobject src, jobject dest) { 
    //src:待修复的函数。 
    //dest:修复函数。  
    //jni调用反射包下Method类getDeclaringclass方法,获得class对象clazz jobject clazz = env->CallObjectMethod(dest, jClassMethod); 
    
    //将clazz对象转换为dalvik直接引用,返回ClassObject*引用它.
    //因为davlik是操作ClassObject的。 
    ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr( dvmThreadSelf_fnPtr(), clazz); 
    
    //参见http://osxr.org/android/source/dalvik/vm/oo/Object.h 不过andfix jni目录下copy一份放在dalvik.h 
    //标示clz为初始化完毕的ready状态。
     clz->status = CLASS_INITIALIZED; 
    
    //转换java.lang.reflect.Method或java.lang.reflect.Constructor对象为函数地址。 
    Method* meth = (Method*) env->FromReflectedMethod(src); 
     
    //修复函数地址。 
    Method* target = (Method*) env->FromReflectedMethod(dest);  
    
    //打印待修复方法名称 LOGD("dalvikMethod: %s", meth->name); 
    //这个变量记录了一些预先计算好的信息,从而不需要在调用的时候再通过方法的参数和返回值实时计算了,方便了JNI的调用,提高了调用的速度。
    //如果第一位为1(即0x80000000),则Dalvik虚拟机会忽略后面的所有信息,强制在调用时实时计算; 
    meth->jniArgInfo = 0x80000000; 
    
    //方法的访问标识置为native,即将meth的访问标识设为native方式。
     meth->accessFlags |= ACC_NATIVE; 
    
    //获取方法形参个数 
    int argsSize = dvmComputeMethodArgsSize_fnPtr(meth);  
    if (!dvmIsStaticMethod(meth)) argsSize++;
    
    //非静态方法参数个数加一,引用改方法的对象this也默认当作一个参数,且放到第0位。  
    //registersSize:该方法总共用到的寄存器个数,包含入口参数所用到的寄存器,还有方法内部自己所用到的其它本地寄存器; 
    //insSize:作为调用该方法时,参数传递而使用到的寄存器个数; 
    //outsSize:当该方法要调用其它方法时,用作参数传递而使用的寄存器个数;  
    meth->registersSize = meth->insSize = argsSize; 
    
    //如果这个方法不是Native的话,则这里存放了指向方法具体的Dalvik指令的指针(这个变量指向的是实际加载到内存中的Dalvik 
    //指令,而不是在Dex文件中的)。如果这个方法是一个Dalvik虚拟机自带的Native函数(Internal Native)的话,则这个变量 
    //会是Null。如果这个方法是一个普通的Native函数的话,则这里存放了指向JNI实际函数机器码的首地址; 
    meth->insns = (void*) target;
    
    //指向替换src的target方法. 
    //如果这个方法是一个Dalvik虚拟机自带的Native函数(Internal Native)的话,则这里存放了指向JNI实际函数机器码的首地址。
    //如果这个方法是一个普通的Native函数的话,则这里将指向一个中间的跳转JNI桥(Bridge)代码; 
    meth->nativeFunc = dalvik_dispatcher;
    
    }
    

    上面即完成了待修复函数的替换.

    实际执行将通过jni桥代码执行。

    static void dalvik_dispatcher(const u4* args, jvalue* pResult, const Method* method, void* self) { 
    //返回对象类型 
    ClassObject* returnType;
    
    //返回值 ArrayObject* argArray;//对象数组,这里表示为参数数组。 
    jvalue result;
    
    //打印一下,函数名称和简短的函数名,这里输出待修复方法 
    LOGD("dalvik_dispatcher source method: %s %s", method->name,method->shorty);  
    
    //赋值为修复函数 
    Method* meth = (Method*) method->insns; 
    
    //访问标识置为
    public meth->accessFlags = meth->accessFlags | ACC_PUBLIC; 
    
    //打印修复函数的名称和简短名称 
    LOGD("dalvik_dispatcher target method: %s %s", method->name, method->shorty); 
    
    //调用返回值装箱类型函数, 
    returnType = dvmGetBoxedReturnType_fnPtr(method);
     
    //处理返回值类型装箱异常 
    if (returnType == NULL) { 
      assert(dvmCheckException_fnPtr(self));
      goto bail; 
    }  
    //函数开始调用 
    LOGD("dalvik_dispatcher start call->");
    if (!dvmIsStaticMethod(meth)) {
    //如果不是静态函数 
    //获取持有该方法的对象。注意这个object是dalvik中typedef的。 
    Object* thisObj = (Object*) args[0]; 
    
    //获取tmp临时ClassObject临时较好对象。 
    ClassObject* tmp = thisObj->clazz; 
    thisObj->clazz = meth->clazz; 
    
    //将方法参数都封装到array中 
    argArray = boxMethodArgs(meth, args + 1); 
    if (dvmCheckException_fnPtr(self)) 
      goto bail;  
    
    //执行该方法 
    //1、self:线程id 
    //2、jInvokeMethod:指向invoke函数,public Object invoke(Object receiver, Object... args) 
    //3、创建一个新的Method对象.使用Meth地址来构造出它. 
    //4、result返回值的地址 
    //5、持有该方法的对象 
    //6、函数参数数组 
    dvmCallMethod_fnPtr(self, (Method*) jInvokeMethod, dvmCreateReflectMethodObject_fnPtr(meth), &result, thisObj, argArray); 
    //恢复引用 
    thisObj->clazz = tmp; 
    } 
    else 
    {
     //静态函数 
     argArray = boxMethodArgs(meth, args); 
      if (dvmCheckException_fnPtr(self)) 
        goto bail; 
    //不用传递持有该函数的对象,因为函数是静态的
      dvmCallMethod_fnPtr(self, (Method*) jInvokeMethod, dvmCreateReflectMethodObject_fnPtr(meth), &result, NULL, argArray); 
    } 
    if (dvmCheckException_fnPtr(self)) { 
      Object* excep = dvmGetException_fnPtr(self); 
      jni_env->Throw((jthrowable) excep); goto bail; 
    } 
    //检测返回类型和返回值。 
    if (returnType->primitiveType == PRIM_VOID) {
    //返回类型为void 
    LOGD("+++ ignoring return to void"); 
    } else if (result.l == NULL) {
    //返回值为null 
      if (dvmIsPrimitiveClass(returnType)) {
        //检测返回类型是否是原型类型。 
        jni_env->ThrowNew(NPEClazz, "null result when primitive expected"); goto bail; 
      } 
      pResult->l = NULL;
      //非原型类型,即引用类型返回null值。
     } else { 
      //解析拆箱后的类型,该方法无论原型或非原型类型的拆箱。 
       if (!dvmUnboxPrimitive_fnPtr(result.l, returnType, pResult)) { 
        char msg[1024] = { 0 };
       snprintf(msg, sizeof(msg) - 1, "%s!=%s\0", ((Object*) result.l)->clazz->descriptor, returnType->descriptor); 
       jni_env->ThrowNew(CastEClazz, msg); goto bail;
     } 
    } 
    //出异常时dalvik释放tracked内存 
    bail: dvmReleaseTrackedAlloc_fnPtr((Object*) argArray, self);
    }
    

    下面是获取参数时的装箱方法,比较简单就不详细的介绍了。

    static ArrayObject* boxMethodArgs(const Method* method, const u4* args) { 
    const char* desc = &method->shorty[1]; 
    // [0] is the return type. 
    /* count args */ 
    size_t argCount = dexProtoGetParameterCount_fnPtr(&method->prototype); 
    /* allocate storage */ 
    ArrayObject* argArray = dvmAllocArrayByClass_fnPtr(classJavaLangObjectArray, argCount, ALLOC_DEFAULT);
     if (argArray == NULL) 
    return NULL; 
    Object** argObjects = (Object**) (void*) argArray->contents; 
    /* * Fill in the array. */ 
    size_t srcIndex = 0; 
    size_t dstIndex = 0; 
    while (*desc != '\0')
     { 
    char descChar = *(desc++); 
    jvalue value; 
    switch (descChar) { 
     case 'Z':
     case 'C':
     case 'F':
     case 'B':
     case 'S':
     case 'I':
      value.i = args[srcIndex++]; 
      argObjects[dstIndex] = (Object*) dvmBoxPrimitive_fnPtr(value,   
      dvmFindPrimitiveClass_fnPtr(descChar));
       /* argObjects is tracked, don't need to hold this too */ 
       dvmReleaseTrackedAlloc_fnPtr(argObjects[dstIndex], NULL);   
       dstIndex++;
     break;
     case 'D':
     case 'J':
     value.j = dvmGetArgLong(args, srcIndex);
     srcIndex += 2;
     argObjects[dstIndex] = (Object*) dvmBoxPrimitive_fnPtr(value, dvmFindPrimitiveClass_fnPtr(descChar));
     dvmReleaseTrackedAlloc_fnPtr(argObjects[dstIndex], NULL);
     dstIndex++;
     break;
     case '[':
     case 'L':
     argObjects[dstIndex++] = (Object*) args[srcIndex++];
     LOGD("boxMethodArgs object: index = %d", dstIndex - 1); break; }
     }
     return argArray;
    }
    

    上述的核心代码流程分析完毕,相信大家做下分析下都能了解方法替换的实现了。需要学习xpose机制,熟悉dalvik结构,然后吸收成自己的理解创出这个实现方案。

    相关文章

      网友评论

        本文标题:AndFix实现原理详解[二]

        本文链接:https://www.haomeiwen.com/subject/wvmorttx.html