iOS奔溃信息类型捕捉与分析

iOS 异常奔溃类型分类

1.数组越界导致的崩溃

2.数据集合类型,如字典、数组中插入元素时,插入空指针nil

3.调用当前对象类中不存在的方法导致崩溃

4.调用的库函数版本高于本机

5.内存管理不当,向野指针发送消息导致的崩溃(包括某些信号量奔溃)

6.EXC_ARITHMETIC 除零操作

7.信号量的崩溃

信号量的崩溃

什么是Signal

在计算机科学中,信号(英语:Signals)是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数

SIGSEGV(无效内存中止信号)

段错误信号SIGSEGV 是操作系统产生的一个更严重的问题。当硬件出现错误、访问不可读的内存地址或向受保护的内存地址写入数据时,就会发生这个错误。

 硬件错误这一情况并不常见。当要读取保存在RAM中数据,而该位置的RAM硬件出现问题时,你会收到SIGSEGV。SIGSEGV更多是出现在后两种情况。默认情况下,代码页不允许进行写操作,而数据页不允许进行执行操作。当应用中的某个指针指向代码页并试图修改指向位置的指时,你会收到SIGSEGV。当要读取一个指针的值,而它被初始化成指向无效内存地址的垃圾值时,你也会收到SIGSEGV。

SIGSEGV错误调试起来更困难,而导致SIGSEGV的最常见原因是不正确的类型转换。要避免过度使用指针或尝试手动修改指针来读取数据结构。如果你那样做了,而在修改指针时没有注意内存对齐和填充问题,就会收到SIGSEGV

SIGBUS(内存地址未对齐)

总线错误信号(SIGBUS)代表无效内存访问,即访问的内存是一个无效的内存地址。也就是说,那个地址指向的位置根本不是物理内存地址(它可能是某个硬件芯片的地址)。SIGBUS和 SIGSEGV 都属于EXC_BAD_ACCESS的子类型。程序内存字节未对齐中止信号

SIGILL

SIGILL代表 SIGNAL_ILLEGL INSTRUCTION (非法指令信号)。当在处理器上执行非法指令时,它就会发生。执行非法指令是指,将函数指针传给另一个函数时,该函数指针由于某种原因是坏的,指向了一段已经释放的内存或是一个数据段。有时你收到的是GNAL_ILLEGL INSTRUCTION 而不是 SIGILL ,虽然它们是一回事,不过EXC_*等同于此信号不依赖体系结构

SIGABRT

SIGABRT 代表SIGNAL_ABORT(中止信号)。当操作系统发现不安全的情况时,它能够对这种情况进行更多控制:必要的话,它能要求进程进行清理工作。在调试造成此信号的底层错误时,并没有什么妙招。cocos2d或UIKit等框架通常会在特定的前提条件没有满足或一些糟糕的情况出现时调C函数abort(由它来发送此信号)。当SIGABRT 出现时,控制台通常会输出大量的信息,说明具体哪里出错了。由于它是可控制的崩溃,所以可以在LLDB控制台上键入bt 命令打印出回搠信息。

0x8badf00d

是一个固定的错误码,这种崩溃通畅比较容易分辨,也就是俗称的看门狗超时,它经常出现在执行一个同步网络调用而阻塞线程的情况,因此,永远不要进行同步网络调用

iOS 异常分析与处理方法

1.用xcode

crash日志奔溃路径是windows->organizer->crashes
这个日志是appstore上收集过来的日志,可以具体定位到代码

2.集成友盟sdk

2.1通过友盟sdk收集奔溃日志信息,利用 umcrashtool(下载链接) 结合友盟平台上奔溃日志csv文件。

第一步 下载错误分析工具 并解压zip得到umcrashtool文件,可将umcrashtool与已下载的xxx.csv文件放入同一目录下。

第二步 在terminal中运行umcrashtool命令,参数为错误分析的.csv文件绝对路径,如下:
sanzhang$ ./umcrashtool [absolutely_path_of_csv_file]
将umcrashtool与错误分析.csv文件放入同一目录下

umcrashtool工具截图

