IOS程序crash捕获

原创 2015年11月18日 09:24:45

IOS程序crash捕获

基础理论

Crash分为两种,一种是程序抛出的异常,没有被捕获造成的;另一种是signal类型的异常。针对未被捕获的异常可以使用NSSetUncaughtExceptionHandler系统方法来设置异常处理函数;对于signal类型的异常,需要使用signal系统方法给每种需要处理的signal类型的异常设置处理函数。如果没有为一个信号设置对应的处理函数,就会使用默认的处理函数SIG_DFL。

Signal的常见类型:

SIGABRT——程序中止信号

SIGFPE——浮点异常信号

SIGILL——非法指令异常

SIGSEGV——无效内存异常

SIGBUS——内存字节未对齐异常

SIGPIPE——socket发送失败异常

 

头文件”execinfo.h”声明了几个函数用于获取当前线程的函数调用堆栈,在程序出错的时候,打印出调用堆栈是非常有用的。

int backtrace(void **buffer,int size)

该函数用于获取当前线程的调用堆栈,获取的信息将被存储在buffer中,它是一个指针列表。参数size用来指定buffer中可以保存多少个指针元素。函数返回值是实际获取的指针个数,最大不超过size的大小。

 

char ** backtrace_symbols (void *const *buffer, int size)

backtrace_symbols将从backtrace函数中获取的信息转化为字符串数组。参数buffer应该是从backtrace函数获取的指针数组,size是该数组中的元素个数(backtrace的返回值)。函数返回值是一个指向字符串数组的指针,它的大小同buffer相同.每个字符串包含了一个相对于buffer中对应元素的可打印信息.它包括函数名,函数的偏移地址,和实际的返回地址

 

解决方案

这里我定义一个类UncaughtExceptionHandler处理以上提及的两种异常类型。大致思路:分别给两种类型的异常注册处理函数,然后以弹出视图的方式显示异常信息。

头文件UncaughtExceptionHandler.h

#import <Foundation/Foundation.h>

@interface UncaughtExceptionHandler : NSObject
{
    BOOL dismissed;//是否继续程序
}
@end

//处理未捕获的异常
void HandleUncaughtException(NSException *exception);
//处理信号类型的异常
void HandleSignal(int signal);
//为两种类型的信号注册处理函数
void InstallUncaughtExceptionHandler(void);

实现文件UncaughtExceptionHandler.m
<pre name="code" class="objc">//
//  UncaughtExceptionHandler.m
//  crashTest
//
//  Created by HuberySun on 15/11/11.
//  Copyright © 2015年 HuberySun. All rights reserved.
//

#import "UncaughtExceptionHandler.h"
#import <UIKit/UIKit.h>
#include <libkern/OSAtomic.h>
#include <execinfo.h>

NSString * const UncaughtExceptionHandlerSignalExceptionName=@"UncaughtExceptionHandlerSignalExceptionName";
NSString * const UncaughtExceptionHandlerSignalKey=@"UncaughtExceptionHandlerSignalKey";
NSString * const UncaughtExceptionHandlerAddressesKey=@"UncaughtExceptionHandlerAddressesKey";

volatile int32_t exceptionCount = 0;
const int32_t exceptionMaximum = 10;

const NSInteger UncaughtExceptionHandlerReportAddressCount = 10;//指明报告多少条调用堆栈信息
@interface UncaughtExceptionHandler ()<UIAlertViewDelegate>
//获取堆栈指针,返回符号化之后的数组
+ (NSArray *)backtrace;

//处理异常2,包括抛出的异常和信号异常
- (void)handleException:(NSException *)exception;
@end

@implementation UncaughtExceptionHandler
+ (NSArray *)backtrace{
    void *callStack[128];//堆栈方法数组
    int frames=backtrace(callStack, 128);//从iOS的方法backtrace中获取错误堆栈方法指针数组,返回数目
    char **strs=backtrace_symbols(callStack, frames);//符号化
    
    int i;
    NSMutableArray *symbolsBackTrace=[NSMutableArray arrayWithCapacity:frames];
    for (i=0; i<UncaughtExceptionHandlerReportAddressCount; i++) {
        [symbolsBackTrace addObject:[NSString stringWithUTF8String:strs[i]]];
    }
    free(strs);
    return symbolsBackTrace;
}

