List of articles in this series:

Introduction to Android JNI (1) – The first Android JNI project

Android JNI Introduction (ii) – A detailed analysis of the first JNI project

Android JNI introduction (iii) – Java and Native calls to each other

Android JNI Introduction (4) – Exception handling

Android JNI Introduction (5) – Function registration

Android JNI introduction (6) – Rely on other libraries

Android JNI Introduction (7) – Reference management

Android JNI Introduction (eight) – The use of CMakeLists


In the previous article, we have learned the basic JNI code writing method and exception handling, but the function registration part we just follow the rules, such as static registration, JNI is how to find native functions according to the Java_ package name _ class name _ function name this rule? This article will analyze this section.

One, static registration

We already know that JNI dynamically registers functions according to rules like Java_ package name _ class name _ function name, so where is this finding implemented? Let’s take a look at the crash log and analyze the crash log.

  • Change the function name to crash

    Crash log:

         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) 
    Copy the code

    Notice this sentence:

    (tried Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1 and Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1__)

  • Log analysis

    We did not pass Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1, Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1__ Character strings, that is, these two strings are generated internally, and then looking at the log, it’s easy to figure out which strings are variables, which strings are constants, For No implementation found for Java. Lang. String com. Wsy. Jnidemo. MainActivity. TestExceptionCrash1 () such a message, The following is naturally a dynamically generated string, and the previous No implementation Found for is an additional description that is normally written in code as a constant. Look at the Android source code, it’s easy to find, The void* FindNativeMethod(Thread* self, ArtMethod* m, STD ::string& detail) REQUIRES(! For Locks:: jni_LIBRaries_lock_) REQUIRES_SHARED(Locks::mutator_lock_), let’s scroll back into its context.

  • Code reading

    In Quick_trampoline_entrypoints. Cc, if the native function cache for this Java function exists, the search is stopped and returned directly. Otherwise, artFindNativeMethod is called

        /*
         * 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 thingsin 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 JniMethodStartfor @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.
                returnGetTwoWordFailureValue(); }}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 ifthe 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));
        }
    Copy the code

    The artFindNativeMethod in jni_entrypoints.cc is as follows. FindCodeForNativeMethod will be called to find the native function corresponding to ArtMethod

        // 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);
        }
    Copy the code

    Java_vm_ext. Cc FindCodeForNativeMethod implementation is as follows, looking for a native function, can’t find out the Java. Lang. UnsatisfiedLinkError error

        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;
        }
    Copy the code

    Jni_short_name = jni_long_name = jni_short_name Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1 and Java_com_wsy_jnidemo_MainActivity_testExceptionCrash1__ Find a function in the library, return if it can find it, return null if it can’t find it

    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); // 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; }Copy the code

    In art_method.cc, STD ::string ArtMethod::JniShortName calls GetJniShortName internally

        std::string ArtMethod::JniShortName() {
          return GetJniShortName(GetDeclaringClassDescriptor(), GetName());
        }
    Copy the code

    Let’s see how the function name is generated, so here’s descriptors_names.cc, implementation of GetJniShortName, okay

        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;
        }
    Copy the code

    The prefix L and suffix of the class signature are erased in the previous step. The following MangleForJni function mainly changes/to _

    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;
    }
    Copy the code

    So we get jni_short_name, and jni_long_name is jni_short_name followed by some formatted signature

        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;
        }
    Copy the code

    Select short_name from all loaded libraries as jni_long_name, null if not found

    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() << "\]" ";
            returnfn; }}return nullptr;
      }
    Copy the code

    Above is the static registration process, after understanding the static registration, look at the dynamic registration is how to proceed.

Second, dynamic registration

In the static registration description above, we can see that in the artFindNativeMethod function in jni_entrypoints.cc, ArtMethod invokes the const void * ArtMethod: : RegisterNative (const void * native_method) function and native functions of binding, for dynamic registration, It doesn’t have any of these operations to generate function names, DLSYm, etc. We passed in the function pointer when we did the dynamic registration, so it will call the RegisterNative registration directly after a bunch of checks.





Above is the function registration related specific introduction.