AndFix實(shí)現(xiàn)原理詳解[二]

實(shí)現(xiàn)原理核心代碼詳解(davlik部分)


1蹋笼、c++背景知識(shí)介紹

  • extern關(guān)鍵字

extern可置于變量或者函數(shù)前钠绍,以表示變量或者函數(shù)的定義在別的文件中拨匆,提示編譯器遇到此變量或函數(shù)時(shí)端壳,在其它模塊中尋找其定義

//該方法聲明在頭文件
extern void dalvik_setFieldFlag(JNIEnv* env, jobject field) {}

  • __attribute__關(guān)鍵字

GNU C的一大特色就是__attribute__機(jī)制,__attribute__可以設(shè)置函數(shù)屬性(Function Attribute)谦秧、變量屬性(Variable Attribute)和類型屬性(Type Attribute),語法格式:__attribute__ ( ( attribute-list ) )

//設(shè)置dalvik_setup函數(shù)屬性為hidden
extern jboolean __attribute__ ((visibility ("hidden"))) dalvik_setup(JNIEnv* env, int apilevel) {}

  • dlopen函數(shù)
  • dlopen()函數(shù)以指定模式打開指定的動(dòng)態(tài)鏈接庫文件,并返回一個(gè)句柄給dlsym()的調(diào)用進(jìn)程搞莺。
  • RTLD_NOW:需要在dlopen返回前,解析出所有未定義符號(hào)掂咒,如果解析不出來才沧,在dlopen會(huì)返回NULL。
  • 句柄和指針的區(qū)別:我們調(diào)用句柄就是調(diào)用句柄所提供的服務(wù)绍刮,即句柄已經(jīng)把它能做的操作都設(shè)定好了温圆,我們只能在句柄所提供的操作范圍內(nèi)進(jìn)行操作,但是普通指針的操作卻多種多樣孩革,不受限制岁歉。
//打開libdvm.so文件,并返還句柄給后面dlsym()使用
void* dvm_hand = dlopen("libdvm.so", RTLD_NOW);

  • dlsym函數(shù)

根據(jù)動(dòng)態(tài)鏈接庫操作句柄與符號(hào),返回符號(hào)對(duì)應(yīng)的地址锅移,使用這個(gè)函數(shù)不但可以獲取函數(shù)地址熔掺,也可以獲取變量地址。

//獲取函數(shù)地址方法
static void* dvm_dlsym(void *hand, const char *name) {
 //根據(jù)動(dòng)態(tài)鏈接庫操作句柄與符號(hào)非剃,返回符號(hào)對(duì)應(yīng)的地址置逻。 
void* ret = dlsym(hand, name); return ret; 
}

  • typedef關(guān)鍵字>typedef關(guān)鍵字使用可自行了解,這里解釋的是復(fù)雜的變量聲明备绽。結(jié)合下面代碼分析說明
typedef int (*dvmComputeMethodArgsSize_func)(void*);
//首先找到變量名dvmComputeMethodArgsSize_func券坞,
//外面有一對(duì)圓括號(hào),而且左邊是一個(gè)*號(hào)肺素,說明這它是一個(gè)指針恨锚;
//然后跳出這個(gè)圓括號(hào),先看右邊又遇到圓括號(hào)倍靡,
//說明(*dvmComputeMethodArgsSize_func)是一個(gè)函數(shù)
//所以dvmComputeMethodArgsSize_func即是函數(shù)指針猴伶,
//具有void*類型的形參,返回值類型為int菌瘫。

當(dāng)調(diào)用了dlsym方法獲取函數(shù)指針后繼給它們做相應(yīng)的引用蜗顽。

  • 方法調(diào)用表各方法可到davlik源碼查,
    這里有個(gè)比較方便的源碼查尋:Android cross;