- (void)handleException:(NSException *)exception{
    NSString *message=[NSString stringWithFormat:@"如果点击继续,程序有可能会出现其他的问题,建议您还是点击退出按钮并重新打开\n\n异常报告:\n异常名称:%@\n异常原因:%@\n其他信息:%@\n",
                       [exception name],
                       [exception reason],
                       [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]];
    UIAlertView *alert =
    [[UIAlertView alloc]
      initWithTitle:NSLocalizedString(@"抱歉,程序出现了异常", nil)
      message:message
      delegate:self
      cancelButtonTitle:@"退出"
      otherButtonTitles:@"继续", nil];
    [alert show];
    
    ///////////////
    CFRunLoopRef runLoop=CFRunLoopGetCurrent();
    CFArrayRef allModes=CFRunLoopCopyAllModes(runLoop);
    NSArray *arr=(__bridge NSArray *)allModes;
    while (!dismissed) {
        for (NSString *mode in arr) {
            CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
        }
    }
    CFRelease(allModes);
    
    NSSetUncaughtExceptionHandler(NULL);
    signal(SIGABRT, SIG_DFL);
    signal(SIGILL, SIG_DFL);
    signal(SIGSEGV, SIG_DFL);
    signal(SIGFPE, SIG_DFL);
    signal(SIGBUS, SIG_DFL);
    signal(SIGPIPE, SIG_DFL);
    
    if ([[exception name] isEqual:UncaughtExceptionHandlerSignalExceptionName])
    {
        kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);
    }
    else
    {
        [exception raise];
    }
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    if (buttonIndex==0) {
        dismissed=YES;
    }else{
        dismissed=false;
    }
}

@end

