实现原理核心代码详解(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 访问标识
-
field访问标识
-
method访问标识
字段的描述符
上面的当我们分析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结构,然后吸收成自己的理解创出这个实现方案。