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 : b 0x7f8a8dac70 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
问题:
movx20, #0x2
strw20, [sp,#16]
这两条指令,保存 ref 个数后,看起来没有使用,猜测是为了给 compiler driver使用的,用来计算参数偏移 ?