Android-jni(6)-常见错误异常

NDK异常信息一般有三个要素:

  1. 信号
  2. 调用栈信息
  3. 寄存器信息

比如一下是一个空指针的错误信息:

 A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 31286 (shixin.ndkdemo)
I/crash_dump32: obtaining output fd from tombstoned
I//system/bin/tombstoned: received crash request for pid 31286
I/crash_dump32: performing dump of process 31286 (target tid = 31286)
A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
A/DEBUG: Build fingerprint: 'google/sdk_gphone_x86/generic_x86:8.0.0/OSR1.170901.056/4497355:userdebug/dev-keys'
A/DEBUG: Revision: '0'
A/DEBUG: ABI: 'x86'
A/DEBUG: pid: 31286, tid: 31286, name: shixin.ndkdemo  >>> shixin.ndkdemo <<<
A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
A/DEBUG: Cause: null pointer dereference
A/DEBUG:     eax 9014fe0c  ebx 9014fe0c  ecx 00000000  edx afd29310
A/DEBUG:     esi 90484b65  edi bfa41468
A/DEBUG:     xcs 00000073  xds 0000007b  xes 0000007b  xfs 0000003b  xss 0000007b
A/DEBUG:     eip 9010fc94  ebp bfa41278  esp bfa41230  flags 00010286
A/DEBUG: backtrace:
A/DEBUG:     #00 pc 00008c94  /data/app/shixin.ndkdemo-1hGHzLT4mUe7NO7XJcyfdg==/lib/x86/libmy_native_lib.so (_Z9crashNullv+36)
A/DEBUG:     #01 pc 00008dda  /data/app/shixin.ndkdemo-1hGHzLT4mUe7NO7XJcyfdg==/lib/x86/libmy_native_lib.so (Java_shixin_ndkdemo_MainActivity_testCrash+42)
A/DEBUG:     #02 pc 000086f8  /data/app/shixin.ndkdemo-1hGHzLT4mUe7NO7XJcyfdg==/oat/x86/base.odex (offset 0x8000)
A/DEBUG:     #03 pc 00059fff  [anon:libc_malloc:afd00000]
A/DEBUG:     #04 pc 0008ef27  /dev/ashmem/dalvik-main space (region space) (deleted)
A/DEBUG:     #05 pc 8bc3b28f  <unknown>
A/DEBUG:     #06 pc 80c1c541  <unknown>
A/DEBUG:     #07 pc 0d3fffff  /dev/ashmem/dalvik-main space (region space) (deleted)
E//system/bin/tombstoned: Tombstone written to: /data/tombstones//tombstone_00

对应的三个要素:

  1. 信号:这里的signal 11 (SIGSEGV),每个错误信息有对应的值,比如这个是11
  2. 调用栈:
//堆栈
A/DEBUG: backtrace:
            //错误起始地址依次往下
A/DEBUG:     #00 pc 00008c94  /data/app/shixin.ndkdemo-1hGHzLT4mUe7NO7XJcyfdg==/lib/x86/libmy_native_lib.so (_Z9crashNullv+36)
A/DEBUG:     #01 pc 00008dda  /data/app/shixin.ndkdemo-1hGHzLT4mUe7NO7XJcyfdg==/lib/x86/libmy_native_lib.so (Java_shixin_ndkdemo_MainActivity_testCrash+42)
A/DEBUG:     #02 pc 000086f8  /data/app/shixin.ndkdemo-1hGHzLT4mUe7NO7XJcyfdg==/oat/x86/base.odex (offset 0x8000)
A/DEBUG:     #03 pc 00059fff  [anon:libc_malloc:afd00000]
A/DEBUG:     #04 pc 0008ef27  /dev/ashmem/dalvik-main space (region space) (deleted)
A/DEBUG:     #05 pc 8bc3b28f  <unknown>
A/DEBUG:     #06 pc 80c1c541  <unknown>
A/DEBUG:     #07 pc 0d3fffff  /dev/ashmem/dalvik-main space (region space) (deleted)
  1. 寄存器信息:
    //空指针引用错误原因,很多时候没有这个
