首页
社区
课程
招聘
[分享]Android调用JNI本地方法经过有点改变
2014-11-14 13:27 6922

[分享]Android调用JNI本地方法经过有点改变

2014-11-14 13:27
6922
一个java本地方法被注册后,在调用后会经过哪些路了。
Android一个异常捕获项目 https://github.com/xroche/coffeecatch可以看出端倪。
coffeecatch
coffeecatch
CoffeeCatch, a tiny native POSIX signal catcher (especially useful for JNI code on Android/Dalvik, but it can be used in non-Java projects)

It allows to "gracefully" recover from a signal (SIGSEGV, SIGBUS...) as if it was an exception. It will not gracefully recover from allocator/mutexes corruption etc., however, but at least "most" gentle crashes (null pointer dereferencing, integer division, stack overflow etc.) should be handled without too much troubles.

/** Enter protected section. **/
COFFEE_TRY() {
/** Try to call 'call_some_native_function'. **/
call_some_protected_function();
} COFFEE_CATCH() {
/** Caught a signal: throw Java exception. **/
/** In pure C projects, you may print an error message (coffeecatch_get_message()). **/
coffeecatch_throw_exception(env);
} COFFEE_END();
You may read the corresponding discussion about this project.

The handler is thread-safe, but client must have exclusive control on the signal handlers (ie. the library is installing its own signal handlers on top of the existing ones).

Libraries

If you want to get useful stack traces, you should build all your libraries with -funwind-tables (this adds unwinding information). On ARM, you may also use the --no-merge-exidx-entries linker switch, to solve certain issues with unwinding (the switch is possibly not needed anymore). On Android, this can be achieved by using this line in the Android.mk file in each library block:

LOCAL_CFLAGS := -funwind-tables -Wl,--no-merge-exidx-entries
Example

Inside JNI (typically, Android)
First, build the library, or just add the two files in the list of local files to be built:

LOCAL_SRC_FILES += coffeecatch.c coffeejni.c
then, use the COFFEE_TRY_JNI() macro to protect your call(s):

/** The potentially dangerous function. **/
jint call_dangerous_function(JNIEnv* env, jobject object) {
// ... do dangerous things!
return 42;
}

/** Protected function stub. **/
void foo_protected(JNIEnv* env, jobject object, jint *retcode) {
/* Try to call 'call_dangerous_function', and raise proper Java Error upon
* fatal error (SEGV, etc.). **/
COFFEE_TRY_JNI(env, *retcode = call_dangerous_function(env, object));
}

/** Regular JNI entry point. **/
jint Java_com_example_android_MyNative_foo(JNIEnv* env, jobject object) {
jint retcode = 0;
foo_protected(env, object, &retcode);
return retcode;
}
and, in case of crash, get something like this (note: the last Exception with native backtrace is produced on Android >= 4.1.1):

FATAL EXCEPTION: AsyncTask #5
java.lang.RuntimeException: An error occured while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:299)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:352)
at java.util.concurrent.FutureTask.setException(FutureTask.java:219)
at java.util.concurrent.FutureTask.run(FutureTask.java:239)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:841)
Caused by: java.lang.Error: signal 11 (Address not mapped to object) at address 0x42 [at libexample.so:0xa024]
at com.example.jni.ExampleLib.main(Native Method)
at com.example.ExampleActivity$Runner.runInternal(ExampleActivity.java:998)
at com.example.ExampleActivity$Runner.doInBackground(ExampleActivity.java:919)
at com.example.ExampleActivity$Runner.doInBackground(ExampleActivity.java:1)
at android.os.AsyncTask$2.call(AsyncTask.java:287)
at java.util.concurrent.FutureTask.run(FutureTask.java:234)
... 4 more
Caused by: java.lang.Error: signal 11 (Address not mapped to object) at address 0x42 [at libexample.so:0xa024]
at data.app_lib.com_example.libexample_so.0xa024(Native Method)
at data.app_lib.com_example.libexample_so.0x705fc(hts_main2:0x8f74:0)
at data.app_lib.com_example.libexamplejni_so.0x4cc8(ExampleLib_main:0xf8:0)
at data.app_lib.com_example.libexamplejni_so.0x52d8(Java_com_example_jni_ExampleLib_main:0x64:0)
at system.lib.libdvm_so.0x1dc4c(dvmPlatformInvoke:0x70:0)
at system.lib.libdvm_so.0x4dcab(dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*):0x18a:0)
at system.lib.libdvm_so.0x385e1(dvmCheckCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*):0x8:0)
at system.lib.libdvm_so.0x4f699(dvmResolveNativeMethod(unsigned int const*, JValue*, Method const*, Thread*):0xb8:0)
at system.lib.libdvm_so.0x27060(Native Method)
at system.lib.libdvm_so.0x2b580(dvmInterpret(Thread*, Method const*, JValue*):0xb8:0)
at system.lib.libdvm_so.0x5fcbd(dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list):0x124:0)
at system.lib.libdvm_so.0x5fce7(dvmCallMethod(Thread*, Method const*, Object*, JValue*, ...):0x14:0)
at system.lib.libdvm_so.0x54a6f(Native Method)
at system.lib.libc_so.0xca58(__thread_entry:0x48:0)
at system.lib.libc_so.0xcbd4(pthread_create:0xd0:0)
Outside JNI code
The COFFEE_TRY()/COFFEE_CATCH()/COFFEE_END() syntax can be used:

void my_function() {
COFFEE_TRY() {
/** Try to call 'call_some_native_function'. **/
call_some_native_function();
} COFFEE_CATCH() {
/** Caught a signal. **/
const char*const message = coffeecatch_get_message();
fprintf(stderr, "**FATAL ERROR: %s\n", message);
} COFFEE_END();
}
Hints
If you wish to catch signals and continue running your program rather than ending it (this may be dangerous, especially if a crash was spotted within a C library function, such as malloc()), use thecoffeecatch_cancel_pending_alarm() function to cancel the default pending alarm triggered to avoid deadlocks.


但是从网上看到注册本地方法流程没看到dvmCallMethod而一般是有dvmCallJNIMethod_general。

JNI方法调用改变

dvmCallJNIMethod_general不知从哪个版本就没了,但从http://androidxref.com/这里看,Gingerbread - 2.3.7还有,ICS - 4.0.3就没了。

这里模拟器4.4.2里导出的libdvm.so,符号,只有dvmCallJNIMethod,如下(4.4.2_API19):

File: /cygdrive/d/Developer/sdk/platforms/android-19/lib/libdvm.so

Symbol table '.dynsym' contains 1713 entries:
Num:    Value  Size  Type  Bind    Vis    Ndx  Name
    0:     00000000         0  NOTYPE     LOCAL        DEFAULT   UND
    1:     00000000         0     FUNC     GLOBAL      DEFAULT    UND    __cxa_finalize

394:     0004dd75      664     FUNC    GLOBAL       DEFAULT    8         _Z16dvmCallJNIMethodPKjP6JValuePK6MethodP6Thread


从这里可以看到
http://androidxref.com/4.4.2_r2/xref/dalvik/vm/interp/Stack.cpp
http://androidxref.com/4.4.2_r2/xref/dalvik/vm/Jni.cpp

/*
 * General form, handles all cases.
 */
 void dvmCallJNIMethod(const u4* args, JValue* pResult, const Method* method, Thread* self) {
     u4* modArgs = (u4*) args;
}

统一了。

拿2.3.3的

print(GetString(R1,-1, ASCSTR_C)),使用0 * print(GetString(DbgDword(R2+0x10),-1, ASCSTR_C)) 跟踪而不断下来,虽然文档说'void' means that function returns no meaningful value (always 0),但是还会段下来,乘以0就不一样,即运行语句又判断值。

IDA的帮助文档
// Print variables in the message window
// This function print text representation of all its arguments to the output window.
// This function can be used to debug IDC scripts

void    print           (...);

Alphabetical list of IDC functions

The following conventions are used in the function descriptions:

'ea' is a linear address
'success' is 0 if a function fails, 1 otherwise
'void' means that function returns no meaningful value (always 0)
'anyvalue' means that function may return value of any type

下面一个截图跟踪方法调用,最后到我们的jni方法。

