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程序异常crash捕获与拦截

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

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

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

libxml2在iOS4上由于xmlFreeDoc导致程序Crash的解决方法

最近在开发iOS程序,调用WebService时候使用到了iOS内置的libxml2来作为DOM树的操作库,按照官方代码的参考,写了一段创建DOM的代码,部分片段如下: xmlNodePtr g...

iOS 应用程序 Crash文件的理解与分析

Understanding and Analyzing iOS Application Crash Reports https://developer.apple.com/library/ios...
  • jeffasd
  • jeffasd
  • 2016年03月29日 11:50
  • 1388

ios程序发布后,收集Crash崩溃信息

有人想在iOS程序发布之后,收集程序崩溃的信息以便改进程序,今天我给大家介绍个比较实用的方法。 崩溃信息的收集完成后会在沙盒生成txt报告,但是这个crash收集也不能收集所有的程序崩溃信息,例如栈...
  • hushing
  • hushing
  • 2013年06月28日 16:11
  • 1240

查看iOS Crash logs的方法(程序崩溃)

一、如何获得crash日志 当一个iOS应用程序崩溃时,系统会创建一份crash日志保存在设备上。这份crash日志记录着应用程序崩溃时的信息,通常包含着每个执行线程的栈调用信息(低内存闪退日志...

iOS程序crash原因排查

1、ios应用crash的四种类型 程序崩溃: 可能是最常见的,经常发生于内存访问出错,异常,或者其他的程序错误 内存不足: 系统因为没有足够的内存满足程序需求从而杀死程序...

捕获程序崩溃(crash)

  • 2015年08月11日 14:01
  • 1.42MB
  • 下载

iOS Crash闪退信息捕获工具类

IOS SDK中提供了一个现成的函数 NSSetUncaughtExceptionHandler 用来做异常处理,但功能非常有限,而引起崩溃的大多数原因如:内存访问错误,重复释放等错误捕获不到,因为这...

iOS crash捕获异常崩溃日志

在APP发布到线上后,会出现用户使用时闪退的糟糕情况。为了改进用户体验,就需要收集app崩溃的日志信息,来完善应用。 像数组越界、字典操作对象值为nil等都会发出一般异常,利用 NSSetUncau...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:IOS程序crash捕获
举报原因:
原因补充:

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