符號(hào)標(biāo)示 對(duì)應(yīng)源碼函數(shù) 函數(shù)功能
dvmComputeMethodArgsSize int dvmComputeMethodArgsSize(const Method* method) 計(jì)算指定函數(shù)的參數(shù)個(gè)數(shù)方法.
dvmCallMethod void dvmCallMethod(Thread* self, const Method* method, Object* obj, JValue* pResult, ...) dalvik執(zhí)行指定函數(shù)的方法.
dexProtoGetParameterCount size_t dexProtoGetParameterCount(const DexProto* pProto) 獲取原型類型的參數(shù)個(gè)數(shù).
dvmAllocArrayByClass ArrayObject* dvmAllocArrayByClass(ClassObject* arrayClass,size_t length, int allocFlags) dalvik通過class申請(qǐng)數(shù)組函數(shù).
dvmWrapPrimitive雨让、dvmBoxPrimitive DataObject* dvmBoxPrimitive(JValue value, ClassObject* returnType) 創(chuàng)建一個(gè)適配對(duì)象給一個(gè)原型數(shù)據(jù)類型,如果返回類型不是原型,僅僅把值強(qiáng)轉(zhuǎn)為object返回.
dvmFindPrimitiveClass ClassObject* dvmFindPrimitiveClass(char type) dalvik虛擬機(jī)查找原型類函數(shù)
dvmReleaseTrackedAlloc void dvmReleaseTrackedAlloc(Object* obj, Thread* self) dalvik釋放tracked內(nèi)存
dvmCheckException bool dvmCheckException(Thread* self) 檢測異常
dvmGetException Object* dvmGetException(Thread* self) 獲取異常
dvmFindArrayClass ClassObject* dvmFindArrayClass(const char* descriptor, Object* loader) 查找Class對(duì)象
dvmCreateReflectMethodObject Object* dvmCreateReflectMethodObject(const Method* meth) 創(chuàng)建反射方法對(duì)象雇盖,即: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) 轉(zhuǎn)換一個(gè)間接引用為對(duì)象引用
dvmThreadSelf Thread* dvmThreadSelf() 獲得線程自身的ID

具體方法代碼詳見方法源碼附錄栖忠。
大家讀源碼的時(shí)候可能注意到_Z20dvmDecodeIndirectRefP6ThreadP8_jobject類似函數(shù)還附加了些特殊符號(hào)崔挖,這是因?yàn)榫幾g時(shí)區(qū)分不同版本,c++做了命名重載庵寞。

2狸相、字節(jié)碼的文件結(jié)構(gòu)

  • 字段訪問標(biāo)識(shí)

  • class 訪問標(biāo)識(shí)


    class.png
  • field訪問標(biāo)識(shí)


    field.png
  • method訪問標(biāo)識(shí)


    method.png
  • 字段的描述符

character.png

上面的當(dāng)我們分析andfix時(shí)遇到符號(hào)查表對(duì)應(yīng)即可。

3捐川、函數(shù)核心方法分析
3.1 replaceMethod

