JNI实现源码分析【四 函数调用】
有了前面的鋪墊,終于可以說(shuō)說(shuō)虛擬機(jī)是如何調(diào)用JNI方法的了。JNI方法,對(duì)應(yīng)Java中的native方法,所以我們跟蹤對(duì)Native方法的處理即可。
在徹底弄懂dalvik字節(jié)碼【一】中,我們跟蹤過(guò)非Native方法的調(diào)用,現(xiàn)在我們來(lái)跟蹤Native方法的調(diào)用,從dvmCallMethodV入手吧:
0x01:dvmCallMethodV
void dvmCallMethodV(Thread* self, const Method* method, Object* obj,bool fromJni, JValue* pResult, va_list args) {...if (dvmIsNativeMethod(method)) {TRACE_METHOD_ENTER(self, method);/** Because we leave no space for local variables, "curFrame" points* directly at the method arguments.*/(*method->nativeFunc)((u4*)self->interpSave.curFrame, pResult,method, self);TRACE_METHOD_EXIT(self, method);} else {dvmInterpret(self, method, pResult);}... }可以看到,當(dāng)發(fā)現(xiàn)是Native方法時(shí),直接調(diào)用Method.nativeFunc
0x02:nativeFunc
看看Method中的定義:
DalvikBridgeFunc nativeFunc;typedef void (*DalvikBridgeFunc)(const u4* args, JValue* pResult,const Method* method, struct Thread* self);原來(lái)是個(gè)函數(shù)指針。名字既然叫做Bridge,說(shuō)明是橋接的作用,即主要起到方法的串聯(lián)和參數(shù)的適配作用。
0x03: 何時(shí)賦值
那么這個(gè)函數(shù)指針何時(shí)被賦值了呢?
有好幾處。
a. dvmResolveNativeMethod
在Class.cpp的loadMethodFromDex中,我們看到:
loadMethodFromDex在從dex中加載類的時(shí)候會(huì)被調(diào)用,也就是說(shuō),這里是最初的調(diào)用。
所以就是場(chǎng)景就是:我們需要使用到Dex中的一個(gè)類,這個(gè)類第一次被加載,構(gòu)建這個(gè)類的方法時(shí),發(fā)現(xiàn)是一個(gè)native方法,將nativeFunc設(shè)置成為dvmResolveNativeMethod。
看看dvmResolveNativeMethod做了啥:
void dvmResolveNativeMethod(const u4* args, JValue* pResult,const Method* method, Thread* self) {ClassObject* clazz = method->clazz;/** If this is a static method, it could be called before the class* has been initialized.*/if (dvmIsStaticMethod(method)) {if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {assert(dvmCheckException(dvmThreadSelf()));return;}} else {assert(dvmIsClassInitialized(clazz) ||dvmIsClassInitializing(clazz));}/* start with our internal-native methods */DalvikNativeFunc infunc = dvmLookupInternalNativeMethod(method);if (infunc != NULL) {/* resolution always gets the same answer, so no race here */IF_LOGVV() {char* desc = dexProtoCopyMethodDescriptor(&method->prototype);LOGVV("+++ resolved native %s.%s %s, invoking",clazz->descriptor, method->name, desc);free(desc);}if (dvmIsSynchronizedMethod(method)) {ALOGE("ERROR: internal-native can't be declared 'synchronized'");ALOGE("Failing on %s.%s", method->clazz->descriptor, method->name);dvmAbort(); // harsh, but this is VM-internal problem}DalvikBridgeFunc dfunc = (DalvikBridgeFunc) infunc;dvmSetNativeFunc((Method*) method, dfunc, NULL);dfunc(args, pResult, method, self);return;}/* now scan any DLLs we have loaded for JNI signatures */void* func = lookupSharedLibMethod(method);if (func != NULL) {/* found it, point it at the JNI bridge and then call it */dvmUseJNIBridge((Method*) method, func);(*method->nativeFunc)(args, pResult, method, self);return;}IF_ALOGW() {char* desc = dexProtoCopyMethodDescriptor(&method->prototype);ALOGW("No implementation found for native %s.%s:%s",clazz->descriptor, method->name, desc);free(desc);}dvmThrowUnsatisfiedLinkError("Native method not found", method); }其中dvmLookupInternalNativeMethod是查找這個(gè)方法是不是屬于虛擬機(jī)里面定義的Native方法,如果是,則直接調(diào)用調(diào)用。我們自己寫的native方法自然不是這里。
再看lookupSharedLibMethod,從classpath下的so中查找對(duì)應(yīng)的函數(shù),函數(shù)名稱使用了如下格式:
alling dlsym(Java_com_sina_weibo_sdk_net_HttpManager_calcOauthSignNative) calling dlsym(Java_com_sina_weibo_sdk_net_HttpManager_calcOauthSignNative__Landroid_content_Context_2Ljava_lang_String_2Ljava_lang_String_2)函數(shù)名稱構(gòu)建的代碼:
...mangleCM = mangleString(preMangleCM, len);if (mangleCM == NULL)goto bail;ALOGV("+++ calling dlsym(%s)", mangleCM);func = dlsym(pLib->handle, mangleCM);if (func == NULL) {mangleSig =createMangledSignature(&meth->prototype);if (mangleSig == NULL)goto bail;mangleCMSig = (char*) malloc(strlen(mangleCM) + strlen(mangleSig) +3);if (mangleCMSig == NULL)goto bail;sprintf(mangleCMSig, "%s__%s", mangleCM, mangleSig);ALOGV("+++ calling dlsym(%s)", mangleCMSig);func = dlsym(pLib->handle, mangleCMSig);if (func != NULL) {ALOGV("Found '%s' with dlsym", mangleCMSig);}} else {ALOGV("Found '%s' with dlsym", mangleCM);}所以,這里我們看到了,默認(rèn)的函數(shù)名映射的規(guī)則是:Java_you_pakcage_ClassName_MethodName[__methoidSig],當(dāng)通過(guò)Java_you_pakcage_ClassName_MethodName找不到時(shí),會(huì)再嘗試Java_you_pakcage_ClassName_MethodName__methoidSig來(lái)查找。
在找到c函數(shù)后,通過(guò)dvmUseJNIBridge來(lái)建立聯(lián)系:
void dvmUseJNIBridge(Method* method, void* func) {method->shouldTrace = shouldTrace(method);// Does the method take any reference arguments?method->noRef = true;const char* cp = method->shorty;while (*++cp != '\0') { // Pre-increment to skip return type.if (*cp == 'L') {method->noRef = false;break;}}DalvikBridgeFunc bridge = gDvmJni.useCheckJni ? dvmCheckCallJNIMethod : dvmCallJNIMethod;dvmSetNativeFunc(method, bridge, (const u2*) func); }在正常情況下,gDvmJni.useCheckJni為false,所以bridge函數(shù)為dvmCallJNIMethod:
void dvmCallJNIMethod(const u4* args, JValue* pResult, const Method* method, Thread* self) {u4* modArgs = (u4*) args;jclass staticMethodClass = NULL;u4 accessFlags = method->accessFlags;bool isSynchronized = (accessFlags & ACC_SYNCHRONIZED) != 0;//ALOGI("JNI calling %p (%s.%s:%s):", method->insns,// method->clazz->descriptor, method->name, method->shorty);/** Walk the argument list, creating local references for appropriate* arguments.*/int idx = 0;Object* lockObj;if ((accessFlags & ACC_STATIC) != 0) {lockObj = (Object*) method->clazz;/* add the class object we pass in */staticMethodClass = (jclass) addLocalReference(self, (Object*) method->clazz);} else {lockObj = (Object*) args[0];/* add "this" */modArgs[idx++] = (u4) addLocalReference(self, (Object*) modArgs[0]);}if (!method->noRef) {const char* shorty = &method->shorty[1]; /* skip return type */while (*shorty != '\0') {switch (*shorty++) {case 'L'://ALOGI(" local %d: 0x%08x", idx, modArgs[idx]);if (modArgs[idx] != 0) {modArgs[idx] = (u4) addLocalReference(self, (Object*) modArgs[idx]);}break;case 'D':case 'J':idx++;break;default:/* Z B C S I -- do nothing */break;}idx++;}}if (UNLIKELY(method->shouldTrace)) {logNativeMethodEntry(method, args);}if (UNLIKELY(isSynchronized)) {dvmLockObject(self, lockObj);}ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_NATIVE);ANDROID_MEMBAR_FULL(); /* guarantee ordering on method->insns */assert(method->insns != NULL);JNIEnv* env = self->jniEnv;COMPUTE_STACK_SUM(self);dvmPlatformInvoke(env,(ClassObject*) staticMethodClass,method->jniArgInfo, method->insSize, modArgs, method->shorty,(void*) method->insns, pResult);CHECK_STACK_SUM(self);dvmChangeStatus(self, oldStatus);convertReferenceResult(env, pResult, method, self);if (UNLIKELY(isSynchronized)) {dvmUnlockObject(self, lockObj);}if (UNLIKELY(method->shouldTrace)) {logNativeMethodExit(method, self, *pResult);} }這個(gè)函數(shù)重點(diǎn)說(shuō)一下,做了幾個(gè)事情:
b. RegisterNatives
另外一種是通過(guò)自行調(diào)用JNIEnv.RegisterNatives來(lái)完成注冊(cè):
主要是dvmRegisterJNIMethod:
static bool dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,const char* signature, void* fnPtr) {...dvmUseJNIBridge(method, fnPtr);... }又是dvmUseJNIBridge,剩下的就和前面一樣了。
所以主動(dòng)注冊(cè)與默認(rèn)查找的區(qū)別就是,主動(dòng)注冊(cè)需要告訴JNI,Java方法和C函數(shù)的映射,而默認(rèn)查找則按照對(duì)應(yīng)的規(guī)則去查找。對(duì)后在調(diào)用邏輯上,完全一致。
同時(shí),我們也看到了,在調(diào)用C函數(shù)前,真實(shí)的對(duì)象被轉(zhuǎn)化為間接引用,然后傳遞到JNI方法中,同時(shí),JNI方法返回的間接引用被轉(zhuǎn)化為真實(shí)的對(duì)象,供下一步使用。
作者:difcareer
鏈接:http://www.jianshu.com/p/1ef556aec1cd
來(lái)源:簡(jiǎn)書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
總結(jié)
以上是生活随笔為你收集整理的JNI实现源码分析【四 函数调用】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: JNI实现源码分析【三 间接引用表】
- 下一篇: 反调试检测之一TracerPid