从 Java 调用一个 Native 函数

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:39
39    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 0x7ff85caa80
0x7ff85caa80:   0x7147c530
(gdb) art_get_method_name_by_method_id 0x7147c530
android.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 0x7147c530
0x7147c530: 0x00000109710807f8  0x0000c7f900000000
0x7147c540: 0x0000000000000008  0x0000000071544588
0x7147c550: 0x000000007153cda8  0x0000007f8a9400d8
0x7147c560: 0x00000000751aa2d4
 
(gdb) p $sp
$28 = (void *) 0x7ff85caa80
(gdb) x 0x7ff85caa80
0x7ff85caa80:   0x7147c530
(gdb) x /x 0x7147c530+40
(gdb) disassemble 0x0000007f8a9400d8
Dump 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 ; undefined
End 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



问题:

mov	x20, #0x2
str	w20, [sp,#16]

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


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值