从 Native 函数调用 Java 函数

前言

要想深入地理解 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 函数可以总结为以下几个步骤:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/u013989732/article/details/80670989