前言
要想深入地理解 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 函数可以总结为以下几个步骤: