从 Native 函数调用 Java 函数

匿名 (未验证) 提交于 2019-12-02 21:53:52

要想深入地理解 art 虚拟机,那么理解 Java 方法在虚拟机当中是如何执行的是必不可少的一环。
本篇从 Native 函数调用 Java 函数角度来探讨一下 Java 函数在 art 虚拟机当中的执行。(基于 Android 8.1)

首先,我们用 gdb 将断点打在 art_quick_invoke_stub,观察一下 Native 函数 -> Java 函数的调用栈:

点击查看大图

我们可以看到 Native 函数调用 Java 函数最开始是要调用 env->CallBooleanMethod:

(gdb) f 6 #6  0x000000798f46c2d0 in JavaBBinder::onTransact (this=0x79032efa80, code=4, data=..., reply=0x78f55fd1c0, flags=17) at frameworks/base/core/jni/android_util_Binder.cpp:312 312         jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact, (gdb) list 307         const int32_t strict_policy_before = thread_state->getStrictModePolicy(); 308  309         //printf("Transact from %p to Java code sending: ", this); 310         //data.print(); 311         //printf("\n"); 312         jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact, 313             code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags); 314  315         if (env->ExceptionCheck()) { 316             jthrowable excep = env->ExceptionOccurred();

1、CallBooleanMethod

我们来看一下 CallBooleanMethod 定义:
libnativehelper/include_jni/jni.h

594#define CALL_TYPE_METHOD(_jtype, _jname)                                    \ 595    _jtype Call##_jname##Method(jobject obj, jmethodID methodID, ...)       \ 596    {                                                                       \ 597        _jtype result;                                                      \ 598        va_list args;                                                       \ 599        va_start(args, methodID);                                           \ 600        result = functions->Call##_jname##MethodV(this, obj, methodID,      \ 601                    args);                                                  \ 602        va_end(args);                                                       \ 603        return result;                                                      \ 604    } 605#define CALL_TYPE_METHODV(_jtype, _jname)                                   \ 606    _jtype Call##_jname##MethodV(jobject obj, jmethodID methodID,           \ 607        va_list args)                                                       \ 608    { return functions->Call##_jname##MethodV(this, obj, methodID, args); } 609#define CALL_TYPE_METHODA(_jtype, _jname)                                   \ 610    _jtype Call##_jname##MethodA(jobject obj, jmethodID methodID,           \ 611        jvalue* args)                                                       \ 612    { return functions->Call##_jname##MethodA(this, obj, methodID, args); } 613 614#define CALL_TYPE(_jtype, _jname)                                           \ 615    CALL_TYPE_METHOD(_jtype, _jname)                                        \ 616    CALL_TYPE_METHODV(_jtype, _jname)                                       \ 617    CALL_TYPE_METHODA(_jtype, _jname) 618 619    CALL_TYPE(jobject, Object) 620    CALL_TYPE(jboolean, Boolean) 621    CALL_TYPE(jbyte, Byte) 622    CALL_TYPE(jchar, Char) 623    CALL_TYPE(jshort, Short) 624    CALL_TYPE(jint, Int) 625    CALL_TYPE(jlong, Long) 626    CALL_TYPE(jfloat, Float) 627    CALL_TYPE(jdouble, Double)

可以看到其是通过宏来定义的,CALL_TYPE(jboolean, Boolean) 即相当于

595    jboolean CallBooleanMethod(jobject obj, jmethodID methodID, ...)        596    {                                                                        597        jboolean result;                                                       598        va_list args;                                                        599        va_start(args, methodID);                                            600        result = functions->CallBooleanMethodV(this, obj, methodID,       601                    args);                                                   602        va_end(args);                                                        603        return result;                                                       604    }

可以看到这里的作用主要是调整了一下参数,然后调用 JNIInvokeInterface 的 CallBooleanMethodV 方法,下面我们看一下 CallBooleanMethodV 的实现:
art/runtime/jni_internal.cc

817  static jboolean CallBooleanMethodV(JNIEnv* env, jobject obj, jmethodID mid, va_list args) { 818    CHECK_NON_NULL_ARGUMENT_RETURN_ZERO(obj); 819    CHECK_NON_NULL_ARGUMENT_RETURN_ZERO(mid); 820    ScopedObjectAccess soa(env); 821    return InvokeVirtualOrInterfaceWithVarArgs(soa, obj, mid, args).GetZ(); 822  }

需要注意的是 ScopedObjectAccess soa(env); 这一步 Thread 会完成到 kRunnable 状态的切换(通过一系列的构造函数)并且其会 Shared Locks::mutator_lock_

2、InvokeVirtualOrInterfaceWithVarArgs

art/runtime/reflection.cc

552JValue InvokeVirtualOrInterfaceWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, 553                                           jobject obj, jmethodID mid, va_list args) { 561  ... 562  ObjPtr<mirror::Object> receiver = soa.Decode<mirror::Object>(obj); 563  ArtMethod* method = FindVirtualMethod(receiver, jni::DecodeArtMethod(mid));      ... 570  uint32_t shorty_len = 0; 571  const char* shorty = 572      method->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetShorty(&shorty_len); 573  JValue result; 574  ArgArray arg_array(shorty, shorty_len); 575  arg_array.BuildArgArrayFromVarArgs(soa, receiver, args); 576  InvokeWithArgArray(soa, method, &arg_array, &result, shorty);      ... 581  return result; 582}

可以看到,这里主要做了四件事:

  • get shorty
  • prepare result
  • 构造参数并将其放在 arg_array 中
  • 调用 InvokeWithArgArray(soa, method, &arg_array, &result, shorty);

