从 Java 调用一个 Native 函数

来源:互联网 发布:北京80坐标系数据 编辑:程序博客网 时间:2024/05/22 17:28

Java 调用Native函数,实际就是 JNI 调用。

我们将关注 Java端如何把参数传递到 Native,Java调用Native函数时,额外的做了哪些事情。


在前面分析Native 调用Java 函数时,直接打断点,就能得到调用 backtrace,那是得益于 GDB 对 Native代码的调试支持,

可以根据包含 symbols的 so库,自动帮忙我们理清 pc 对应的代码以及其所在文件,行号,函数等信息;这极大的帮助了我们分析流程;


而 Java 调用 Native 就不那么方便的调试了;因为:1.GDB 并不是调试 java 代码的工具 2.当 java代码存在native code时,其native code

是存放在 oat 文件中,虽然其是一个 ELF文件,但是其代码和都嵌入新建的 "oatdata" 和 “oatexec” 段,其组织结构并不能被 GDB 所识别;


JNI 方法的执行有两种情况:

1.在 java 中声明的 native 限定的方法在APP安装时,已经被编译了,那么从其他 java 方法调用该方法时,会直接跳转到其native code;

2.声明为 native 的java方法没有被编译,那么会经过 trampoline 以及 art_jni_dlsym_lookup_stub 配合完成调用;


在安装APP,编译dex时,根据compiler-filter决定是否编译 JNI method:

bool CompilerFilter::IsJniCompilationEnabled(Filter filter) {  switch (filter) {    case CompilerFilter::kVerifyNone:    case CompilerFilter::kVerifyAtRuntime: return false;     case CompilerFilter::kVerifyProfile:    case CompilerFilter::kInterpretOnly:    case CompilerFilter::kSpaceProfile:    case CompilerFilter::kSpace:    case CompilerFilter::kBalanced:    case CompilerFilter::kTime:    case CompilerFilter::kSpeedProfile:    case CompilerFilter::kSpeed:    case CompilerFilter::kEverythingProfile:    case CompilerFilter::kEverything: return true;  }  UNREACHABLE();}  static bool InstructionSetHasGenericJniStub(InstructionSet isa) {  switch (isa) {    case kArm:    case kArm64:    case kThumb2:    case kMips:    case kMips64:    case kX86:    case kX86_64: return true;    default: return false;  }}

如果compiler-filter是下面的 10种的一个,则必定会编译 JNI method;

如果是 kVerifyNone 或者 kVerifyAtRuntime ,且是所支持的指令集,则不会编译 JNI method;

在这里我们先分析第一种情况,即 Native java Method被编译的情况,此时,如果其他java 函数调用该函数,

会直接跳转到其被编译出来的 native 代码;


举个栗子:

SystemClock.java 的一个 native 函数 :    native public static long uptimeMillis();