extern void __attribute__ ((visibility ("hidden"))) 
dalvik_replaceMethod( JNIEnv* env, jobject src, jobject dest) { 
//src:待修復(fù)的函數(shù)脓鹃。 
//dest:修復(fù)函數(shù)。  
//jni調(diào)用反射包下Method類getDeclaringclass方法,獲得class對(duì)象clazz jobject clazz = env->CallObjectMethod(dest, jClassMethod); 

//將clazz對(duì)象轉(zhuǎn)換為dalvik直接引用,返回ClassObject*引用它.
//因?yàn)閐avlik是操作ClassObject的古沥。 
ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr( dvmThreadSelf_fnPtr(), clazz); 

//參見http://osxr.org/android/source/dalvik/vm/oo/Object.h 不過andfix jni目錄下copy一份放在dalvik.h 
//標(biāo)示clz為初始化完畢的ready狀態(tài)瘸右。
 clz->status = CLASS_INITIALIZED; 

//轉(zhuǎn)換java.lang.reflect.Method或java.lang.reflect.Constructor對(duì)象為函數(shù)地址。 
Method* meth = (Method*) env->FromReflectedMethod(src); 
 
//修復(fù)函數(shù)地址岩齿。 
Method* target = (Method*) env->FromReflectedMethod(dest);  

//打印待修復(fù)方法名稱 LOGD("dalvikMethod: %s", meth->name); 
//這個(gè)變量記錄了一些預(yù)先計(jì)算好的信息太颤,從而不需要在調(diào)用的時(shí)候再通過方法的參數(shù)和返回值實(shí)時(shí)計(jì)算了,方便了JNI的調(diào)用盹沈,提高了調(diào)用的速度龄章。
//如果第一位為1(即0x80000000),則Dalvik虛擬機(jī)會(huì)忽略后面的所有信息,強(qiáng)制在調(diào)用時(shí)實(shí)時(shí)計(jì)算做裙; 
meth->jniArgInfo = 0x80000000; 

//方法的訪問標(biāo)識(shí)置為native岗憋,即將meth的訪問標(biāo)識(shí)設(shè)為native方式。
 meth->accessFlags |= ACC_NATIVE; 

//獲取方法形參個(gè)數(shù) 
int argsSize = dvmComputeMethodArgsSize_fnPtr(meth);  
if (!dvmIsStaticMethod(meth)) argsSize++;

//非靜態(tài)方法參數(shù)個(gè)數(shù)加一,引用改方法的對(duì)象this也默認(rèn)當(dāng)作一個(gè)參數(shù)菇用,且放到第0位澜驮。  
//registersSize:該方法總共用到的寄存器個(gè)數(shù),包含入口參數(shù)所用到的寄存器惋鸥,還有方法內(nèi)部自己所用到的其它本地寄存器杂穷; 
//insSize:作為調(diào)用該方法時(shí),參數(shù)傳遞而使用到的寄存器個(gè)數(shù)卦绣; 
//outsSize:當(dāng)該方法要調(diào)用其它方法時(shí)耐量,用作參數(shù)傳遞而使用的寄存器個(gè)數(shù);  
meth->registersSize = meth->insSize = argsSize; 

//如果這個(gè)方法不是Native的話滤港,則這里存放了指向方法具體的Dalvik指令的指針(這個(gè)變量指向的是實(shí)際加載到內(nèi)存中的Dalvik 
//指令廊蜒,而不是在Dex文件中的)。如果這個(gè)方法是一個(gè)Dalvik虛擬機(jī)自帶的Native函數(shù)(Internal Native)的話溅漾,則這個(gè)變量 
//會(huì)是Null山叮。如果這個(gè)方法是一個(gè)普通的Native函數(shù)的話,則這里存放了指向JNI實(shí)際函數(shù)機(jī)器碼的首地址添履; 
meth->insns = (void*) target;

//指向替換src的target方法. 
//如果這個(gè)方法是一個(gè)Dalvik虛擬機(jī)自帶的Native函數(shù)(Internal Native)的話屁倔,則這里存放了指向JNI實(shí)際函數(shù)機(jī)器碼的首地址。
//如果這個(gè)方法是一個(gè)普通的Native函數(shù)的話暮胧,則這里將指向一個(gè)中間的跳轉(zhuǎn)JNI橋(Bridge)代碼锐借; 
meth->nativeFunc = dalvik_dispatcher;

}

上面即完成了待修復(fù)函數(shù)的替換.

實(shí)際執(zhí)行將通過jni橋代碼執(zhí)行。

