这是JNI实现源码分析系列文章中的一部分,本系列文章结合Dalvik源码来说明JNI实现上的细节,本系列包括:
JNI实现源码分析【一 前言】
JNI实现源码分析【二 数据结构】
JNI实现源码分析【三 间接引用表】
JNI实现源码分析【四 函数调用】
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
中,我们看到:
if (dvmIsNativeMethod(meth)) {
meth->nativeFunc = dvmResolveNativeMethod;
meth->jniArgInfo = computeJniArgInfo(&meth->prototype);
}
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);
}
}
这个函数重点说一下,做了几个事情:
- 判断是否是静态方法,如果是,就将ClassObject的间接引用设置为第一个参数。如果不是,则将this对象的间接引用设置为第一个参数。这就和JNI方法中的参数定义对应起来了:
Java_you_package_Class_StaticMethod(JNIEnv *env, jclass type, ...) {}
Java_you_package_Class_Method(JNIEnv *env, jobject jthis, ...) {}
- 判断参数中是否存在引用类型的对象(非原型对象),如果有,将对象添加到局部引用表中获取其间接引用,替换参数。
- 调用
dvmPlatformInvoke
,最终就会调用到JNI方法了。dvmPlatformInvoke
对不同的ABI有不同的实现。 - 从pResult中获取返回值,如果是间接引用,则转化为真实的对象。
b. RegisterNatives
另外一种是通过自行调用JNIEnv.RegisterNatives
来完成注册:
static jint RegisterNatives(JNIEnv* env, jclass jclazz,
const JNINativeMethod* methods, jint nMethods)
{
ScopedJniThreadState ts(env);
ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
if (gDvm.verboseJni) {
ALOGI("[Registering JNI native methods for class %s]",
clazz->descriptor);
}
for (int i = 0; i < nMethods; i++) {
if (!dvmRegisterJNIMethod(clazz, methods[i].name,
methods[i].signature, methods[i].fnPtr))
{
return JNI_ERR;
}
}
return JNI_OK;
}
主要是dvmRegisterJNIMethod
:
static bool dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,
const char* signature, void* fnPtr)
{
...
dvmUseJNIBridge(method, fnPtr);
...
}
又是dvmUseJNIBridge
,剩下的就和前面一样了。
所以主动注册与默认查找的区别就是,主动注册需要告诉JNI,Java方法和C函数的映射,而默认查找则按照对应的规则去查找。对后在调用逻辑上,完全一致。
同时,我们也看到了,在调用C函数前,真实的对象被转化为间接引用,然后传递到JNI方法中,同时,JNI方法返回的间接引用被转化为真实的对象,供下一步使用。