Android JNI介绍(五)- 函数的注册

2,841 阅读8分钟

本系列文章列表:

Android JNI介绍(一)- 第一个Android JNI工程

Android JNI介绍(二)- 第一个JNI工程的详细分析

Android JNI介绍(三)- Java和Native的互相调用

Android JNI介绍(四)- 异常的处理

Android JNI介绍(五)- 函数的注册

Android JNI介绍(六)- 依赖其他库

Android JNI介绍(七)- 引用的管理

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_testExceptionCrash1Java_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.ccFindCodeForNativeMethod的实现如下,寻找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_namejni_long_name,也就是日志中的Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1__,在动态库里找函数,找得到就回传,找不到就回传null

      void* 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内部调用了GetJniShortName

        std::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找,还找不到就是null

      void* 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注册。



以上就是函数注册相关的具体介绍。