看这里http://blog.csdn.net/luoshengyang/article/details/8923483,了解到注册jni方法以后再调用jni方法需要经过最直接看出的就是第一二参数变量等准备,这个来自注册时由Dalvik虚拟机的启动选项来为即将要注册的JNI选择一个合适的Bridge函数。以后通过这个Bridge函数(DalvikBridgeFunc)调用你的jni方法。但是这里有2类,每类4个。一般只有能选择1类,则对于jni方法调用存在4种调用过程。正如前面描述,从哪个版本开始就不会出现了,已经只有 一个了。
Dalvik虚拟机提供的Bridge函数主要是分为两类。第一类Bridge函数在调用完成JNI方法之后,会检查该JNI方法的返回结果是否与声明的一致,这是因为一个声明返回String的JNI方法在执行时返回的可能会是一个Byte Array。如果不一致,取决于Dalvik虚拟机的启动选项,它可能会停机。第二类Bridge函数不对JNI方法的返回结果进行上述检查。选择哪一类Bridge函数可以通过-Xcheck:jni选项来决定。不过由于检查一个JNI方法的返回结果是否与声明的一致是很耗时的,因此,我们一般都不会使用第一类Bridge函数。

此外,每一类Bridge函数又分为四个子类:Genernal、Sync、VirtualNoRef和StaticNoRef,它们的选择规则为:

1. 一个JNI方法的参数列表中如果包含有引用类型的参数,那么对应的Bridge函数就是Genernal类型的,即为dvmCallJNIMethod_general或者dvmCheckCallJNIMethod_general。

2. 一个JNI方法如果声明为同步方法,即带有synchronized修饰符,那么对应的Bridge函数就是Sync类型的,即为dvmCallJNIMethod_synchronized或者dvmCheckCallJNIMethod_synchronized。

3. 一个JNI方法的参数列表中如果不包含有引用类型的参数,并且它是一个虚成员函数,那么对应的Bridge函数就是kJNIVirtualNoRef类型的,即为dvmCallJNIMethod_virtualNoRef或者dvmCheckCallJNIMethod_virtualNoRef。

4. 一个JNI方法的参数列表中如果不包含有引用类型的参数,并且它是一个静态成员函数,那么对应的Bridge函数就是StaticNoRef类型的,即为dvmCallJNIMethod_staticNoRef或者dvmCheckCallJNIMethod_staticNoRef。

每一类Bridge函数之所以要划分为上述四个子类,是因为每一个子类的Bridge函数在调用真正的JNI方法之前,所要进行的准备工作是不一样的。例如,Genernal类型的Bridge函数需要为引用类型的参数增加一个本地引用,避免它在JNI方法执行的过程中被回收。又如,Sync类型的Bridge函数在调用JNI方法之前,需要执行同步原始,以避免多线程访问的竞争问题。


函数原型
void dvmCallJNIMethod_general(const u4* args, JValue* pResult, const Method* method, Thread* self)

在IDA中试试这个

能到我们的函数。
这样是不是说根据"method" == GetString(DbgDword(R2+0x10),-1, ASCSTR_C)条件断点,找到调用的函数地址。

看方法

如果方法太多,如界面上的可能会无响应对话框,一直在打印如下

"unlockCanvasAndPost"
"native_computeBounds"
"lockCanvasNative"
"nativeDraw"
"native_getClipBounds"
"native_drawText"
"nativeDraw"
"unlockCanvasAndPost"
"native_computeBounds"
"lockCanvasNative"
"nativeDraw"
"native_getClipBounds"
"native_drawText"
"nativeDraw"

....

或者打印地址语句

0 * Message("%s = %d\n", GetString(DbgDword(R2+0x10),-1, ASCSTR_C), R2+0x20)

结果

enforceInterface = 1108147904
writeInterfaceToken = 1108151492
writeStrongBinder = 1108152272
transact = 1108144564
lockCanvasNative = 1108185020
nativeDraw = 1108271444
native_getClipBounds = 1108165440
native_measureText = 1108172200
native_drawText = 1108165180
nativeDraw = 1108271444
unlockCanvasAndPost = 1108186532
enforceInterface = 1108147904
writeInterfaceToken = 1108151492
writeStrongBinder = 1108152272
transact = 1108144564
native_get_long = 1108308360
method = 1110009932
native_measureText = 1108172200
getFontMetricsInt = 1108173712
native_measureText = 1108172200
native_measureText = 1108172200
lockCanvasNative = 1108185020
drawText = 1108168304
nativeDraw = 1108271444
native_getClipBounds = 1108165440
native_measureText = 1108172200
native_drawText = 1108165180
nativeDraw = 1108271444
native_getClipBounds = 1108165440
native_measureText = 1108172200
native_drawText = 1108165180
nativeDraw = 1108271444
unlockCanvasAndPost = 1108186532

测试demo
ATest.rar

阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回