static void dalvik_dispatcher(const u4* args, jvalue* pResult, const Method* method, void* self) { 
//返回對(duì)象類型 
ClassObject* returnType;

//返回值 ArrayObject* argArray;//對(duì)象數(shù)組往衷,這里表示為參數(shù)數(shù)組钞翔。 
jvalue result;

//打印一下,函數(shù)名稱和簡短的函數(shù)名席舍,這里輸出待修復(fù)方法 
LOGD("dalvik_dispatcher source method: %s %s", method->name,method->shorty);  

//賦值為修復(fù)函數(shù) 
Method* meth = (Method*) method->insns; 

//訪問標(biāo)識(shí)置為
public meth->accessFlags = meth->accessFlags | ACC_PUBLIC; 

//打印修復(fù)函數(shù)的名稱和簡短名稱 
LOGD("dalvik_dispatcher target method: %s %s", method->name, method->shorty); 

//調(diào)用返回值裝箱類型函數(shù)布轿, 
returnType = dvmGetBoxedReturnType_fnPtr(method);
 
//處理返回值類型裝箱異常 
if (returnType == NULL) { 
  assert(dvmCheckException_fnPtr(self));
  goto bail; 
}  
//函數(shù)開始調(diào)用 
LOGD("dalvik_dispatcher start call->");
if (!dvmIsStaticMethod(meth)) {
//如果不是靜態(tài)函數(shù) 
//獲取持有該方法的對(duì)象。注意這個(gè)object是dalvik中typedef的来颤。 
Object* thisObj = (Object*) args[0]; 

//獲取tmp臨時(shí)ClassObject臨時(shí)較好對(duì)象汰扭。 
ClassObject* tmp = thisObj->clazz; 
thisObj->clazz = meth->clazz; 

//將方法參數(shù)都封裝到array中 
argArray = boxMethodArgs(meth, args + 1); 
if (dvmCheckException_fnPtr(self)) 
  goto bail;  

//執(zhí)行該方法 
//1、self:線程id 
//2脚曾、jInvokeMethod:指向invoke函數(shù),public Object invoke(Object receiver, Object... args) 
//3东且、創(chuàng)建一個(gè)新的Method對(duì)象.使用Meth地址來構(gòu)造出它. 
//4启具、result返回值的地址 
//5本讥、持有該方法的對(duì)象 
//6、函數(shù)參數(shù)數(shù)組 
dvmCallMethod_fnPtr(self, (Method*) jInvokeMethod, dvmCreateReflectMethodObject_fnPtr(meth), &result, thisObj, argArray); 
//恢復(fù)引用 
thisObj->clazz = tmp; 
} 
else 
{
 //靜態(tài)函數(shù) 
 argArray = boxMethodArgs(meth, args); 
  if (dvmCheckException_fnPtr(self)) 
    goto bail; 
//不用傳遞持有該函數(shù)的對(duì)象,因?yàn)楹瘮?shù)是靜態(tài)的
  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;
 } 
} 
//出異常時(shí)dalvik釋放tracked內(nèi)存 
bail: dvmReleaseTrackedAlloc_fnPtr((Object*) argArray, self);
}

下面是獲取參數(shù)時(shí)的裝箱方法序无,比較簡單就不詳細(xì)的介紹了验毡。

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;
}

上述的核心代碼流程分析完畢,相信大家做下分析下都能了解方法替換的實(shí)現(xiàn)了帝嗡。需要學(xué)習(xí)xpose機(jī)制晶通,熟悉dalvik結(jié)構(gòu),然后吸收成自己的理解創(chuàng)出這個(gè)實(shí)現(xiàn)方案哟玷。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末狮辽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子巢寡,更是在濱河造成了極大的恐慌喉脖,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抑月,死亡現(xiàn)場離奇詭異树叽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)爪幻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門菱皆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人挨稿,你說我怎么就攤上這事仇轻。” “怎么了奶甘?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵篷店,是天一觀的道長。 經(jīng)常有香客問我臭家,道長疲陕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任钉赁,我火速辦了婚禮蹄殃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘你踩。我一直安慰自己诅岩,他們只是感情好讳苦,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吩谦,像睡著了一般鸳谜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上式廷,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天咐扭,我揣著相機(jī)與錄音,去河邊找鬼滑废。 笑死蝗肪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蠕趁。 我是一名探鬼主播穗慕,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼妻导!你這毒婦竟也來了逛绵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤倔韭,失蹤者是張志新(化名)和其女友劉穎术浪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寿酌,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胰苏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了醇疼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片硕并。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖秧荆,靈堂內(nèi)的尸體忽然破棺而出倔毙,到底是詐尸還是另有隱情,我是刑警寧澤乙濒,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布陕赃,位于F島的核電站,受9級(jí)特大地震影響颁股,放射性物質(zhì)發(fā)生泄漏么库。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一甘有、第九天 我趴在偏房一處隱蔽的房頂上張望诉儒。 院中可真熱鬧,春花似錦亏掀、人聲如沸忱反。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缭受。三九已至,卻和暖如春该互,著一層夾襖步出監(jiān)牢的瞬間米者,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工宇智, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蔓搞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓随橘,卻偏偏與公主長得像喂分,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子机蔗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容