A/DEBUG: Cause: null pointer dereference
            //寄存器信息地址
A/DEBUG:     eax 9014fe0c  ebx 9014fe0c  ecx 00000000  edx afd29310
A/DEBUG:     esi 90484b65  edi bfa41468
A/DEBUG:     xcs 00000073  xds 0000007b  xes 0000007b  xfs 0000003b  xss 0000007b
A/DEBUG:     eip 9010fc94  ebp bfa41278  esp bfa41230  flags 00010286

接下来记录下常见的异常:

一. 空指针:

说明:

  1. 指针引用不能访问地址
  2. 指针为空

空指针是很容易出现的一种bug,但是它也很容易被发现和修复。比如以下代码就会报空指针:

  • 操作不能访问地址:
/**
 * 空指针
 */
void crashNull() {
    //0为其实地址,是不可读写的,会立即崩溃
    int *p = 0;
    *p = 1;
    LOGE("p:%d", *p);
}
  • 空指针访问:
/**
 * 空指针
 */
void crashNull() {
    int *p = NULL;
    *p = 1;
    LOGE("p:%d", *p);
}

运行崩溃,异常日志:

 A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 31286 (shixin.ndkdemo)
I/crash_dump32: obtaining output fd from tombstoned
I//system/bin/tombstoned: received crash request for pid 31286
I/crash_dump32: performing dump of process 31286 (target tid = 31286)
A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
A/DEBUG: Build fingerprint: 'google/sdk_gphone_x86/generic_x86:8.0.0/OSR1.170901.056/4497355:userdebug/dev-keys'
A/DEBUG: Revision: '0'
A/DEBUG: ABI: 'x86'
A/DEBUG: pid: 31286, tid: 31286, name: shixin.ndkdemo  >>> shixin.ndkdemo <<<
A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
A/DEBUG: Cause: null pointer dereference
A/DEBUG:     eax 9014fe0c  ebx 9014fe0c  ecx 00000000  edx afd29310
A/DEBUG:     esi 90484b65  edi bfa41468
A/DEBUG:     xcs 00000073  xds 0000007b  xes 0000007b  xfs 0000003b  xss 0000007b
A/DEBUG:     eip 9010fc94  ebp bfa41278  esp bfa41230  flags 00010286
A/DEBUG: backtrace:
A/DEBUG:     #00 pc 00008c94  /data/app/shixin.ndkdemo-1hGHzLT4mUe7NO7XJcyfdg==/lib/x86/libmy_native_lib.so (_Z9crashNullv+36)
A/DEBUG:     #01 pc 00008dda  /data/app/shixin.ndkdemo-1hGHzLT4mUe7NO7XJcyfdg==/lib/x86/libmy_native_lib.so (Java_shixin_ndkdemo_MainActivity_testCrash+42)
A/DEBUG:     #02 pc 000086f8  /data/app/shixin.ndkdemo-1hGHzLT4mUe7NO7XJcyfdg==/oat/x86/base.odex (offset 0x8000)
A/DEBUG:     #03 pc 00059fff  [anon:libc_malloc:afd00000]
A/DEBUG:     #04 pc 0008ef27  /dev/ashmem/dalvik-main space (region space) (deleted)
A/DEBUG:     #05 pc 8bc3b28f  <unknown>
A/DEBUG:     #06 pc 80c1c541  <unknown>
A/DEBUG:     #07 pc 0d3fffff  /dev/ashmem/dalvik-main space (region space) (deleted)
E//system/bin/tombstoned: Tombstone written to: /data/tombstones//tombstone_00
解决方案:使用前加非空判断
//判断不为空再进行执行
if (!*p == NULL) {
        *p = 1;
        LOGE("p:%d", *p);
    }

注意:该解决方案针对与指针为空的情况,如果指针是第一种不能访问地址,那么判空也是不行的,不过第一种我想大家也很容易就能发现

二. 野指针:

说明:

