【第22期】观点:IT 行业加班,到底有没有价值?

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闪退日志获取和上传至服务器

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

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

iOS crash log分析

程序员升职加薪指南!还缺一个“证”!

CSDN出品,立即查看!

iOS Crash 处理办法

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

IOS程序异常crash捕获与拦截

IOS程序异常crash捕获与拦截开发iOS应用,解决Crash问题始终是一个难题。Crash分为两种,一种是由EXC_BAD_ACCESS引起的,原因是访问了不属于本进程的内存地址,有可能是访问已被...

IOS程序异常crash捕获与拦截

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

IOS程序异常crash捕获与拦截

开发iOS应用,解决Crash问题始终是一个难题。Crash分为两种,一种是由EXC_BAD_ACCESS引起的,原因是访问了不属于本进程的内存地址,有可能是访问已被释放的内存;另一种是未被捕获的Ob...
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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