JNI实现源码分析【四 函数调用】
有了前面的鋪墊,終于可以說說虛擬機是如何調用JNI方法的了。JNI方法,對應Java中的native方法,所以我們跟蹤對Native方法的處理即可。
在徹底弄懂dalvik字節碼【一】中,我們跟蹤過非Native方法的調用,現在我們來跟蹤Native方法的調用,從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);}... }可以看到,當發現是Native方法時,直接調用Method.nativeFunc
0x02:nativeFunc
看看Method中的定義:
DalvikBridgeFunc nativeFunc;typedef void (*DalvikBridgeFunc)(const u4* args, JValue* pResult,const Method* method, struct Thread* self);原來是個函數指針。名字既然叫做Bridge,說明是橋接的作用,即主要起到方法的串聯和參數的適配作用。
0x03: 何時賦值
那么這個函數指針何時被賦值了呢?
有好幾處。
a. dvmResolveNativeMethod
在Class.cpp的loadMethodFromDex中,我們看到:
loadMethodFromDex在從dex中加載類的時候會被調用,也就是說,這里是最初的調用。
所以就是場景就是:我們需要使用到Dex中的一個類,這個類第一次被加載,構建這個類的方法時,發現是一個native方法,將nativeFunc設置成為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是查找這個方法是不是屬于虛擬機里面定義的Native方法,如果是,則直接調用調用。我們自己寫的native方法自然不是這里。
再看lookupSharedLibMethod,從classpath下的so中查找對應的函數,函數名稱使用了如下格式:
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)函數名稱構建的代碼:
...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);}所以,這里我們看到了,默認的函數名映射的規則是:Java_you_pakcage_ClassName_MethodName[__methoidSig],當通過Java_you_pakcage_ClassName_MethodName找不到時,會再嘗試Java_you_pakcage_ClassName_MethodName__methoidSig來查找。
在找到c函數后,通過dvmUseJNIBridge來建立聯系:
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函數為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);} }這個函數重點說一下,做了幾個事情:
b. RegisterNatives
另外一種是通過自行調用JNIEnv.RegisterNatives來完成注冊:
主要是dvmRegisterJNIMethod:
static bool dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,const char* signature, void* fnPtr) {...dvmUseJNIBridge(method, fnPtr);... }又是dvmUseJNIBridge,剩下的就和前面一樣了。
所以主動注冊與默認查找的區別就是,主動注冊需要告訴JNI,Java方法和C函數的映射,而默認查找則按照對應的規則去查找。對后在調用邏輯上,完全一致。
同時,我們也看到了,在調用C函數前,真實的對象被轉化為間接引用,然后傳遞到JNI方法中,同時,JNI方法返回的間接引用被轉化為真實的對象,供下一步使用。
作者:difcareer
鏈接:http://www.jianshu.com/p/1ef556aec1cd
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
總結
以上是生活随笔為你收集整理的JNI实现源码分析【四 函数调用】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JNI实现源码分析【三 间接引用表】
- 下一篇: 反调试检测之一TracerPid