指针指向无效地址:

  1. 如果该地址是不可读不可写,那么立马会遇到crash(内核给进程发送错误信息SIGSEGV)
  2. 如果该指针的地址可写,那么可能等一会才会出现崩溃(其他指针修改了这一块地址),这时候查看调用栈和野指针所在代码部分可能根本没有关联。
/**
 * 野指针
 */
void wildPointer() {
    //开始没有初始化
    int *p;
    //中途使用
    *p = 1;
}

这种情况不会立马crash,但是是很不安全的,说不定在之后某一时刻就崩溃了

解决方案:
  1. 指针变量一定要初始化,特别是结构体或类中的成员变量的指针
  2. 使用完后,在不用的情况,尽量执为NULL(如果别的地方也有指针指向这段内存就不好解决)

例如:

int o;
    //初始化,指向给o的地址
    int *p = &o;
    //中途使用赋值,就是给o赋值了
    *p = 1;
    LOGE("o:%d", *p);
    //用完后指向NULL
    *p = NULL;

注意: 野指针造成的内存破坏,很难发现,一般需要专业内存检测工具,才能发现这一类的bug

三. 数组越界

说明:

数组越界和野指针有点像,访问了其他地址,如果访问地址是不可读写的,那么立马会Crash(内核给进程发送错误信号SIGSEGV),如果是可读写的地址,修改了该地址内存,造成内存破坏,那么有可能等会在别的地方发生Crash。

解决方案:
  1. 所有数组遍历的循环,都要加上越界判断
  2. 用下标访问数组的时候,要判断是否越界
  3. 通过代码分析工具可以发现绝大部分数组越界问题
 int data[10];
    //越界赋值,先判断下size
    int sizeData = sizeof(data);
    for (int i = 0; i < 17; ++i) {
        if (sizeData <= 17) {
            data[i] = 666;
        }
    }
    LOGE("data[11]:%d", data[11]);

PS:在笔者四处查询,貌似不同的编译器对此问题也有的有处理,会报错等,C++也有运行时报错的情况(有的也说不报错,尴尬)。不管它怎么,自己使用的时候一定要注意判断下,数组越界这个需要重视,因为内存连续的原因,如果一越界了,那么很有可能就将这个数组之后定义的一个变量值给修改了。

四. 内存泄漏

说明:

c与c++没有像java那样自动回收的GC机制,所以使用完需要手动释放,如果没有释放,就造成内存泄漏。

比如:

jstring str = env->NewStringUTF("哈哈哈哈");
    if (str == NULL) {
        LOGE("str is null");
        return;
    }
    const char *stringChar = env->GetStringUTFChars(str, 0);
    LOGE("str:%s", stringChar);
解决方案:
  1. 用完后进行释放

例如:

jstring str = env->NewStringUTF("哈哈哈哈");
    if (str == NULL) {
        LOGE("str is null");
        return;
    }
    const char *stringChar = env->GetStringUTFChars(str, 0);
    LOGE("str:%s", stringChar);

    //用完后进行释放
    env->DeleteLocalRef(str);
    env->ReleaseStringUTFChars(str,stringChar);
五. 堆栈溢出

说明:

与java的堆栈溢出异常一样,jni中一样有堆栈溢出异常,比如超过堆大小,或超过栈深度时候爆出。

解决方案:
  1. 对于堆溢出,在做好释放操作,只要做好避免内存泄漏,合理分配内存使用,一般不会出,现在堆大小还是挺大的
  2. 对于栈溢出,做递归的时候重复调用方法的时候,考虑好栈的深度,适时的做好切换操作,避免栈溢出
六. 格式化参数错误
  1. 需要格式化参数类型的时候,有可能出现错误

比如:

int b=0;
    LOGE("b:%s", b);

异常日志和空指针挺像的

