AndFix实现原理详解[二]

实现原理核心代码详解(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 访问标识


    AndFix实现原理详解[二]_第1张图片
    class.png
  • field访问标识


    AndFix实现原理详解[二]_第2张图片
    field.png
  • method访问标识


    AndFix实现原理详解[二]_第3张图片
    method.png
  • 字段的描述符

AndFix实现原理详解[二]_第4张图片
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实现原理详解[二])