ART virtual machine_ JNI static registration and dynamic registration, deep analysis principle

bool JavaVMExt::IsBadJniVersion(int version) {
 // We don't support JNI_VERSION_1_1. These are the only other valid versions.
 return version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6;
} 

JNI_Onload function is a treasure place for dynamic registration. We can register in RegisterNatives. The specific methods and details are left in the chapter "dynamic registration".

native method in virtual machine

Generally speaking, a Java method can find two execution modes in the virtual machine, one is interpretation execution, and the other is machine code execution. When the interpretation is executed, the interpreter will look for the entry address of the bytecode. When the machine code is executed, the virtual opportunity looks for the entry address of the machine instruction. Considering that each Java method is represented by ArtMethod object in the virtual machine, the entry information of bytecode (indirectly) exists in its data_ Field, and the machine code entry information exists in entry_point_from_quick_compiled_code_ Field. As shown below.

// Must be the last fields in the method.
struct PtrSizedFields {
  // Depending on the method type, the data is
  //   - native method: pointer to the JNI function registered to this method
  //                    or a function to resolve the JNI function,
  //   - resolution method: pointer to a function to resolve the method and
  //                        the JNI function for @CriticalNative.
  //   - conflict method: ImtConflictTable,
  //   - abstract/interface method: the single-implementation if any,
  //   - proxy method: the original interface method or constructor,
  //   - other methods: during AOT the code item offset, at runtime a pointer
  //                    to the code item.
  void* data_;

  // Method dispatch from quick compiled code invokes this pointer which may cause bridging into
  // the interpreter.
  void* entry_point_from_quick_compiled_code_;
} ptr_sized_fields_; 

However, for the native method, it is only defined but not implemented in the Java world, so there will be no bytecode information. Although there is no need to store the entry address of bytecode, the native method will take one more step in the calling process.

  • First, enter a springboard function, which will handle the conversion from Java parameters to Native parameters and thread state switching.
  • Call the JNI function implemented in the Native world inside the springboard function.

In this way, there is no need to store data of bytecode entry information_ Field can be used to store the entry address of JNI function. And entry_point_from_quick_compiled_code_ Stored in is the entry address of the springboard function. For details, please refer to ART perspective | why can calling Native methods enter the C + + world.

Static registration

When we're not in JNI_ RegisterNatives is called in Onload, or JNI_ is not written in so library at all. When using the onload function, the mapping of the native method can only be completed by static registration.

Although the name of this process is "static registration", the actual registration is completed dynamically on demand at runtime. It is only called "static" because the mapping relationship is determined in advance.

What are the specific mapping rules?

JNI implements two sets of mapping rules, a simplified version and a complex version to solve method overloading. Finally, the converted function names are spliced in sequence according to the following rules.

  • Prefix Java_
  • Class name, convert / to_
  • Underscore connector_
  • Method name
  • If you need to distinguish between two overloaded methods, use a double underscore__ Connection parameter signature. If the method is not overloaded, omit this step.

