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 访问标识


    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结构,然后吸收成自己的理解创出这个实现方案。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容