本文分析基于Android R(11)源码
市面上讲述JNI的文章,大多只介绍使用规则和注册方式,比如native方法和JNI函数之间参数应该如何对应,如何转换;或者静态注册和动态注册应该如何操作,各有什么优劣,等等。但看完这些文章,我依然存在一个疑惑:当我们在Java中调用native方法时,为什么瞬间就跳到了JNI函数?这中间到底还隐藏了哪些细节?
对于程序而言,了解它最直接的方式就是阅读汇编指令。虽然会有难度,但却是最本质的理解。这里我们通过Android自带的oatdump工具,从oat或odex文件中获取字节码编译所产生的汇编指令(这里不考虑解释执行的模式)。
1. 示例方法的基本介绍
本文挑选如下native方法作为范例,其从boot-framework.oat文件中解析得出:
frameworks/base/core/java/android/app/backup/BackupDataInput.java
198 private native int readNextHeader_native(long mBackupReader, EntityHeader entity); //native方法有两个参数,其对应的JNI函数有四个参数
对应的JNI函数如下所示:
frameworks/base/core/jni/android_backup_BackupDataInput.cpp
51 static jint
52 readNextHeader_native(JNIEnv* env, jobject clazz, jlong r, jobject entity) <================它有四个参数
53 {
......
91 }
Native方法和JNI函数之间的映射关系写入了如下g_methods[]数组:
frameworks/base/core/jni/android_backup_BackupDataInput.cpp
127 static const JNINativeMethod g_methods[] = {
......
130 { "readNextHeader_native", "(JLandroid/app/backup/BackupDataInput$EntityHeader;)I",
131 (void*)readNextHeader_native },
......
134 };
该native方法有两个显著的特点:
- 非static,属于实例方法。
- 有一个引用类型的参数。
2. Native方法汇编指令详解
从Java文件中看,native方法似乎是一个缺乏实体的存在。那么这样一个缺乏实体的方法经过dex2oat编译后,会产生相应的汇编指令么?
答案是肯定的,不仅会产生,而且汇编指令做的事情还不少。这些汇编指令并不涉及业务逻辑,而是为接下来的调用JNI函数搭建环境。具体而言,它的作用可以总结为以下两点。
- 将Java参数转换为C++参数,不仅是形式上的转换,参数数量上也会增加。
- 在真实的JNI函数前后分别调用
JniMethodStart和JniMethodEnd。JniMethodStart会将线程状态从Runnable切换到Native,同时创建JNI函数的local reference table。Local reference table的主要作用是记录在JNI函数中用到的Java对象,防止他们在使用的时候被GC回收。JniMethodEnd做的事和JniMethodStart恰恰相反,它将线程状态恢复至Runnable,且删除该JNI函数的local reference table。这样一来,下次GC就可以回收JNI函数刚刚使用过的对象,不至于产生堆内存泄露。
以下是其他Java方法调用native方法时实际发生的跳转过程。
2.1 Native方法的编译过程
下面展示dex2oat是如何为native方法生成汇编指令的,通过CompileMethodQuick=>OptimizingCompiler::JniCompile=>ArtQuickJniCompileMethod,最终调用ArtJniCompileMethodInternal来生成具体的机器码。
art/compiler/jni/quick/jni_compiler.cc
108 // Generate the JNI bridge for the given method, general contract:
109 // - Arguments are in the managed runtime format, either on stack or in
110 // registers, a reference to the method object is supplied as part of this
111 // convention.
112 //
113 template <PointerSize kPointerSize>
114 static JniCompiledMethod ArtJniCompileMethodInternal(const CompilerOptions& compiler_options,
115 uint32_t access_flags,
116 uint32_t method_idx,
117 const DexFile& dex_file) {
......
219 // 1. Build the frame saving all callee saves, Method*, and PC return address.
220 // For @CriticalNative, this includes space for out args, otherwise just the managed frame.
......
234 // 2. Set up the HandleScope
......
248 // 3. Place incoming reference arguments into handle scope
......
299 // 4. Write out the end of the quick frames.
......
308 // 5. Move frame down to allow space for out going args.
......
383 // 6. Call into appropriate JniMethodStart passing Thread* so that transition out of Runnable
384 // can occur. The result is the saved JNI local state that is restored by the exit call. We
385 // abuse the JNI calling convention here, that is guaranteed to support passing 2 pointer
386 // arguments.
......
434 // 7. Iterate over arguments placing values from managed calling convention in
435 // to the convention required for a native call (shuffling). For references
436 // place an index/pointer to the reference after checking whether it is
437 // null (which must be encoded as null).
438 // Note: we do this prior to materializing the JNIEnv* and static's jclass to
439 // give as many free registers for the shuffle as possible.
......
492 if (LIKELY(!is_critical_native)) {
493 // 8. Create 1st argument, the JNI environment ptr.
494 // Register that will hold local indirect reference table
......
506
507 // 9. Plant call to native code associated with method.
......
526 // 10. Fix differences in result widths.
......
542 // 11. Process return value
......
590 // 12. Call JniMethodEnd
......
635 // 13. Reload return value
......
644 // 14. Move frame up now we're done with the out arg space.
645 // @CriticalNative remove out args together with the frame in RemoveFrame().
......
651 // 15. Process pending exceptions from JNI call or monitor exit.
652 // @CriticalNative methods do not need exception poll in the stub.
......
657 // 16. Remove activation - need to restore callee save registers since the GC may have changed
658 // them.
......
668 // 17. Finalize code generation
......
681 }
ArtJniCompileMethodInternal源码中的注释写的很详细,总共分为17个步骤,每个步骤都会生成相应的机器码。但在我们下面的例子中,有些步骤是多余的。
2.2 Native方法生成的机器码详解
[readNextHeader_native编译产生的汇编指令]
4: int android.app.backup.BackupDataInput.readNextHeader_native(long, android.app.backup.BackupDataInput$EntityHeader) (dex_method_idx=16258)
DEX CODE:
OatMethodOffsets (offset=0x00046b4c)
code_offset: 0x001f52d0
OatQuickMethodHeader (offset=0x001f52c8)
vmap_table: (offset=0x0017431d)
QuickMethodFrameInfo
frame_size_in_bytes: 208
core_spill_mask: 0x7ff80000 (r19, r20, r21, r22, r23, r24, r25, r26, r27, r28, r29, r30)
fp_spill_mask: 0x0000ff00 (fr8, fr9, fr10, fr11, fr12, fr13, fr14, fr15)
CODE: (code_offset=0x001f52d0 size_offset=0x001f52cc size=264)...
0x001f52d0: d10343ff sub sp, sp, #0xd0 (208)
0x001f52d4: a90753f3 stp tr, x20, [sp, #112] //tr表示thread register,其是x19寄存器的别名,用于存储art::Thread对象的指针
0x001f52d8: a9085bf5 stp x21, x22, [sp, #128]
0x001f52dc: a90963f7 stp x23, x24, [sp, #144]
0x001f52e0: a90a6bf9 stp x25, x26, [sp, #160]
0x001f52e4: a90b73fb stp x27, x28, [sp, #176]
0x001f52e8: a90c7bfd stp x29, lr, [sp, #192] //x19-x29在AArch64的架构下属于callee-saved registers,因此需要在方法开头进行压栈保存,以免丢失原本存储于寄存器中的数据
0x001f52ec: 6d0327e8 stp d8, d9, [sp, #48]
0x001f52f0: 6d042fea stp d10, d11, [sp, #64]
0x001f52f4: 6d0537ec stp d12, d13, [sp, #80]
0x001f52f8: 6d063fee stp d14, d15, [sp, #96]
0x001f52fc: f90003e0 str x0, [sp] //对于所有Java方法而言,其接收到的第一个参数(x0)实际上是art::ArtMethod(C++)对象的指针。该ArtMethod对象是此Java方法在虚拟机层面的表征
0x001f5300: b900dbe1 str w1, [sp, #216] //对于Java的实例方法而言,第二个参数(w1)实际上是art::mirror::Object(C++)对象的指针,该对象是BackupDataInput(Java)对象在虚拟机中的表征,目前不论是32位还是64位机器,ART虚拟机中art::mirror::Object对象都存放在低32位地址可以寻址到的地址空间中
0x001f5304: f80dc3e2 stur x2, [sp, #220] //第三个参数(x2)才是本方法真实的第一个传入参数mBackupReader,为long类型
0x001f5308: b900e7e3 str w3, [sp, #228] //第四个参数(w3)是本方法真实的第二个传入参数entity,为android.app.backup.BackupDataInput$EntityHeader类型
0x001f530c: d2800050 mov x16, #0x2 //BaseHandleScope::number_of_references_ = 2,表示传入参数中有2个引用,即w1和w3
0x001f5310: b90013f0 str w16, [sp, #16]
0x001f5314: f9409670 ldr x16, [tr, #296] ; top_handle_scope //top_handle_scope用于判定哪个对象属于这一帧的作用域范围
0x001f5318: f90007f0 str x16, [sp, #8]
0x001f531c: 910023f0 add x16, sp, #0x8 (8)
0x001f5320: f9009670 str x16, [tr, #296] ; top_handle_scope
0x001f5324: b940dbf0 ldr w16, [sp, #216] //之前BackupDataInput(Java)对象对应的art::mirror::Object(C++)对象指针存入了sp+216的位置,此处再取出来存入w16
0x001f5328: b90017f0 str w16, [sp, #20] //将其转存一份到sp+20的位置
0x001f532c: b940e7f0 ldr w16, [sp, #228] //之前参数entity(Java)对象所对应的art::mirror::Object(C++)指针存入了sp+228的位置,此处再取出来存入w16
0x001f5330: b9001bf0 str w16, [sp, #24] //将其转存一份到sp+24的位置
0x001f5334: 910003f0 mov x16, sp
0x001f5338: f9005e70 str x16, [tr, #184] ; top_quick_frame_method
0x001f533c: aa1303e0 mov x0, tr //将art::Thread对象的指针存入x0寄存器
0x001f5340: f9419c10 ldr x16, [x0, #824] //art::Thread对象偏移824的字段是pJniMethodStart,为函数指针
0x001f5344: d63f0200 blr x16 //调用pJniMethodStart指向的函数
0x001f5348: b9001fe0 str w0, [sp, #28] //将pJniMethodStart的返回值存入sp+28的位置,返回值为saved_local_ref_cookie,4字节,用于local reference table的创建和销毁
0x001f534c: b9401be3 ldr w3, [sp, #24] //将参数android.app.backup.BackupDataInput$EntityHeader(Java)对象所对应的art::mirror::Object(C++)指针存入w3,将其作为接下来JNI函数的第四个参数
0x001f5350: 7100007f cmp w3, #0x0 (0)
0x001f5354: 910063f0 add x16, sp, #0x18 (24)
0x001f5358: 9a831203 csel x3, x16, x3, ne //如果w3不为0,则x3将存入art::mirror::Object指针的指针,否则存入0
0x001f535c: f84dc3e2 ldur x2, [sp, #220] //将原先存入栈中的long类型参数取出,存入x2,作为接下来JNI函数的第三个参数
0x001f5360: 910053e1 add x1, sp, #0x14 (20) //sp+20的位置存储的是art::mirror::Object的指针,因此这条指令结束后x1存储的是指针的指针,将其作为接下来JNI函数的第二个参数
0x001f5364: f9406e60 ldr x0, [tr, #216] ; jni_env//从art::Thread对象中取出jni_env字段,其类型为JNIEnv*,将其作为接下来JNI函数的第一个参数
0x001f5368: f94003f0 ldr x16, [sp] //之前将art::ArtMethod对象的指针存入了sp指向的位置,此处将其取回,存入x16寄存器
0x001f536c: f9400e10 ldr x16, [x16, #24] //ArtMethod对象偏移24的字段为ptr_sized_fields_.data_,其为JNI函数的函数指针
0x001f5370: d63f0200 blr x16 //调用C++中的实现函数
0x001f5374: b90023e0 str w0, [sp, #32] //将返回值(int类型)存入sp+32的位置
0x001f5378: b9401fe0 ldr w0, [sp, #28] //将之前存入的saved_local_ref_cookie作为接下来调用的第一个传入参数
0x001f537c: aa1303e1 mov x1, tr //将art::Thread指针作为接下来调用的第二个传入参数
0x001f5380: f941a830 ldr x16, [x1, #848] //art::Thread对象偏移848的字段是pJniMethodEnd,为函数指针
0x001f5384: d63f0200 blr x16 //调用pJniMethodEnd指向的函数
0x001f5388: b94023e0 ldr w0, [sp, #32] //将C++实现函数的返回值再次存入w0,这样调用ret返回时,上一帧方法便可以通过w0来获取返回值
0x001f538c: f9405670 ldr x16, [tr, #168] ; exception //检测在JNI函数中是否发生过exception
0x001f5390: b50001d0 cbnz x16, #+0x38 (addr 0x1f53c8)
0x001f5394: a94753f3 ldp tr, x20, [sp, #112] //将原先保存的callee-saved registers恢复到相应的寄存器中
0x001f5398: a9485bf5 ldp x21, x22, [sp, #128]
0x001f539c: a94963f7 ldp x23, x24, [sp, #144]
0x001f53a0: a94a6bf9 ldp x25, x26, [sp, #160]
0x001f53a4: a94b73fb ldp x27, x28, [sp, #176]
0x001f53a8: a94c7bfd ldp x29, lr, [sp, #192]
0x001f53ac: 6d4327e8 ldp d8, d9, [sp, #48]
0x001f53b0: 6d442fea ldp d10, d11, [sp, #64]
0x001f53b4: 6d4537ec ldp d12, d13, [sp, #80]
0x001f53b8: 6d463fee ldp d14, d15, [sp, #96]
0x001f53bc: b9403674 ldr w20, [tr, #52] ; is_gc_marking
0x001f53c0: 910343ff add sp, sp, #0xd0 (208)
0x001f53c4: d65f03c0 ret
0x001f53c8: aa1003e0 mov x0, x16 //如果JNI函数中有异常发生,则跳到此处来进一步处理
0x001f53cc: f9429a71 ldr x17, [tr, #1328] ; pDeliverException
0x001f53d0: d63f0220 blr x17
0x001f53d4: d4200000 brk #0x0
在详解之前,我们先按顺序将上述范例中机器码做的事罗列出来。
-
建立HandleScope,方便JNI函数判断哪些对象属于这一帧的作用范围,譬如参数传递时传入的entity对象,这些对象都会作为GC Roots的一部分。
-
将输入的引用参数放入HandleScope中。
-
更新quick frame的指针(机器执行产生的栈称为quick frame,解释执行产生的栈称为shadow frame)。
-
调用JniMethodStart函数,JniMethodStart会将线程状态从Runnable切换到Native,同时创建JNI函数的local reference table。Local reference table的主要作用是记录在JNI函数中使用到的Java对象,这些对象都是GC Roots的一部分,防止他们还在使用的时候被GC回收。
-
准备JNI函数的参数:
- 对原有的Java参数进行转换,基本类型参数直接传递,引用类型参数最终会转换为art::mirror::Object**传入JNI函数。jobject或jclass等效于一个指针,所以其可以承载art::mirror::Object**.
- 所有JNI函数的第一个参数为JNIEnv*,从art::Thread中取出并将其作为JNI函数的第一个参数。JNIEnv结构体内部存储了一系列函数指针,这些函数都是虚拟机操纵Java世界的重要函数。所以可以将JNIEnv*理解为调用虚拟机函数的重要入口。
- 对于实例方法而言,还有一个潜在的参数就是Java实例,将其作为JNI函数的第二个参数。
- 后面的参数依次顺延。
-
调用JNI函数。
-
将返回值存入栈中,以免接下来调用JniMethodEnd时破坏了x0寄存器。
-
调用JniMethodEnd函数,JniMethodEnd做的事和JniMethodStart恰恰相反,将线程状态恢复至Runnable,且删除该JNI函数的local reference table。这样一来,下一次GC就可以回收JNI函数刚刚使用过的对象,不至于产生堆内存泄露。
-
恢复之前的返回值到x0寄存器。
-
检测JNI函数中是否有抛出的异常,如果有则跳到pDeliverException进行相应处理。
-
退出native方法,x0寄存器记录的就是JNI函数的返回值。
2.2.1 HandleScope的作用
91 // HandleScopes are scoped objects containing a number of Handles. They are used to allocate
92 // handles, for these handles (and the objects contained within them) to be visible/roots for the
93 // GC. It is most common to stack allocate HandleScopes using StackHandleScope.
根据源码注释可知,HandleScope中存有一系列handle。而这些Handle实际上就是指针,指向art::mirror::Object对象。Handle的数量也存在HandleScope中,用变量number_of_references_单独记录。
所有记录在HandleScope中的对象都会作为GC Roots,保证他们不会在JNI函数使用时被回收,这是HandleScope最重要的作用。另外,每一个HandleScope只跟一次JNI调用关联,所以它被设计成链表结构,方便追踪一个线程创建的所有HandleScope。整个链表的头部存在art::Thread*->tlsPtr_.top_handle_scope中。
81 // Link-list of handle scopes. The root is held by a Thread.
82 BaseHandleScope* const link_;
83
84 // Number of handlerized references. -1 for variable sized handle scopes.
85 const int32_t number_of_references_;
[汇编指令]
0x001f530c: d2800050 mov x16, #0x2 //BaseHandleScope::number_of_references_ = 2,表示传入参数中有2个引用,即w1和w3
0x001f5310: b90013f0 str w16, [sp, #16]
0x001f5314: f9409670 ldr x16, [tr, #296] ; top_handle_scope //top_handle_scope用于判定哪个对象属于这一帧的作用域范围
0x001f5318: f90007f0 str x16, [sp, #8]
0x001f531c: 910023f0 add x16, sp, #0x8 (8)
0x001f5320: f9009670 str x16, [tr, #296] ; top_handle_scope
0x001f5324: b940dbf0 ldr w16, [sp, #216] //之前BackupDataInput(Java)对象对应的art::mirror::Object(C++)对象指针存入了sp+216的位置,此处再取出来存入w16
0x001f5328: b90017f0 str w16, [sp, #20] //将其转存一份到sp+20的位置
0x001f532c: b940e7f0 ldr w16, [sp, #228] //之前参数android.app.backup.BackupDataInput$EntityHeader(Java)对象所对应的art::mirror::Object(C++)指针存入了sp+228的位置,此处再取出来存入w16
0x001f5330: b9001bf0 str w16, [sp, #24] //将其转存一份到sp+24的位置
[HandleScope创建完成后的栈结构]
从上图可以看出,art::mirror::Object的指针仅为4个字节。这是因为所有堆内存都强制限定在低32位的地址空间中,即便在64位的机器上。所以最后只需在4字节的地址前补零便可以用于寻址。
2.2.2 top_quick_frame_method的作用
[汇编指令]
0x001f5334: 910003f0 mov x16, sp
0x001f5338: f9005e70 str x16, [tr, #184] ; top_quick_frame_method
[top_quick_frame_method设定完成后的栈结构]
从上图可以看出,top_quick_frame_method的类型为ArtMethod**,其作用是方便代码获取本帧的ArtMethod对象。此外,在Java栈帧恢复的过程中,top_quick_frame_method通常也是栈遍历的起点。
2.2.3 JniMethodStart
[汇编指令]
0x001f533c: aa1303e0 mov x0, tr //将art::Thread对象的指针存入x0寄存器
0x001f5340: f9419c10 ldr x16, [x0, #824] //art::Thread对象偏移824的字段是pJniMethodStart,为函数指针
0x001f5344: d63f0200 blr x16 //调用pJniMethodStart指向的函数
0x001f5348: b9001fe0 str w0, [sp, #28] //将pJniMethodStart的返回值存入sp+28的位置,返回值为saved_local_ref_cookie,4字节,用于local reference table的创建和销毁
JniMethodStart函数需要一个输入参数,类型为art::Thread*。对于汇编指令而言,x0默认存储第一个参数。因此mov x0, tr将art::Thread指针存入x0与预期相符。
art/runtime/entrypoints/quick/quick_default_init_entrypoints.h
74 // JNI
75 qpoints->pJniMethodStart = JniMethodStart;
art/runtime/entrypoints/quick/quick_jni_entrypoints.cc
65 // Called on entry to JNI, transition out of Runnable and release share of mutator_lock_.
66 extern uint32_t JniMethodStart(Thread* self) {
67 JNIEnvExt* env = self->GetJniEnv();
68 DCHECK(env != nullptr);
69 uint32_t saved_local_ref_cookie = bit_cast<uint32_t>(env->GetLocalRefCookie()); //将之前的local reference cookie存下来,等该native方法返回时恢复
70 env->SetLocalRefCookie(env->GetLocalsSegmentState()); //更新本次的local reference cookie = 上一个segment的末尾
71 ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame();
72 // TODO: Introduce special entrypoint for synchronized @FastNative methods?
73 // Or ban synchronized @FastNative outright to avoid the extra check here?
74 DCHECK(!native_method->IsFastNative() || native_method->IsSynchronized());
75 if (!native_method->IsFastNative()) {
76 // When not fast JNI we transition out of runnable.
77 self->TransitionFromRunnableToSuspended(kNative);
78 }
79 return saved_local_ref_cookie;
80 }
还记得我们刚刚说过top_quick_frame_method里面存的是ArtMethod**么?这个值在上面第71行起了作用。
2.2.3.1 LocalReferenceTable
Local reference table的最终最终实现为IndirectReferenceTable。由于这个table与线程关联,因此会存入不止一帧的信息。每一帧的信息都是一个segment(段),一个segment里面有当前帧的所有local objects。因此其结构如下图所示。
和HandleScope一样,Local reference table中的所有对象也会计入GC Roots。以下代码反映了这个事实。
4013 template <bool kPrecise>
4014 void Thread::VisitRoots(RootVisitor* visitor) {
4015 const pid_t thread_id = GetThreadId();
4016 visitor->VisitRootIfNonNull(&tlsPtr_.opeer, RootInfo(kRootThreadObject, thread_id));
4017 if (tlsPtr_.exception != nullptr && tlsPtr_.exception != GetDeoptimizationException()) {
4018 visitor->VisitRoot(reinterpret_cast<mirror::Object**>(&tlsPtr_.exception),
4019 RootInfo(kRootNativeStack, thread_id));
4020 }
4021 if (tlsPtr_.async_exception != nullptr) {
4022 visitor->VisitRoot(reinterpret_cast<mirror::Object**>(&tlsPtr_.async_exception),
4023 RootInfo(kRootNativeStack, thread_id));
4024 }
4025 visitor->VisitRootIfNonNull(&tlsPtr_.monitor_enter_object, RootInfo(kRootNativeStack, thread_id));
4026 tlsPtr_.jni_env->VisitJniLocalRoots(visitor, RootInfo(kRootJNILocal, thread_id)); //Local reference table中的对象计入GC Roots
4027 tlsPtr_.jni_env->VisitMonitorRoots(visitor, RootInfo(kRootJNIMonitor, thread_id));
4028 HandleScopeVisitRoots(visitor, thread_id); //HandleScope中的对象计入GC Roots
2.2.3.2 线程状态切换
如果此native方法没有用@FastNative去注解的话(下面展示一个用它注解的例子),它就是一个普通的native方法。
libcore/ojluni/src/main/java/java/lang/Object.java
326 @FastNative
327 public final native void notify();
对于普通的native方法而言,他需要进行线程状态切换,由Runnable状态切换到Native状态。而FastNative则不需要。省去了线程切换的时间,自然也就“Fast”了。
线程切换到Native状态后就不允许再操作Java堆中的数据了。这个特性在GC和Dump Trace的过程中都得到了利用。那如果我确实需要在JNI函数中操作Java对象,怎么办?答案是在每次操作之前将线程状态切换为Runnable,操作完成后再切换回来。ART为我们提供了ScopedObjectAccess类来简化这个操作,所有线程切换的动作都在这个对象的构造和析构过程中完成。
2.2.4 调用JNI函数
[汇编指令]
0x001f534c: b9401be3 ldr w3, [sp, #24] //将参数entity(Java)对象所对应的art::mirror::Object(C++)指针存入w3,将其作为接下来JNI函数的第四个参数
0x001f5350: 7100007f cmp w3, #0x0 (0)
0x001f5354: 910063f0 add x16, sp, #0x18 (24)
0x001f5358: 9a831203 csel x3, x16, x3, ne //如果w3不为0,则x3将存入art::mirror::Object指针的指针,否则存入0
0x001f535c: f84dc3e2 ldur x2, [sp, #220] //将原先存入栈中的long类型参数取出,存入x2,作为接下来JNI函数的第三个参数
0x001f5360: 910053e1 add x1, sp, #0x14 (20) //sp+20的位置存储的是art::mirror::Object的指针,因此这条指令结束后x1存储的是指针的指针,将其作为接下来JNI函数的第二个参数
0x001f5364: f9406e60 ldr x0, [tr, #216] ; jni_env//从art::Thread对象中取出jni_env字段,其类型为JNIEnv*,将其作为接下来JNI函数的第一个参数
0x001f5368: f94003f0 ldr x16, [sp] //之前将art::ArtMethod对象的指针存入了sp指向的位置,此处将其取回,存入x16寄存器
0x001f536c: f9400e10 ldr x16, [x16, #24] //ArtMethod对象偏移24的字段为ptr_sized_fields_.data_,其为JNI函数的函数指针
0x001f5370: d63f0200 blr x16 //调用C++中的实现函数
0x001f5374: b90023e0 str w0, [sp, #32] //将返回值(int类型)存入sp+32的位置
上面汇编代码的前半部分在准备JNI函数的参数,分别将4个参数存入x0,x1,x2,x3。接着寻找JNI函数的函数指针,通过blr的汇编指令跳转过去。
这里需要重点关注ArtMethod对象。它内部有个字段为结构体PtrSizdFields,这个结构体中的两个变量都可以存储函数指针。entry_point_from_quick_compiled_code_存储的是本native(Java)方法汇编指令的起始地址,而data_则存储的是接下来需要调用的JNI函数的函数指针。
835 // Must be the last fields in the method.
836 struct PtrSizedFields {
837 // Depending on the method type, the data is
838 // - native method: pointer to the JNI function registered to this method
839 // or a function to resolve the JNI function,
840 // - conflict method: ImtConflictTable,
841 // - abstract/interface method: the single-implementation if any,
842 // - proxy method: the original interface method or constructor,
843 // - other methods: the profiling data.
844 void* data_;
845
846 // Method dispatch from quick compiled code invokes this pointer which may cause bridging into
847 // the interpreter.
848 void* entry_point_from_quick_compiled_code_;
849 } ptr_sized_fields_;
2.2.5 JniMethodEnd
JniMethodEnd与JniMethodStart做的事恰好相反,不再赘述。
art/runtime/entrypoints/quick/quick_jni_entrypoints.cc
152 extern void JniMethodEnd(uint32_t saved_local_ref_cookie, Thread* self) {
153 GoToRunnable(self);
154 PopLocalReferences(saved_local_ref_cookie, self);
155 }
2.2.6 pDeliverException
关于Java方法的异常检测和异常处理,未来会专门成文,在此按下不表。
3. 总结
本文通过分析native方法编译生成的汇编指令,详细了解了JNI调用的过程。这些内容虽然有些晦涩,但对理解虚拟机和JNI过程中的转换至关重要。
另外,上文中分别阐述了ArtMethod中entry_point_from_quick_compiled_code_和data_的不同含义。理解了这一层后,接下来我打算写篇文章讲述JNI静态注册和动态注册背后的原理,以及两种注册方式和这两个函数指针间有着什么样的关联。