A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x1 in tid 4406 (shixin.ndkdemo)
I/crash_dump32: obtaining output fd from tombstoned
I//system/bin/tombstoned: received crash request for pid 4406
I/crash_dump32: performing dump of process 4406 (target tid = 4406)
A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
A/DEBUG: Build fingerprint: 'google/sdk_gphone_x86/generic_x86:8.0.0/OSR1.170901.056/4497355:userdebug/dev-keys'
A/DEBUG: Revision: '0'
A/DEBUG: ABI: 'x86'
A/DEBUG: pid: 4406, tid: 4406, name: shixin.ndkdemo  >>> shixin.ndkdemo <<<
A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x1
A/DEBUG: Cause: null pointer dereference
解决方案
  1. 在书写输出格式和参数时,要做到参数个数和类型都要与输出格式一致。
  2. 在GCC的编译选项中加入-wformat,让GCC在编译时检测出此类错误。
六. 除以0

分母为0的这种情况,会很快Crash,一般都是在实际运行环境中还有可能出现,所以编码习惯的时候应该尽量习惯性去判断下

示例代码:

/**
 * 除以0
 */
void zeroDiv() {
    int a = 1;
    int b = a / 0; //整数除以0,产生SIGFPE信号,导致Crash
    LOGE("b:%d", b);
}

崩溃日志:

A/libc: Fatal signal 8 (SIGFPE), code 1, fault addr 0x8ff0ef12 in tid 3804 (shixin.ndkdemo)
I/crash_dump32: obtaining output fd from tombstoned
I//system/bin/tombstoned: received crash request for pid 3804
I/crash_dump32: performing dump of process 3804 (target tid = 3804)
A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
A/DEBUG: Build fingerprint: 'google/sdk_gphone_x86/generic_x86:8.0.0/OSR1.170901.056/4497355:userdebug/dev-keys'
A/DEBUG: Revision: '0'
A/DEBUG: ABI: 'x86'
A/DEBUG: pid: 3804, tid: 3804, name: shixin.ndkdemo  >>> shixin.ndkdemo <<<
A/DEBUG: signal 8 (SIGFPE), code 1 (FPE_INTDIV), fault addr 0x8ff0ef12
A/DEBUG:     eax 00000001  ebx 00000001  ecx 00000004  edx 00000000
A/DEBUG:     esi 8ff48171  edi 00000000
A/DEBUG:     xcs 00000073  xds 0000007b  xes 0000007b  xfs 0000003b  xss 0000007b
A/DEBUG:     eip 8ff0ef12  ebp bfa41278  esp bfa41230  flags 00010246
A/DEBUG: backtrace:
A/DEBUG:     #00 pc 00008f12  /data/app/shixin.ndkdemo-QvqFUNT_8-bffRNbIoelFQ==/lib/x86/libmy_native_lib.so (_Z7zeroDivv+66)
A/DEBUG:     #01 pc 00008f7a  /data/app/shixin.ndkdemo-QvqFUNT_8-bffRNbIoelFQ==/lib/x86/libmy_native_lib.so (Java_shixin_ndkdemo_MainActivity_testCrash+42)
A/DEBUG:     #02 pc 000096f8  /data/app/shixin.ndkdemo-QvqFUNT_8-bffRNbIoelFQ==/oat/x86/base.odex (offset 0x9000)
A/DEBUG:     #03 pc 00059fff  [anon:libc_malloc:afd00000]
A/DEBUG:     #04 pc 00052727  /dev/ashmem/dalvik-main space (region space) (deleted)
A/DEBUG:     #05 pc 8bc3b28f  <unknown>
A/DEBUG:     #06 pc 80c1c541  <unknown>
A/DEBUG:     #07 pc 0d3fffff  /dev/ashmem/dalvik-main space (region space) (deleted)
解决方案
  1. 在有除法的时候,判断下分母为0的情况

例如:

    int a = 1;
    //将分母给拿出来判断,运行时很可能是某个计算值
    int c = 0;
    int b;
    if (c != 0) {
        //判断下
        b = a / c;
    } else {
        b = a;
    }
    LOGE("b:%d", b);

感谢(http://www.droidsec.cn/常见android-native崩溃及错误原因/),这位作者总结的很好,学习了。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值