ART虚拟机 | JNI调用的中间环节

6,046 阅读9分钟

本文分析基于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方法有两个显著的特点:

  1. 非static,属于实例方法。
  2. 有一个引用类型的参数。

2. Native方法汇编指令详解

从Java文件中看,native方法似乎是一个缺乏实体的存在。那么这样一个缺乏实体的方法经过dex2oat编译后,会产生相应的汇编指令么?

答案是肯定的,不仅会产生,而且汇编指令做的事情还不少。这些汇编指令并不涉及业务逻辑,而是为接下来的调用JNI函数搭建环境。具体而言,它的作用可以总结为以下两点。

  1. 将Java参数转换为C++参数,不仅是形式上的转换,参数数量上也会增加。
  2. 在真实的JNI函数前后分别调用JniMethodStartJniMethodEnd
    • 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

在详解之前,我们先按顺序将上述范例中机器码做的事罗列出来。

  1. 建立HandleScope,方便JNI函数判断哪些对象属于这一帧的作用范围,譬如参数传递时传入的entity对象,这些对象都会作为GC Roots的一部分。

  2. 将输入的引用参数放入HandleScope中。

  3. 更新quick frame的指针(机器执行产生的栈称为quick frame,解释执行产生的栈称为shadow frame)。

  4. 调用JniMethodStart函数,JniMethodStart会将线程状态从Runnable切换到Native,同时创建JNI函数的local reference table。Local reference table的主要作用是记录在JNI函数中使用到的Java对象,这些对象都是GC Roots的一部分,防止他们还在使用的时候被GC回收。

  5. 准备JNI函数的参数:

    1. 对原有的Java参数进行转换,基本类型参数直接传递,引用类型参数最终会转换为art::mirror::Object**传入JNI函数。jobject或jclass等效于一个指针,所以其可以承载art::mirror::Object**.
    2. 所有JNI函数的第一个参数为JNIEnv*,从art::Thread中取出并将其作为JNI函数的第一个参数。JNIEnv结构体内部存储了一系列函数指针,这些函数都是虚拟机操纵Java世界的重要函数。所以可以将JNIEnv*理解为调用虚拟机函数的重要入口。
    3. 对于实例方法而言,还有一个潜在的参数就是Java实例,将其作为JNI函数的第二个参数。
    4. 后面的参数依次顺延。
  6. 调用JNI函数。

  7. 将返回值存入栈中,以免接下来调用JniMethodEnd时破坏了x0寄存器。

  8. 调用JniMethodEnd函数,JniMethodEnd做的事和JniMethodStart恰恰相反,将线程状态恢复至Runnable,且删除该JNI函数的local reference table。这样一来,下一次GC就可以回收JNI函数刚刚使用过的对象,不至于产生堆内存泄露。

  9. 恢复之前的返回值到x0寄存器。

  10. 检测JNI函数中是否有抛出的异常,如果有则跳到pDeliverException进行相应处理。

  11. 退出native方法,x0寄存器记录的就是JNI函数的返回值。

2.2.1 HandleScope的作用

art/runtime/handle_scope.h

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中。

art/runtime/handle_scope.h

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。以下代码反映了这个事实。

art/runtime/thread.cc

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函数的函数指针。

art/runtime/art_method.h

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静态注册和动态注册背后的原理,以及两种注册方式和这两个函数指针间有着什么样的关联。