下面看一下是如何构造参数的:
art/runtime/reflection.cc

101  void BuildArgArrayFromVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, 102                                ObjPtr<mirror::Object> receiver, 103                                va_list ap) 104      REQUIRES_SHARED(Locks::mutator_lock_) { 105    // Set receiver if non-null (method is not static) 106    if (receiver != nullptr) { 107      Append(receiver); 108    } 109    for (size_t i = 1; i < shorty_len_; ++i) { 110      switch (shorty_[i]) { 111        case 'Z': 112        case 'B': 113        case 'C': 114        case 'S': 115        case 'I': 116          Append(va_arg(ap, jint)); 117          break; 118        case 'F': 119          AppendFloat(va_arg(ap, jdouble)); 120          break; 121        case 'L': 122          Append(soa.Decode<mirror::Object>(va_arg(ap, jobject))); 123          break; 124        case 'D': 125          AppendDouble(va_arg(ap, jdouble)); 126          break; 127        case 'J': 128          AppendWide(va_arg(ap, jlong)); 129          break; 130#ifndef NDEBUG 131        default: 132          LOG(FATAL) << "Unexpected shorty character: " << shorty_[i]; 133#endif 134      } 135    } 136  }

需要注意下面几个点:

  • 如果 receiver 不为 null,即 method 是非 static 的,那么 arg_array 中放入的第一个参数即为 receiver
  • 会根据参数对应的 shorty 中的值来决定以何种方式将参数放入 arg_array 中

下面看一下 InvokeWithArgArray 的实现:

446static void InvokeWithArgArray(const ScopedObjectAccessAlreadyRunnable& soa, 447                               ArtMethod* method, ArgArray* arg_array, JValue* result, 448                               const char* shorty) 449    REQUIRES_SHARED(Locks::mutator_lock_) { 450  uint32_t* args = arg_array->GetArray(); 451  if (UNLIKELY(soa.Env()->check_jni)) { 452    CheckMethodArguments(soa.Vm(), method->GetInterfaceMethodIfProxy(kRuntimePointerSize), args); 453  } 454  method->Invoke(soa.Self(), args, arg_array->GetNumBytes(), result, shorty); 455}

这里没什么可说的,就是调用 ArtMethod 的 Invoke 方法

3、art::ArtMethod::Invoke

art/runtime/art_method.cc

void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,                        const char* shorty) {   // Push a transition back into managed code onto the linked list in thread.   ManagedStack fragment;   self->PushManagedStackFragment(&fragment);    Runtime* runtime = Runtime::Current();   // Call the invoke stub, passing everything as arguments.   // If the runtime is not yet started or it is required by the debugger, then perform the   // Invocation by the interpreter, explicitly forcing interpretation over JIT to prevent   // cycling around the various JIT/Interpreter methods that handle method invocation.   if (UNLIKELY(!runtime->IsStarted() || Dbg::IsForcedInterpreterNeededForCalling(self, this))) {     ...   } else {     DCHECK_EQ(runtime->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);      constexpr bool kLogInvocationStartAndReturn = false;     bool have_quick_code = GetEntryPointFromQuickCompiledCode() != nullptr;     if (LIKELY(have_quick_code)) {       ...       if (!IsStatic()) {         (*art_quick_invoke_stub)(this, args, args_size, self, result, shorty);       } else {         (*art_quick_invoke_static_stub)(this, args, args_size, self, result, shorty);       }       ...     } else {       LOG(INFO) << "Not invoking '" << PrettyMethod() << "' code=null";       if (result != nullptr) {         result->SetJ(0);       }     }   }    // Pop transition.   self->PopManagedStackFragment(fragment); }

可以看到,正常情况下其会根据 method 是否为 static 来分别调用 (*art_quick_invoke_stub)(this, args, args_size, self, result, shorty) 和 (*art_quick_invoke_static_stub)(this, args, args_size, self, result, shorty) 方法,this 为 ArtMethod 对象指针

4、art_quick_invoke_stub

详细实现部分现在功力不够,之后有时间再分析;
可以看到无论是 art_quick_invoke_stub 还是 art_quick_invoke_static_stub 最终都会调用 INVOKE_STUB_CALL_AND_RETURN,其定义为:
arch/arm64/quick_entrypoints_arm64.S

.macro INVOKE_STUB_CALL_AND_RETURN      REFRESH_MARKING_REGISTER      // load method-> METHOD_QUICK_CODE_OFFSET     ldr x9, [x0, #ART_METHOD_QUICK_CODE_OFFSET_64]     // Branch to method.     blr x9     ...     ret  .endm

通过前面的分析,我们知道:

  • x0 中是想要调用的方法的 ArtMethod 的地址
  • ART_METHOD_QUICK_CODE_OFFSET_64 为 40,即 0x28

通过 gdb 可以看到:

(gdb) p (('art::ArtMethod'*)0)->ptr_sized_fields_ Cannot access memory at address 0x18 (gdb) ptype 'art::ArtMethod::PtrSizedFields' type = struct art::ArtMethod::PtrSizedFields {     art::mirror::MethodDexCacheType *dex_cache_resolved_methods_;     void *data_;     void *entry_point_from_quick_compiled_code_; } (gdb) p (('art::ArtMethod'*)0)->ptr_sized_fields_.entry_point_from_quick_compiled_code_ Cannot access memory at address 0x28

由此可知,这里是要跳转到对应 ArtMethod 的 entry_point_from_quick_compiled_code_ 处

通过上面的分析,从一个 Native 函数调用一个 Java 函数可以总结为以下几个步骤:

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!