Thread 1 "miui.yellowpage" hit Breakpoint 1, art::JniMethodStart (self=0x7f86e96a00) at art/runtime/entrypoints/quick/quick_jni_entrypoints.cc:3939    if (!native_method->IsFastNative()) {(gdb) bt#0  art::JniMethodStart (self=0x7f86e96a00) at art/runtime/entrypoints/quick/quick_jni_entrypoints.cc:39#1  0x00000000751aa338 in ?? ()Backtrace stopped: previous frame inner to this frame (corrupt stack?) (gdb) p self->tlsPtr_->managed_stack->top_quick_frame_$2 = (art::ArtMethod **) 0x7ff85caa80(gdb) x 0x7ff85caa800x7ff85caa80:   0x7147c530(gdb) art_get_method_name_by_method_id 0x7147c530android.os.SystemClock.uptimeMillis "()J"


JNI Method的编译是通过 ArtJniCompileMethodInternal 这个函数来为Jni Method生成代码的,我们先了解一下 JNI method的编译流程:


看下运行时 这个函数的Native code,实际上这个 code 就是 uptimeMillis() 这个函数被编译成native code在 oat文件中的存放;

72f2c000-748ae000 r--p 00000000 103:1d 2065 /system/framework/arm64/boot-framework.oat
748ae000-759d9000 r-xp 01982000 103:1d 2065 /system/framework/arm64/boot-framework.oat 
759d9000-759da000 r--p 02aad000 103:1d 2065 /system/framework/arm64/boot-framework.oat
759da000-759db000 rw-p 02aae000 103:1d 2065 /system/framework/arm64/boot-framework.oat

native public static long uptimeMillis();

   0x00000000751aa2c0:  .inst   0x00000000 ; undefined   0x00000000751aa2c4:  .inst   0x000000d0 ; undefined   0x00000000751aa2c8:  .inst   0x7ff80000 ; undefined   0x00000000751aa2cc:  .inst   0x0000ff00 ; undefined   0x00000000751aa2d0:  .inst   0x000000dc ; undefined   0x00000000751aa2d4:  sub sp, sp, #0xd0                           ; 1. Build the frame saving all callee saves   0x00000000751aa2d8:  stp x19, x20, [sp,#112]   0x00000000751aa2dc:  stp x21, x22, [sp,#128]   0x00000000751aa2e0:  stp x23, x24, [sp,#144]   0x00000000751aa2e4:  stp x25, x26, [sp,#160]   0x00000000751aa2e8:  stp x27, x28, [sp,#176]   0x00000000751aa2ec:  stp x29, x30, [sp,#192]   0x00000000751aa2f0:  stp d8, d9, [sp,#48]   0x00000000751aa2f4:  stp d10, d11, [sp,#64]   0x00000000751aa2f8:  stp d12, d13, [sp,#80]   0x00000000751aa2fc:  stp d14, d15, [sp,#96]   0x00000000751aa300:  str x0, [sp]                                ; 根据 ART method 调用约定:x0是 ArtMethod*,保存在在栈顶   0x00000000751aa304:  mov x20, #0x1                       // #1   ; 保存 local_ref 的个数到栈上,代表的reference,基础参数不计算在内   0x00000000751aa308:  str w20, [sp,#16]                           ; 16是因为栈顶 ArtMethod* 8 字节,HandleScope的 link_指针 8 字节 (gdb) p /d &((art::Thread*)0)->tlsPtr_->top_handle_scope$9 = 264    0x00000000751aa30c: ldr x20, [x19,#264]                         ; x19 是 Thread*,偏移 #264是 top_handle_scope   0x00000000751aa310:  str x20, [sp,#8]                            ; 把 top_handle_scope 拷贝到栈上   0x00000000751aa314:  add x20, sp, #0x8   0x00000000751aa318:  str x20, [x19,#264]                         ; top_handle_scope指向栈上对应地址   0x00000000751aa31c:  ldr w20, [x0]                               ; 保存 ArtMethod对应的 Class到栈上   0x00000000751aa320:  str w20, [sp,#20] (gdb) p /d &((art::Thread*)0)->tlsPtr_->managed_stack$16 = 152    0x00000000751aa324: mov x16, sp   0x00000000751aa328:  str x16, [x19,#152]                         ; managed_stack在 self 偏移#152,把其指向栈顶  (gdb) p /d &((art::Thread*)0)->tlsPtr_->quick_entrypoints->pJniMethodStart$20 = 784       0x00000000751aa32c: mov x0, x19   0x00000000751aa330:  ldr x20, [x0,#784]   0x00000000751aa334:  blr x20                                     ; 跳转到函数 JniMethodStart(Thread*)   0x00000000751aa338:  str w0, [sp,#24]                            ; JniMethodStart 返回值是 local_ref_cookie,保存在 sp+24   0x00000000751aa33c:  add x1, sp, #0x14                           ; 把 ArtMethod 对应的 Class 作为第二个参数放到 x1 此时栈: SP ->  |--------------------|        |     ArtMethod*     |        |--------------------|        | HandleScope* link_ |SP+16-> |--------------------|        |   num_of_ref_(1)   |SP+20-> |--------------------|        |      Class*        |SP+24-> |--------------------|        |      cookie        |        |--------------------|  (gdb) p /d &((art::Thread*)0)->tlsPtr_->jni_env$14 = 184      0x00000000751aa340: ldr  x0, [x19,#184]                         ; 把 jni_env 作为第一参数放到 x0   0x00000000751aa344:  ldr x20, [sp]   0x00000000751aa348:  ldr x20, [x20,#40]                          ; 从ArtMethod中取出 entry_point_from_jni_,并跳转到该函数,实际上entry_point_from_jni_指向JNI函数   0x00000000751aa34c:  blr x20 static jlong android_os_SystemClock_uptimeMillis(JNIEnv* env, jobject clazz);;这里为什么是 ArtMethod偏移 #40,从dump内存看,在 64bit,   uint16_t method_index_;和  uint16_t hotness_count_;都占了 4个字节:(gdb) x /20gx 0x7147c5300x7147c530: 0x00000109710807f8  0x0000c7f9000000000x7147c540: 0x0000000000000008  0x00000000715445880x7147c550: 0x000000007153cda8  0x0000007f8a9400d80x7147c560: 0x00000000751aa2d4 (gdb) p $sp$28 = (void *) 0x7ff85caa80(gdb) x 0x7ff85caa800x7ff85caa80:   0x7147c530(gdb) x /x 0x7147c530+40(gdb) disassemble 0x0000007f8a9400d8Dump of assembler code for function android::android_os_SystemClock_uptimeMillis(JNIEnv*, jobject):   0x0000007f8a9400d8 <+0>:   b   0x7f8a8dac70 <_ZN7android12uptimeMillisEv@plt>End of assembler dump.   0x00000000751aa350:  stur    x0, [sp,#28]                        ; 把 Native 函数的返回值保存在 sp+28   0x00000000751aa354:  ldr w0, [sp,#24]                            ; 获取 local_ref_cookie,作为第一个参数放在 w0   0x00000000751aa358:  mov x1, x19                                 ; Thread* 作为第二个参数   0x00000000751aa35c:  ldr x20, [x1,#800]                          ; 跳转到 JniMethodEnd(int cookie, Thread self);   0x00000000751aa360:  blr x20   0x00000000751aa364:  ldur    x0, [sp,#28]                        ; 返回值保存到 x0,准备 return   0x00000000751aa368:  ldr x20, [x19,#136]                         ; 查看 self->tlsPtr_->exception是否为 nullptr;若不是,则跳转到 0x751aa3a0进行处理exception   0x00000000751aa36c:  cbnz    x20, 0x751aa3a0     0x00000000751aa370:  ldp x19, x20, [sp,#112]                     ; 还原 callee save 寄存器   0x00000000751aa374:  ldp x21, x22, [sp,#128]   0x00000000751aa378:  ldp x23, x24, [sp,#144]   0x00000000751aa37c:  ldp x25, x26, [sp,#160]   0x00000000751aa380:  ldp x27, x28, [sp,#176]   0x00000000751aa384:  ldp x29, x30, [sp,#192]   0x00000000751aa388:  ldp d8, d9, [sp,#48]   0x00000000751aa38c:  ldp d10, d11, [sp,#64]   0x00000000751aa390:  ldp d12, d13, [sp,#80]   0x00000000751aa394:  ldp d14, d15, [sp,#96]   0x00000000751aa398:  add sp, sp, #0xd0                           ; pop frame   0x00000000751aa39c:  ret                                         ; return   0x00000000751aa3a0:  mov x0, x20                                 ; exception 作为第一个参数保存在 x0,此时 x1 已经是 Thread* 了   0x00000000751aa3a4:  ldr x16, [x19,#1240]                        ; 跳转到 Thread的 pDeliverException 成员记录的 artDeliverExceptionFromCode(mirror::Throwable* exception, Thread* self)函数   0x00000000751aa3a8:  blr x16   0x00000000751aa3ac:  brk #0x0   0x00000000751aa3b0:  .inst   0x00fd50fc ; undefined   0x00000000751aa3b4:  .inst   0x00000030 ; undefinedEnd of assembler dump.


栈空间:


Java :native public static long uptimeMillis();

Native:static jlong android_os_SystemClock_uptimeMillis(JNIEnv* env, jobject clazz);

java调用过来不带参数,而Native函数有两个参数,这两个参数的准备,上边汇编已经完成分析;


java 声明的 native 函数,被编译到oat后,代码基本组成大概如下:



可以看到,一个 JNI 函数做了很多的事情,在真正的 native 函数调用的前后分别有 JniMethodStart 和 JniMethodEnd 包住;

在这个过程中 JniMethodStart 时会保存 local reference cookie,如果native 函数不是 fastjni,也会切换线程状态  Runnable => Native :

extern uint32_t JniMethodStart(Thread* self) {  JNIEnvExt* env = self->GetJniEnv();  DCHECK(env != nullptr);  uint32_t saved_local_ref_cookie = env->local_ref_cookie;  env->local_ref_cookie = env->locals.GetSegmentState();  ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame();  if (!native_method->IsFastNative()) {    // When not fast JNI we transition out of runnable.    self->TransitionFromRunnableToSuspended(kNative);  }  return saved_local_ref_cookie;}



而后在JniMethodEnd 时,会切换线程状态到 Runnable;

另外会删除本次 JNI调用过程新建的 local reference,还原到 saved_local_ref_cookie 状态,

也就是说:JNI调用过程中添加 Local Reference会自动删除,不会泄露;

extern void JniMethodEnd(uint32_t saved_local_ref_cookie, Thread* self) {GoToRunnable(self);PopLocalReferences(saved_local_ref_cookie, self);}



说明:

  1. ArtMethod 的 entry_point_from_jni_ 填充:
    1.1 AndroidRuntime startVM()之后,会在 startReg()中注册 Android FW中的 JNI Method,会使得这个类被加载并将所有的native 函数指针设置到对应的java Method;
    1.2 在JNI_ONLOAD 函数中可以注册 JNI method 到 对应的 java Method
    1.3 对于没有被注册过的 java native 函数,采用 lazy load,对于没有 native code的 java native method会给其 entry_point_from_jni_ 或者 entry_point_from_quick_compiled_code_
          设置 art_quick_generic_jni_trampoline,在第一次调用该函数时,会通过 trampoline调用 1.artQuickGenericJniTrampoline(会调用JniMethodStart)在当前ClassLoader总的so library中查找对应的native method,
          如果找到会注册到java method,下次不必再查找直接返回,2.接着调用 native method,并且在 native method 返回后,3.调用 artQuickGenericJniEndTrampoline (会调用JniMethodEnd)进行状态切换及清除动作;

  2. 由于开始分析时选取的这个例子,java method没有参数,补充一个java 端带参数的 jni method 版本:

    gdb) art_get_method_name_by_method_id 0x7148e888
    android.os.Parcel.nativeEnforceInterface "(JLjava/lang/String;)V"  

    private static native void nativeEnforceInterface(long nativePtr, String interfaceName);
    static void android_os_Parcel_enforceInterface(JNIEnv* env, jclass clazz, jlong nativePtr, jstring name)
  3. nativeEnforceInterface(long, String):

       0x000000007517b5c0:  .inst   0x00000000 ; undefined   0x000000007517b5c4:  .inst   0x000000c0 ; undefined   0x000000007517b5c8:  .inst   0x7ff80000 ; undefined   0x000000007517b5cc:  .inst   0x0000ff00 ; undefined   0x000000007517b5d0:  .inst   0x000000f8 ; undefined   0x000000007517b5d4:  sub sp, sp, #0xc0   0x000000007517b5d8:  stp x19, x20, [sp,#96]   0x000000007517b5dc:  stp x21, x22, [sp,#112]   0x000000007517b5e0:  stp x23, x24, [sp,#128]   0x000000007517b5e4:  stp x25, x26, [sp,#144]   0x000000007517b5e8:  stp x27, x28, [sp,#160]   0x000000007517b5ec:  stp x29, x30, [sp,#176]   0x000000007517b5f0:  stp d8, d9, [sp,#32]   0x000000007517b5f4:  stp d10, d11, [sp,#48]   0x000000007517b5f8:  stp d12, d13, [sp,#64]   0x000000007517b5fc:  stp d14, d15, [sp,#80]   0x000000007517b600:  str x0, [sp]    0x000000007517b604:  str x1, [sp,#200]                           ; 参数1:long   0x000000007517b608:  str w2, [sp,#208]                           ; 参数2:String    0x000000007517b60c:  mov x20, #0x2                       // #2   ; static 的 Native method,一个String ref,一个 Class ref: sp+16   0x000000007517b610:  str w20, [sp,#16]    0x000000007517b614:  ldr x20, [x19,#264]   0x000000007517b618:  str x20, [sp,#8]   0x000000007517b61c:  add x20, sp, #0x8   0x000000007517b620:  str x20, [x19,#264]    0x000000007517b624:  ldr w20, [x0]   0x000000007517b628:  str w20, [sp,#20]                           ; 保存 class到栈上: sp+20    0x000000007517b62c:  ldr w20, [sp,#208]   0x000000007517b630:  str w20, [sp,#24]                           ; 保存参数2 String ref 到 sp+24    0x000000007517b634:  mov x16, sp   0x000000007517b638:  str x16, [x19,#152]   0x000000007517b63c:  mov x0, x19   0x000000007517b640:  ldr x20, [x0,#784]   0x000000007517b644:  blr x20                                     ; JniMethodStart    0x000000007517b648:  str w0, [sp,#28]                            ; 保存 local cookie 到 sp+28   0x000000007517b64c:  ldr w3, [sp,#24]                            ; 获取 string ref作为 native 函数的参数,放在 w3中   0x000000007517b650:  cmp w3, #0x0                                ; 判断 String != null   0x000000007517b654:  add x16, sp, #0x18   0x000000007517b658:  csel    x3, x16, x3, ne                     ; CSEL:如果条件满足, x3取 x16,否则 x3 取 x3; 这里的条件是 w3 != 0x0   0x000000007517b65c:  ldr x2, [sp,#200]                           ; 把参数1(long)作为 native 函数的参数,放在 x2中   0x000000007517b660:  add x1, sp, #0x14                           ; 把 Class 作为参数放在 x1   0x000000007517b664:  ldr x0, [x19,#184]                          ; 从 x19取 jni_env 作为参数放在 x0   0x000000007517b668:  ldr x20, [sp]   0x000000007517b66c:  ldr x20, [x20,#40]   0x000000007517b670:  blr x20                                     ; 跳转到 ArtMethod的 entry_point_from_jni_,也即 android_os_Parcel_enforceInterface  此时的栈: SP ->  |--------------------|        |     ArtMethod*     |        |--------------------|        | HandleScope* link_ |SP+16-> |--------------------|        |   num_of_ref_(2)   |SP+20-> |--------------------|        |      Class*        |SP+24-> |--------------------|        |      String        |SP+28-> |--------------------|        |      cookie        |        |--------------------|    0x000000007517b674:  ldr w0, [sp,#28]   0x000000007517b678:  mov x1, x19   0x000000007517b67c:  ldr x20, [x1,#800]   0x000000007517b680:  blr x20                                     ; JniMethodEnd    0x000000007517b684:  ldr x20, [x19,#136]                         ; popFrame and return   0x000000007517b688:  cbnz    x20, 0x7517b6bc   0x000000007517b68c:  ldp x19, x20, [sp,#96]   0x000000007517b690:  ldp x21, x22, [sp,#112]   0x000000007517b694:  ldp x23, x24, [sp,#128]   0x000000007517b698:  ldp x25, x26, [sp,#144]   0x000000007517b69c:  ldp x27, x28, [sp,#160]   0x000000007517b6a0:  ldp x29, x30, [sp,#176]   0x000000007517b6a4:  ldp d8, d9, [sp,#32]   0x000000007517b6a8:  ldp d10, d11, [sp,#48]   0x000000007517b6ac:  ldp d12, d13, [sp,#64]   0x000000007517b6b0:  ldp d14, d15, [sp,#80]   0x000000007517b6b4:  add sp, sp, #0xc0   0x000000007517b6b8:  ret    0x000000007517b6bc:  mov x0, x20                                 ; pDeliverException   0x000000007517b6c0:  ldr x16, [x19,#1240]   0x000000007517b6c4:  blr x16   0x000000007517b6c8:  brk #0x0



问题:

movx20, #0x2
strw20, [sp,#16]

这两条指令,保存 ref 个数后,看起来没有使用,猜测是为了给 compiler driver使用的,用来计算参数偏移 ?