iOS崩溃信息收集

2015-10-20 | 阅读

iOS崩溃信息收集

引发崩溃的代码本质上就两类,一个是c++语言层面的错误,比如野指针,除零,内存访问异常等等;另一类是未捕获异常(Uncaught Exception),iOS下面最常见的就是objective-cNSException(通过@throw抛出,比如,NSArray访问元素越界),android下面就是java抛出的异常了。这些异常如果没有在最上层try住,那么程序就崩溃了.对于后者,可以使用NSUncaughtExceptionHandler来进行抓取,对于前者,无论是iOS还是android系统,其底层都是unix或者是类unix系统,都可以通过信号机制来获取 signal或者是sigaction.设置一个回调函数.

使用NSUncaughtExceptionHandler抓取NSException

使用函数void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler *)来设置一个处理回调函数,而typedef void NSUncaughtExceptionHandler(NSException *exception),其回调函数是一个带一个NSException参数的返回空的函数指针.

抓取异常的代码:

void MyUncaughtExceptionHandler(NSException *exception) {
    NSString *reason = [exception reason];
    NSString *name = [exception name];
//    NSLog(@"%@",exception.userInfo);
    NSString* ret=[NSString stringWithFormat:@"异常名称:\n%@\n\n异常原因:\n%@\n\n出错堆栈内容:\n%@\n",name,reason,[exception callStackSymbols] ];
    NSLog(@"%@",ret);

使用前调用:

NSSetUncaughtExceptionHandler(&MyUncaughtExceptionHandler);

来设置使用该回调函数.

抓取Signal消息

signal信号是Unix系统中的,是一种异步通知机制.信号传递给进程后,在没有处理函数的情况下,程序可以指定三种行为,

  1. 忽略该信号,但是对于信号SIGKILLSIGSTOP不可忽略
  2. 使用默认的处理函数SIG_DFL,大多数信号的默认动作是终止进程.
  3. 捕获信号,执行用户定义的函数.

有两个特殊的常量:

  • SIG_IGN,向内核表示忽略此信号.对于不能忽略的两个信号SIGKILLSIGSTOP,调用时会报错.
  • SIG_DFL,执行该信号的系统默认动作.

还有两个常用的函数

  • int kill(pid_t pid, int signo);,发送信号到指定的进程
  • int raise(int signo);,发送信号给自己.

UNIX系统中常用的信号有以下几种:

SIGABRT--程序中止命令中止信号 
SIGALRM--程序超时信号 
SIGFPE--程序浮点异常信号
SIGILL--程序非法指令信号
SIGHUP--程序终端中止信号
SIGINT--程序键盘中断信号 
SIGKILL--程序结束接收中止信号 
SIGTERM--程序kill中止信号 
SIGSTOP--程序键盘中止信号  
SIGSEGV--程序无效内存中止信号 
SIGBUS--程序内存字节未对齐中止信号 
SIGPIPE--程序Socket发送失败中止信号

而我们抓取的是以下几种:

static int Beacon_errorSignals[] = {
    SIGABRT,
    SIGBUS,
    SIGFPE,
    SIGILL,
    SIGSEGV,
    SIGTRAP,
    SIGTERM,
    SIGKILL,
};
for (int i = 0; i < Beacon_errorSignalsNum; i++) {
    signal(Beacon_errorSignals[i], &mysighandler);
}

这里,抓取信号的处理函数如下:

void mysighandler(int sig) {
    void* callstack[128];
    NSString* name ;
    int i, frames = backtrace(callstack, 128);
    for (i = 0; i < Beacon_errorSignalsNum; i++) {
        if (Beacon_errorSignals[i] == sig ) {
            name = [Beacon_errorSignalNames[i] copy];
            break;
        }
    }
    char** strs = backtrace_symbols(callstack, frames);
    NSMutableString* exceptionStr = [[NSMutableString alloc]initWithFormat:@"异常名称:\n%@\n\n出错堆栈内容:\n",name];
    for (i =0; i <frames; i++) {
        [exceptionStr appendFormat:@"%s\n",strs[i]];
    }
    free(strs);
}

但这里会将信号不断的发向该处理函数,导致应用无法正常崩溃,因为一般的消息处理会向进程终结,但是这里没有,所以还会有同样地信号不断的发过来并被处理.所以处理函数后要终结该处理函数的处理,并将其由系统默认处理,即:

signal(sig, SIG_DFL);

而对于有些时候,在iOS中,在应用崩溃后,保持运行状态而不退出:

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);

while (!dismissed)
{
	for (NSString *mode in (__bridge NSArray *)allModes)
	{
		CFRunLoopRunInMode((__bridge CFStringRef)mode, 0.001, false);
	}
}

CFRelease(allModes);

应用以上代码,可以做到崩溃时弹框提示应用,以让用户还是可以正常操作,让响应更加友好.

崩溃分析

崩溃收集分为两种,系统自动上报的崩溃(在设置-隐私-诊断与用量-与应用开发者共享设置上报),和我们在APP中抓取崩溃上报,如使用百度统计或者自行统计。系统自动上报的崩溃,通过Xcode-Window-Organizer-Crashes中查看,可以根据本地代码定位到崩溃。 而自行统计的代码,会本隐藏,需要通过dSYM文件进行分析:

dwarfdump --arch=armv7 --lookup 0xf3b33 $dSYMPath