注:如果错误分析没有成功,请先确保对应的 xxx.dSYM 文件在 ~/Library/Developer/Xcode/ 或该路径的子目录下。(对于每一个产品发布时archive操作会将dsym文件存放到~/Library/Developer/Xcode/Archives路径下,因此建议保留该路径下的文件,以便后续用工具分析错误。)

umcrashtool分析截图截图

2.2通过友盟sdk收集奔溃日志信息,利用 dSYMTools (下载链接) 结合友盟平台上奔溃信息地址解析

1.找到你的APP->选择错误列表->选择你的APP发布版本就能看到你的崩溃列表:

友盟奔溃列表

2.崩溃日志详情界面

崩溃日志详情

3.把 projectBase.app.dSYM 文件拉进dSYMTools工具,并选择相关的CPU Type

工具使用

3.通过自定义handle工具类进行主动获取

iOS SDK中提供了一个现成的函数 NSSetUncaughtExceptionHandler 用来做异常处理,但功能非常有限,而引起崩溃的大多数原因如:内存访问错误,重复释放等错误就无能为力了。
因为这种错误它抛出的是Signal,所以必须要专门做Signal处理。

1.安装处理器方法

- (void)InstallUncaughtExceptionHandler:(id <RTExceptionHandlerDelegate>)delegate
{
    self.delegate = delegate;
    
    //如果有其他第三方异常处理,未避免影响处理
    preHandle = NSGetUncaughtExceptionHandler();

    InstallUncaughtExceptionHandler();
}

void InstallUncaughtExceptionHandler(void)
{
    NSSetUncaughtExceptionHandler(&HandleException);
    signal(SIGABRT, SignalHandler);
    signal(SIGILL, SignalHandler);
    signal(SIGSEGV, SignalHandler);
    signal(SIGFPE, SignalHandler);
    signal(SIGBUS, SignalHandler);
    signal(SIGPIPE, SignalHandler);
}

2.异常函数处理


//NSSetUncaughtExceptionHandler异常函数处理
void HandleException(NSException *exception)
{
    int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
    if (exceptionCount > UncaughtExceptionMaximum)
    {
        return;
    }
    [[RTExceptionHandler sharedhandle] handleException:exception];
}

