本系列文章列表:
Android JNI介绍(一)- 第一个Android JNI工程
Android JNI介绍(二)- 第一个JNI工程的详细分析
Android JNI介绍(三)- Java和Native的互相调用
Android JNI介绍(八)- CMakeLists的使用
在前面的文章中,我们已经了解了JNI基本的代码编写方法和异常处理,但是函数的注册这一块我们还只是按照规则来,比如静态注册时,JNI是怎么按照Java_包名_类名_函数名这个规则来找native函数的?本文将对这一块进行解析。
一、静态注册
我们已经了解到,JNI会按照类似Java_包名_类名_函数名这个规则来进行函数的动态注册,那么这段关系的寻找是在哪里实现的?我们先反其道而行之,故意写错让它crash一次看看,从crash日志里进行分析。
-
修改函数名,使其crash
crash日志如下
Caused by: java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String com.wsy.jnidemo.MainActivity.testExceptionCrash1() (tried Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1 and Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1__) at com.wsy.jnidemo.MainActivity.testExceptionCrash1(Native Method) at com.wsy.jnidemo.MainActivity.wrongSampleUsingJNIEnv(MainActivity.java:139) at java.lang.reflect.Method.invoke(Native Method)注意这一句:
(tried Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1 and Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1__) -
日志分析
我们并没有传入
Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1、Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1__这样的字符串,也就是说,这两个字符串是内部生成的,然后再看一眼日志,我们很容易分析出,哪些字符串是变量,哪些字符串是常量,对于
No implementation found for java.lang.String com.wsy.jnidemo.MainActivity.testExceptionCrash1()
这么一段信息而言,后面的自然是动态生成的字符串,而前面的No implementation found for是附加的描述,一般在代码中会以常量写入。翻一下android源码,容易找到,它在art/runtime/jni/java_vm_ext.cc这个文件的void* FindNativeMethod(Thread* self, ArtMethod* m, std::string& detail) REQUIRES(!Locks::jni_libraries_lock_) REQUIRES_SHARED(Locks::mutator_lock_)函数中,我们再翻下它的上下文进行阅读。 -
代码阅读
在
quick_trampoline_entrypoints.cc中,如果这个Java函数对应的native函数缓存存在,就不继续找了,直接回传,否则就调用artFindNativeMethod寻找/* * Initializes an alloca region assumed to be directly below sp for a native call: * Create a HandleScope and call stack and fill a mini stack with values to be pushed to registers. * The final element on the stack is a pointer to the native code. * * On entry, the stack has a standard callee-save frame above sp, and an alloca below it. * We need to fix this, as the handle scope needs to go into the callee-save frame. * * The return of this function denotes: * 1) How many bytes of the alloca can be released, if the value is non-negative. * 2) An error, if the value is negative. */ extern "C" TwoWordReturn artQuickGenericJniTrampoline(Thread* self, ArtMethod** sp) REQUIRES_SHARED(Locks::mutator_lock_) { // Note: We cannot walk the stack properly until fixed up below. ArtMethod* called = *sp; DCHECK(called->IsNative()) << called->PrettyMethod(true); Runtime* runtime = Runtime::Current(); uint32_t shorty_len = 0; const char* shorty = called->GetShorty(&shorty_len); bool critical_native = called->IsCriticalNative(); bool fast_native = called->IsFastNative(); bool normal_native = !critical_native && !fast_native; // Run the visitor and update sp. BuildGenericJniFrameVisitor visitor(self, called->IsStatic(), critical_native, shorty, shorty_len, &sp); { ScopedAssertNoThreadSuspension sants(__FUNCTION__); visitor.VisitArguments(); // FinalizeHandleScope pushes the handle scope on the thread. visitor.FinalizeHandleScope(self); } // Fix up managed-stack things in Thread. After this we can walk the stack. self->SetTopOfStackTagged(sp); self->VerifyStack(); // We can now walk the stack if needed by JIT GC from MethodEntered() for JIT-on-first-use. jit::Jit* jit = runtime->GetJit(); if (jit != nullptr) { jit->MethodEntered(self, called); } uint32_t cookie; uint32_t* sp32; // Skip calling JniMethodStart for @CriticalNative. if (LIKELY(!critical_native)) { // Start JNI, save the cookie. if (called->IsSynchronized()) { DCHECK(normal_native) << " @FastNative and synchronize is not supported"; cookie = JniMethodStartSynchronized(visitor.GetFirstHandleScopeJObject(), self); if (self->IsExceptionPending()) { self->PopHandleScope(); // A negative value denotes an error. return GetTwoWordFailureValue(); } } else { if (fast_native) { cookie = JniMethodFastStart(self); } else { DCHECK(normal_native); cookie = JniMethodStart(self); } } sp32 = reinterpret_cast<uint32_t*>(sp); *(sp32 - 1) = cookie; } // Retrieve the stored native code. void const* nativeCode = called->GetEntryPointFromJni(); // There are two cases for the content of nativeCode: // 1) Pointer to the native function. // 2) Pointer to the trampoline for native code binding. // In the second case, we need to execute the binding and continue with the actual native function // pointer. DCHECK(nativeCode != nullptr); if (nativeCode == GetJniDlsymLookupStub()) { nativeCode = artFindNativeMethod(self); if (nativeCode == nullptr) { DCHECK(self->IsExceptionPending()); // There should be an exception pending now. // @CriticalNative calls do not need to call back into JniMethodEnd. if (LIKELY(!critical_native)) { // End JNI, as the assembly will move to deliver the exception. jobject lock = called->IsSynchronized() ? visitor.GetFirstHandleScopeJObject() : nullptr; if (shorty[0] == 'L') { artQuickGenericJniEndJNIRef(self, cookie, fast_native, nullptr, lock); } else { artQuickGenericJniEndJNINonRef(self, cookie, fast_native, lock); } } return GetTwoWordFailureValue(); } // Note that the native code pointer will be automatically set by artFindNativeMethod(). } #if defined(__mips__) && !defined(__LP64__) // On MIPS32 if the first two arguments are floating-point, we need to know their types // so that art_quick_generic_jni_trampoline can correctly extract them from the stack // and load into floating-point registers. // Possible arrangements of first two floating-point arguments on the stack (32-bit FPU // view): // (1) // | DOUBLE | DOUBLE | other args, if any // | F12 | F13 | F14 | F15 | // | SP+0 | SP+4 | SP+8 | SP+12 | SP+16 // (2) // | DOUBLE | FLOAT | (PAD) | other args, if any // | F12 | F13 | F14 | | // | SP+0 | SP+4 | SP+8 | SP+12 | SP+16 // (3) // | FLOAT | (PAD) | DOUBLE | other args, if any // | F12 | | F14 | F15 | // | SP+0 | SP+4 | SP+8 | SP+12 | SP+16 // (4) // | FLOAT | FLOAT | other args, if any // | F12 | F14 | // | SP+0 | SP+4 | SP+8 // As you can see, only the last case (4) is special. In all others we can just // load F12/F13 and F14/F15 in the same manner. // Set bit 0 of the native code address to 1 in this case (valid code addresses // are always a multiple of 4 on MIPS32, so we have 2 spare bits available). if (nativeCode != nullptr && shorty != nullptr && shorty_len >= 3 && shorty[1] == 'F' && shorty[2] == 'F') { nativeCode = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(nativeCode) | 1); } #endif VLOG(third_party_jni) << "GenericJNI: " << called->PrettyMethod() << " -> " << std::hex << reinterpret_cast<uintptr_t>(nativeCode); // Return native code addr(lo) and bottom of alloca address(hi). return GetTwoWordSuccessValue(reinterpret_cast<uintptr_t>(visitor.GetBottomOfUsedArea()), reinterpret_cast<uintptr_t>(nativeCode)); }jni_entrypoints.cc中的artFindNativeMethod如下,会调用FindCodeForNativeMethod寻找ArtMethod对应的native函数// Used by the JNI dlsym stub to find the native method to invoke if none is registered. extern "C" const void* artFindNativeMethod(Thread* self) { DCHECK_EQ(self, Thread::Current()); Locks::mutator_lock_->AssertNotHeld(self); // We come here as Native. ScopedObjectAccess soa(self); ArtMethod* method = self->GetCurrentMethod(nullptr); DCHECK(method != nullptr); // Lookup symbol address for method, on failure we'll return null with an exception set, // otherwise we return the address of the method we found. void* native_code = soa.Vm()->FindCodeForNativeMethod(method); if (native_code == nullptr) { self->AssertPendingException(); return nullptr; } // Register so that future calls don't come here return method->RegisterNative(native_code); }java_vm_ext.cc中FindCodeForNativeMethod的实现如下,寻找native函数,找不到就抛出java.lang.UnsatisfiedLinkError错误void *JavaVMExt::FindCodeForNativeMethod(ArtMethod * m) { CHECK(m->IsNative()); ObjPtr <mirror::Class> c = m->GetDeclaringClass(); // If this is a static method, it could be called before the class has been initialized. CHECK(c->IsInitializing()) << c->GetStatus() << " " << m->PrettyMethod(); std::string detail; Thread *const self = Thread::Current(); void *native_method = libraries_->FindNativeMethod(self, m, detail); if (native_method == nullptr) { // Lookup JNI native methods from native TI Agent libraries. See runtime/ti/agent.h for more // information. Agent libraries are searched for native methods after all jni libraries. native_method = FindCodeForNativeMethodInAgents(m); } // Throwing can cause libraries_lock to be reacquired. if (native_method == nullptr) { LOG(ERROR) << detail; self->ThrowNewException("Ljava/lang/UnsatisfiedLinkError;", detail.c_str()); } return native_method; }以下是寻找的具体过程,首先会获取两个函数的名字,
jni_short_name和jni_long_name,也就是日志中的Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1和Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1__,在动态库里找函数,找得到就回传,找不到就回传nullvoid* FindNativeMethod(Thread* self, ArtMethod* m, std::string& detail) REQUIRES(!Locks::jni_libraries_lock_) REQUIRES_SHARED(Locks::mutator_lock_) { std::string jni_short_name(m->JniShortName()); std::string jni_long_name(m->JniLongName()); const ObjPtr<mirror::ClassLoader> declaring_class_loader = m->GetDeclaringClass()->GetClassLoader(); ScopedObjectAccessUnchecked soa(Thread::Current()); void* const declaring_class_loader_allocator = Runtime::Current()->GetClassLinker()->GetAllocatorForClassLoader(declaring_class_loader); CHECK(declaring_class_loader_allocator != nullptr); // TODO: Avoid calling GetShorty here to prevent dirtying dex pages? const char* shorty = m->GetShorty(); { // Go to suspended since dlsym may block for a long time if other threads are using dlopen. ScopedThreadSuspension sts(self, kNative); // 在这里开始寻找函数 void* native_code = FindNativeMethodInternal(self, declaring_class_loader_allocator, shorty, jni_short_name, jni_long_name); // native函数找到了,回传这个函数 if (native_code != nullptr) { return native_code; } } // 找不到,补充错误信息 detail += "No implementation found for "; detail += m->PrettyMethod(); detail += " (tried " + jni_short_name + " and " + jni_long_name + ")"; return nullptr; }art_method.cc中,std::string ArtMethod::JniShortName内部调用了GetJniShortNamestd::string ArtMethod::JniShortName() { return GetJniShortName(GetDeclaringClassDescriptor(), GetName()); }我们来看看函数名是怎么产生的,这里是
descriptors_names.cc中,GetJniShortName的实现std::string GetJniShortName(const std::string& class_descriptor, const std::string& method) { // Remove the leading 'L' and trailing ';'... std::string class_name(class_descriptor); CHECK_EQ(class_name[0], 'L') << class_name; CHECK_EQ(class_name[class_name.size() - 1], ';') << class_name; class_name.erase(0, 1); class_name.erase(class_name.size() - 1, 1); std::string short_name; short_name += "Java_"; short_name += MangleForJni(class_name); short_name += "_"; short_name += MangleForJni(method); return short_name; }上一步抹掉了类签名的前缀
L及后缀;,下面的MangleForJni函数主要是把/修改为_std::string MangleForJni(const std::string& s) { std::string result; size_t char_count = CountModifiedUtf8Chars(s.c_str()); const char* cp = &s[0]; for (size_t i = 0; i < char_count; ++i) { uint32_t ch = GetUtf16FromUtf8(&cp); if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) { result.push_back(ch); } else if (ch == '.' || ch == '/') { result += "_"; } else if (ch == '_') { result += "_1"; } else if (ch == ';') { result += "_2"; } else if (ch == '[') { result += "_3"; } else { const uint16_t leading = GetLeadingUtf16Char(ch); const uint32_t trailing = GetTrailingUtf16Char(ch); StringAppendF(&result, "_0%04x", leading); if (trailing != 0) { StringAppendF(&result, "_0%04x", trailing); } } } return result; }于是我们得到了
jni_short_name,而jni_long_name就是jni_short_name后面加一些格式化后的签名std::string ArtMethod::JniLongName() { std::string long_name; long_name += JniShortName(); long_name += "__"; std::string signature(GetSignature().ToString()); signature.erase(0, 1); signature.erase(signature.begin() + signature.find(')'), signature.end()); long_name += MangleForJni(signature); return long_name; }在所有加载了的库里,先以
jni_short_name找,找不到就以jni_long_name找,还找不到就是nullvoid* FindNativeMethodInternal(Thread* self, void* declaring_class_loader_allocator, const char* shorty, const std::string& jni_short_name, const std::string& jni_long_name) REQUIRES(!Locks::jni_libraries_lock_) REQUIRES(!Locks::mutator_lock_) { MutexLock mu(self, *Locks::jni_libraries_lock_); for (const auto& lib : libraries_) { SharedLibrary* const library = lib.second; // Use the allocator address for class loader equality to avoid unnecessary weak root decode. if (library->GetClassLoaderAllocator() != declaring_class_loader_allocator) { // We only search libraries loaded by the appropriate ClassLoader. continue; } // Try the short name then the long name... const char* arg_shorty = library->NeedsNativeBridge() ? shorty : nullptr; void* fn = library->FindSymbol(jni_short_name, arg_shorty); if (fn == nullptr) { fn = library->FindSymbol(jni_long_name, arg_shorty); } if (fn != nullptr) { VLOG(jni) << "[Found native code for " << jni_long_name << " in \"" << library->GetPath() << "\"]"; return fn; } } return nullptr; }以上就是静态注册的流程,在了解了静态注册后,再来看下动态注册是怎样进行的。
二、动态注册
在上述静态注册的描述中,我们可以看到,在jni_entrypoints.cc中的artFindNativeMethod函数里,ArtMethod会调用const void* ArtMethod::RegisterNative(const void* native_method)函数进行和native函数的绑定,而对于动态注册而言,它并没有这些生成函数名、dlsym什么的操作,我们在进行动态注册时已传入了函数指针,所以它会在进行一堆检查后直接调用RegisterNative注册。