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文件放入同一目录下

iOS奔溃信息类型捕捉与分析_第1张图片
umcrashtool工具截图

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

iOS奔溃信息类型捕捉与分析_第2张图片
umcrashtool分析截图截图

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

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

iOS奔溃信息类型捕捉与分析_第3张图片
友盟奔溃列表

2.崩溃日志详情界面

iOS奔溃信息类型捕捉与分析_第4张图片
崩溃日志详情

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

iOS奔溃信息类型捕捉与分析_第5张图片
工具使用

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

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

1.安装处理器方法

- (void)InstallUncaughtExceptionHandler:(id )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 
#import 

@protocol RTExceptionHandlerDelegate 

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

@end

@interface RTExceptionHandler : NSObject

//单例
singleton_interface(RTExceptionHandler, handle)

- (void)InstallUncaughtExceptionHandler:(id )delegate;

@end

void InstallUncaughtExceptionHandler(void);

RTExceptionHandle.m

#import "RTExceptionHandler.h"
#include 
#include 

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  delegate;

@end

@implementation RTExceptionHandler

singleton_implementation(RTExceptionHandler, handle)

- (void)InstallUncaughtExceptionHandler:(id )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大解析

你可能感兴趣的:(iOS奔溃信息类型捕捉与分析)