//NSSetUncaughtExceptionHandler异常signal函数处理
void SignalHandler(int signal)
{
    int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
    if (exceptionCount > UncaughtExceptionMaximum)
    {
        return;
    }
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:ExceptionHandlerSignalKey];
    NSArray *callStack = [RTExceptionHandler backtrace];
    [userInfo setObject:callStack forKey:ExceptionHandlerAddressesKey];
    [[RTExceptionHandler sharedhandle] signalHandlerException:[NSException exceptionWithName:ExceptionHandlerSignalExceptionName reason:[NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.", nil),signal] userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:ExceptionHandlerSignalKey]]];
}


3.完整的代码

RTExceptionHandle.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@protocol RTExceptionHandlerDelegate <NSObject>

@required
- (void)handleExceptionContent:(NSString *)excpCnt;

@end

@interface RTExceptionHandler : NSObject

//单例
singleton_interface(RTExceptionHandler, handle)

- (void)InstallUncaughtExceptionHandler:(id <RTExceptionHandlerDelegate>)delegate;

@end

void InstallUncaughtExceptionHandler(void);

RTExceptionHandle.m

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

NSString * const ExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
NSString * const ExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";
NSString * const ExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";

volatile int32_t UncaughtExceptionCount = 0;
const int32_t UncaughtExceptionMaximum = 10;

const NSInteger ExceptionHandlerSkipAddressCount = 4;
const NSInteger ExceptionHandlerReportAddressCount = 5;

static NSUncaughtExceptionHandler *preHandle;

@interface RTExceptionHandler ()

@property (nonatomic,weak) id <RTExceptionHandlerDelegate> delegate;

@end

@implementation RTExceptionHandler

singleton_implementation(RTExceptionHandler, handle)

- (void)InstallUncaughtExceptionHandler:(id <RTExceptionHandlerDelegate>)delegate
{
    self.delegate = delegate;
    
    preHandle = NSGetUncaughtExceptionHandler();

    InstallUncaughtExceptionHandler();
}

+ (NSArray *)backtrace
{
    void  *callstack[128];
    int frames = backtrace(callstack, 128);
    char **strs = backtrace_symbols(callstack, frames);
    
    int I;
    NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
    for (
         i = ExceptionHandlerSkipAddressCount;
         i < ExceptionHandlerSkipAddressCount +
         ExceptionHandlerReportAddressCount;
         I++)
    {
        [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
    }
    free(strs);
    return backtrace;
}


- (void)signalHandlerException:(NSException *)exception
{
    // 异常日志获取
    NSString *excpCnt = [NSString stringWithFormat:@"exceptionType: %@ \n reason: %@ \n stackSymbols: %@",@"signal",[exception reason], [[exception userInfo] objectForKey:ExceptionHandlerAddressesKey]];
    
    if ([NSThread currentThread] == [NSThread mainThread]) {
        if ([self.delegate respondsToSelector:@selector(handleExceptionContent:)]) {
            [self.delegate handleExceptionContent:excpCnt];
        }
    }else
    {
        dispatch_async(dispatch_get_main_queue(), ^{
            if ([self.delegate respondsToSelector:@selector(handleExceptionContent:)]) {
                [self.delegate handleExceptionContent:excpCnt];
            }
        });
    }
}

- (void)handleException:(NSException *)exception
{
    // 异常日志获取
    NSArray  *excpArr = [exception callStackSymbols];
    NSString *reason = [exception reason];
    NSString *name = [exception name];
    NSString *excpCnt = [NSString stringWithFormat:@"exceptionType: %@ \n reason: %@ \n stackSymbols: %@",name,reason,excpArr];
    
    if ([NSThread currentThread] == [NSThread mainThread]) {
        if ([self.delegate respondsToSelector:@selector(handleExceptionContent:)]) {
            [self.delegate handleExceptionContent:excpCnt];
        }
    }else
    {
        dispatch_semaphore_t sem = dispatch_semaphore_create(0);
        dispatch_async(dispatch_get_main_queue(), ^{
            if ([self.delegate respondsToSelector:@selector(handleExceptionContent:)]) {
                [self.delegate handleExceptionContent:excpCnt];
            }
            dispatch_semaphore_signal(sem);
        });
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    }
    
    if (preHandle) {
        preHandle(exception);
    }
}

@end

void HandleException(NSException *exception)
{
    int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
    if (exceptionCount > UncaughtExceptionMaximum)
    {
        return;
    }
    [[RTExceptionHandler sharedhandle] handleException:exception];
}

void SignalHandler(int signal)
{
    int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
    if (exceptionCount > UncaughtExceptionMaximum)
    {
        return;
    }
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:ExceptionHandlerSignalKey];
    NSArray *callStack = [RTExceptionHandler backtrace];
    [userInfo setObject:callStack forKey:ExceptionHandlerAddressesKey];
    [[RTExceptionHandler sharedhandle] signalHandlerException:[NSException exceptionWithName:ExceptionHandlerSignalExceptionName reason:[NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.", nil),signal] userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:ExceptionHandlerSignalKey]]];
}

void InstallUncaughtExceptionHandler(void)
{
    NSSetUncaughtExceptionHandler(&HandleException);
    signal(SIGABRT, SignalHandler);
    signal(SIGILL, SignalHandler);
    signal(SIGSEGV, SignalHandler);
    signal(SIGFPE, SignalHandler);
    signal(SIGBUS, SignalHandler);
    signal(SIGPIPE, SignalHandler);
}

4.捕获异常信息分析

捕捉到这些异常后,需要怎么解决这些bug呢?如果是NSSetUncaughtExceptionHandler函数捕捉到的异常,一般情况下已经有详细的说明,这里就不在详细解释。对于一些signal信号量类的异常,要如何去处理呢。不同的类型,有不同的方法,这里介绍几个技巧。

查看内存分配空间:

dwarfdump --arch=armv64 --lookup 0x1000c11f0 xxx.app.dSYM

iOS内存错误EXC_BAD_ACCESS的解决方法(message sent to deallocated instance)

iOS崩溃crash大解析

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容