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); }
说明:
- 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)进行状态切换及清除动作;
- 由于开始分析时选取的这个例子,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)
-
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使用的,用来计算参数偏移 ?