In order to distinguish overloaded methods, parameter signatures need to be spliced at the end of the string, and there may be [,; and characters in the signature. In order not to appear these special characters in the function name (or not to be confused with the previous connector), these characters are specially processed during conversion.

  • _ Convert to_ one

  • ; Convert to_ two

  • [convert to _3]

Since Java class names and method names cannot start with numbers, such conversion will not conflict with Java class names or method names. Specific rule reference JNI documentation . The following is an example of a transformation.

package pkg;  

class Cls { 

     native double f(int i, String s); 

     ... 

} 

Convert to:

JNIEXPORT jdouble JNICALL Java_pkg_Cls_f__ILjava_lang_String_2 ( 

     JNIEnv *env,        /* interface pointer */ 

     jobject obj,        /* "this" pointer */ 

     jint i,             /* argument #1 */ 

     jstring s)          /* argument #2 */ { 

     /* Obtain a C-copy of the Java string */ 

     const char *str = (*env)->GetStringUTFChars(env, s, 0); 

     /* process the string */ 

     ... 

     /* Now we are done with str */ 

     (*env)->ReleaseStringUTFChars(env, s, str); 

     return ... 

} 

Note, however, that statically registered JNI functions must be modified by JNIEXPORT and JNICALL.

JNIEXPORT indicates that the function name is output to the dynamic symbol table so that it can be found by calling dlsym during subsequent registration. By default, all function names are output to the dynamic symbol table. However, for security, we can pass - fvisibility=hidden at compile time to turn off this output (JNIEXPORT will still output), so as to prevent others from knowing which functions are defined in so. This is particularly important for commercial software.

Jnical is mainly used to eliminate the difference of calling rules between different hardware platforms. For AArch64, jnical does not perform any action.

After introducing the rules, we will go deep into the specific process of registration.

As mentioned above, the data of ArtMethod object_ Field stores the entry address of the JNI function, and entry_point_from_quick_compiled_code_ Store the entry address of the springboard function. However, for static registration, the mapping relationship is not established until the first method call.

Before a method is called, first load the class to which it belongs. So during the time when the class is loaded into the first call of the method, data_ And entry_point_from_quick_compiled_code_ What is it?

When the class is loaded, the LinkCode function will be called to set the entry for the ArtMethod object_ point_ from_ quick_ compiled_ code_ And data_ Field.

static void LinkCode(ClassLinker* class_linker,
                     ArtMethod* method,
                     const OatFile::OatClass* oat_class,
                     uint32_t class_def_method_index) REQUIRES_SHARED(Locks::mutator_lock_) {
  ...
  const void* quick_code = nullptr;
  if (oat_class != nullptr) {
    // Every kind of method should at least get an invoke stub from the oat_method.
    // non-abstract methods also get their code pointers.
    const OatFile::OatMethod oat_method = oat_class->GetOatMethod(class_def_method_index);
    quick_code = oat_method.GetQuickCode();
  }
  ...
  if (quick_code == nullptr) {
    method->SetEntryPointFromQuickCompiledCode(  //set entry_point_from_quick_compiled_code_  field
        method->IsNative() ? GetQuickGenericJniStub() : GetQuickToInterpreterBridge());  
  }
  ...
  if (method->IsNative()) {
    // Set up the dlsym lookup stub. Do not go through `UnregisterNative()`
    // as the extra processing for @CriticalNative is not needed yet.
    method->SetEntryPointFromJni(  // set data_  field
        method->IsCriticalNative() ? GetJniDlsymLookupCriticalStub() : GetJniDlsymLookupStub());
  ...
  }
} 

entry_point_from_quick_compiled_code_ There are two possible values for:

  1. Because the springboard function is mainly responsible for parameter conversion, the same springboard function can be used for different native methods as long as the number and type of their parameters are the same. These springboard functions are generated only under AOT compilation conditions, so pure interpretation execution time quick_code == nullptr.
  2. When quick_ Entry when code = = nullptr_ point_ from_ quick_ compiled_ code_ The value set is art_ quick_ generic_ jni_ Pointer to the trampoline function. It is equivalent to a general springboard function, which dynamically converts parameters during execution.

data_ The value of (regardless of CriticalNative) is only possible:

  1. Set to art_jni_dlsym_lookup_stub function pointer. When the function is executed, it finds the JNI function according to the static conversion rules, and then jumps over. So the real registration takes place in it.

Next, let's look at the search process of JNI function. art_jni_dlsym_lookup_stub is assembly code. It will call artFindNaitveMethod to find the pointer of JNI function, and then jump to the JNI function through br x17 instruction.

ENTRY art_jni_dlsym_lookup_stub
    ...
    // Call artFindNativeMethod() for normal native and artFindNativeMethodRunnable()
    // for @FastNative or @CriticalNative.
    ...
    b.ne  .Llookup_stub_fast_or_critical_native
    bl    artFindNativeMethod
    b     .Llookup_stub_continue
    .Llookup_stub_fast_or_critical_native:
    bl    artFindNativeMethodRunnable
.Llookup_stub_continue:
    mov   x17, x0    // store result in scratch reg.
    ...
    cbz   x17, 1f   // is method code null ?
    br    x17       // if non-null, tail call to method's code.
1:
    ret             // restore regs and return to caller to handle exception.
END art_jni_dlsym_lookup_stub 

extern "C" const void* artFindNativeMethodRunnable(Thread* self)
REQUIRES_SHARED(Locks::mutator_lock_) {
...
const void* native_code = class_linker->GetRegisteredNative(self, method);
if (native_code != nullptr) {
return native_code;
}
...
JavaVMExt* vm = down_cast<JNIEnvExt*>(self->GetJniEnv())->GetVm();
native_code = vm->FindCodeForNativeMethod(method);
...
return class_linker->RegisterNative(self, method, native_code);
}

`artFindNativeMethod`The internal call is`artFindNativeMethodRunnable`,It first judges ArtMethod of`data_`Whether the field has been registered. If yes, it will be returned directly`data_`Stored function pointer. Otherwise, call`FindCodeForNativeMethod`Go find it. Finally, write the found function pointer to`data_`Field.

// See section 11.3 "Linking Native Methods" of the JNI spec.
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());
...
{
// Go to suspended since dlsym may block for a long time if other threads are using dlopen.
ScopedThreadSuspension sts(self, kNative);

Due to space reasons, this interview dictionary has been sorted into PDF documents. If you need a full set of complete documents of Android interview dictionary, please praise it!

CodeChina open source project address: https://codechina.csdn.net/m0_60958482/android_p7

In the open source project: Included in CodeChina , which contains self-study programming routes in different directions, interview questions collection / face experience, and a series of technical articles. The resources are constantly updated

https://codechina.csdn.net/m0_60958482/android_p7)**

[external chain picture transferring... (img-oexNY7Nx-1630397700097)]

In the open source project: Included in CodeChina , which contains self-study programming routes in different directions, interview questions collection / face experience, and a series of technical articles. The resources are constantly updated

Keywords: Java Android Design Pattern

Added by -[ webdreamer ] on Sat, 18 Dec 2021 00:43:31 +0200