void HandleUncaughtException(NSException *exception){
    int32_t exceptionCount=OSAtomicIncrement32(&exceptionCount);
    if (exceptionCount>exceptionMaximum) {
        return;
    }
    
    NSArray *callStack=[UncaughtExceptionHandler backtrace];
    NSMutableDictionary *userInfo=[NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
    [userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
    
    UncaughtExceptionHandler *uncaughtExceptionHandler=[[UncaughtExceptionHandler alloc] init];
    NSException *uncaughtException=[NSException exceptionWithName:[exception name] reason:[exception reason] userInfo:userInfo];
    [uncaughtExceptionHandler performSelectorOnMainThread:@selector(handleException:) withObject:uncaughtException waitUntilDone:YES];
}

void HandleSignal(int signal){
    int32_t exceptionCount= OSAtomicIncrement32(&exceptionCount);
    if (exceptionCount>exceptionMaximum) {
        return;
    }
    
    NSMutableDictionary *userInfo=[NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey];
    NSArray *callBack=[UncaughtExceptionHandler backtrace];
    [userInfo setObject:callBack forKey:UncaughtExceptionHandlerAddressesKey];
    
    UncaughtExceptionHandler *uncaughtExceptionHandler=[[UncaughtExceptionHandler alloc] init];
    NSException *signalException=[NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName reason:[NSString stringWithFormat:@"Signal %d was raised.",signal] userInfo:userInfo];
    [uncaughtExceptionHandler performSelectorOnMainThread:@selector(handleException:) withObject:signalException waitUntilDone:YES];
}

void InstallUncaughtExceptionHandler(void){
    NSSetUncaughtExceptionHandler(HandleUncaughtException);//设置未捕获的异常处理
    
    //设置信号类型的异常处理
    signal(SIGABRT, HandleSignal);
    signal(SIGILL, HandleSignal);
    signal(SIGSEGV, HandleSignal);
    signal(SIGFPE, HandleSignal);
    signal(SIGBUS, HandleSignal);
    signal(SIGPIPE, HandleSignal);
}



测试

测试代码
- (IBAction)OccurCrash:(id)sender {
    NSArray *arr=[NSArray arrayWithObjects:@"4",@"5", nil];
    NSLog(@"%@",[arr objectAtIndex:3]);
}

点击按钮,访问含有两个元素的数组的第三个索引位置的元素,会触发异常,弹出提示窗口,如下


商业应用解决方案

对于发布到app store的应用,大多使用第三方的Crash统计工具,比如Crashlytics


参考信息

[iOS]使用signal让app能够从容崩溃

IOS程序异常crash捕获与拦截

Linux下利用backtrace追踪函数调用堆栈以及定位段错误


版权声明:本文为博主原创文章,未经博主允许不得转载。

IOS开发笔记(5)程序异常crash捕获与拦截

开发iOS应用,解决Crash问题始终是一个难题。Crash分为两种,一种是由EXC_BAD_ACCESS引起的,原因是访问了不属于本进程的内存地址,有可能是访问已被释放的内存;另一种是未被捕获的Ob...
  • jerry19860710
  • jerry19860710
  • 2014年02月25日 14:17
  • 887

ios Crash闪退日志获取和上传至服务器

首先我们整理经常会闪退的异常哪些:数组越界、空引用、引用未定义方法、内存空间不足等等。    友盟分享后台是可以看到crash的日志,如下图: 开始研究的时候,我有两个疑问:      ...
  • jiajiayouba
  • jiajiayouba
  • 2015年04月08日 09:11
  • 7933

iOS开发之Crash日志获取与分析

iOS crash log分析
  • yq910902
  • yq910902
  • 2016年11月01日 14:42
  • 2663

iOS Crash 处理办法

在iOS开发调试过程中以及上线之后,程序经常会出现崩溃的问题。简单的崩溃还好说,复杂的崩溃就需要我们通过解析Crash文件来分析了,解析Crash文件在iOS开发中是比较常见的。 现在网上有很多...
  • sunnyboy9
  • sunnyboy9
  • 2016年02月15日 23:27
  • 1639

iOS崩溃捕获以及收集原理

通过崩溃捕获和收集,可以收集到已发布应用(游戏)的异常,以便开发人员发现和修改bug,对于提高软件质量有着极大的帮助...
  • skylin19840101
  • skylin19840101
  • 2016年03月22日 15:57
  • 1584

教你如何对ios崩溃(crash)日志做符号化

一、场景         客户端的开发流程都相似,如android,搞ios开发就要不停地发版本,随之而来的就是各种版本的崩溃日志(称为crash log)。如果不能好好地管理,那么开发人员很快就会...
  • Weiguang_123
  • Weiguang_123
  • 2016年02月23日 20:28
  • 13427

ios 收集crash xcode 调试 捕捉signal信号

//处理信号类型的异常 void SignalExceptionHandler(int signal) {          NSMutableString *mstr = [[NSMutab...
  • ssyyjj88
  • ssyyjj88
  • 2017年06月20日 11:54
  • 654

iOS异常捕获

前言 在阅读文章之前,建议大家在阅读完此篇文章后可以阅读漫谈iOS Crash收集框架,了解一下原理。 开发iOS应用,解决Crash问题始终是一个难题。Crash分为两种,一种是由EXC_B...
  • sadlike
  • sadlike
  • 2016年08月24日 10:18
  • 523

android和iOS平台的崩溃捕获和收集

原文地址: http://blog.csdn.net/langresser_king/article/details/8288195   通过崩溃捕获和收集,可以收集到已发布应用(游戏)的异常,以...
  • changcsw
  • changcsw
  • 2015年03月23日 19:30
  • 748

(插播)unity的 异常捕捉和 ios Android 崩溃信息的捕捉。

(插播)unity的 异常捕捉和 ios Android 崩溃信息的捕捉。
  • u011866450
  • u011866450
  • 2014年07月16日 15:57
  • 4983
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:IOS程序crash捕获
举报原因:
原因补充:

(最多只允许输入30个字)