iOS crash分类,Mach异常、Unix 信号和NSException 异常

Crash的主要原因是你的应用收到了未处理的信号。

未处理信号可能来源于三个地方:kernel(系统内核)、其他进程、以及App本身。

因此,crash异常也分为三种:

  • Mach异常:是指最底层的内核级异常。用户态的开发者可以直接通过Mach API设置thread,task,host的异常端口,来捕获Mach异常。
  • Unix信号:又称BSD 信号,如果开发者没有捕获Mach异常,则会被host层的方法ux_exception()将异常转换为对应的UNIX信号,并通过方法threadsignal()将信号投递到出错线程。可以通过方法signal(x, SignalHandler)来捕获single。
  • NSException:应用级异常,它是未被捕获的Objective-C异常,导致程序向自身发送了SIGABRT信号而崩溃,对于未捕获的Objective-C异常,是可以通过try catch来捕获的,或者通过NSSetUncaughtExceptionHandler()机制来捕获。

Mach异常与Unix信号

Mach异常是什么?它又是如何与Unix信号建立联系的? Mach是一个XNU的微内核核心,Mach异常是指最底层的内核级异常 。每个thread,task,host都有一个异常端口数组,Mach的部分API暴露给了用户态,用户态的开发者可以直接通过Mach API设置thread,task,host的异常端口,来捕获Mach异常,抓取Crash事件。

所有Mach异常未处理,它将在host层被ux_exception转换为相应的Unix信号,并通过threadsignal将信号投递到出错的线程。iOS中的 POSIX API 就是通过 Mach 之上的 BSD 层实现的。

看看Matt大神的回答

EXC_BAD_ACCESS is a Mach exception sent by the kernel to your application when you try to access memory that is not mapped for your application. If not handled at the Mach level, it will be translated into a SIGBUS or SIGSEGV BSD signal.

EXC_BAD_ACCESS是当您试图访问未映射到应用程序的内存时,内核向应用程序发送的一个Mach异常。如果不在Mach级进行处理,将转换为SIGBUS或SIGSEGV BSD信号。

捕获Mach异常或者Unix信号都可以抓到crash事件,这两种方式哪个更好呢?

优选Mach异常,因为Mach异常处理会先于Unix信号处理发生,如果Mach异常的handler让程序exit了,那么Unix信号就永远不会到达这个进程了。

如果优选Mach来捕获异常,为什么还要转化为unix信号呢?

转换Unix信号是为了兼容更为流行的POSIX标准(SUS规范),这样不必了解Mach内核也可以通过Unix信号的方式来兼容开发。

为什么第三方库PLCrashReporter即使在优选捕获Mach异常的情况下,也放弃捕获Mach异常EXC_CRASH,而选择捕获与之对应的SIGABRT信号?

We still need to use signal handlers to catch SIGABRT in-process. The kernel sends an EXC_CRASH mach exception to denote SIGABRT termination. In that case, catching the Mach exception in-process leads to process deadlock in an uninterruptable wait. Thus, we fall back on BSD signal handlers for SIGABRT, and do not register for EXC_CRASH.

我们仍然需要使用信号处理程序来捕获进程中的SIGABRT。内核发送一个EXC_CRASH mach异常来表示SIGABRT终止。在这种情况下,在进程中捕获Mach异常会导致进程在不间断等待中死锁。因此,对于SIGABRT,我们依赖BSD信号处理程序,而不注册EXC_崩溃。

注意:
因为硬件产生的信号(通过CPU陷阱)被Mach层捕获,然后才转换为对应的Unix信号;苹果为了统一机制,于是操作系统和用户产生的信号(通过调用kill和pthread_kill)也首先沉下来被转换为Mach异常,再转换为Unix信号。

也就是说整个流程是这样的:
硬件产生信号或者kill或pthread_kill信号 --> Mach异常 --> Unix信号(SIGABRT)

因此,捕获crash的流程是这样的

Crash收集方式

通过UncaughtExceptionHandler机制收集

这种手机方式只适合收集应用级异常,我们要做的就是用自定义的函数替代该ExceptionHandler即可。

// 记录之前的崩溃回调函数
static NSUncaughtExceptionHandler *previousUncaughtExceptionHandler = NULL;

@implementation NWUncaughtExceptionHandler

#pragma mark - Register

+ (void)registerHandler {
    //将先前别人注册的handler取出并备份
    previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
    
    NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
}

#pragma mark - Private

// 崩溃时的回调函数
static void UncaughtExceptionHandler(NSException * exception) {
    // 异常的堆栈信息
    NSArray * stackArray = [exception callStackSymbols];
    // 出现异常的原因
    NSString * reason = [exception reason];
    // 异常名称
    NSString * name = [exception name];
    
    NSString * exceptionInfo = [NSString stringWithFormat:@"========uncaughtException异常错误报告========\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@", name, reason, [stackArray componentsJoinedByString:@"\n"]];
    
    // 保存崩溃日志到沙盒cache目录
    [NWCrashTool saveCrashLog:exceptionInfo fileName:@"Crash(Uncaught)"];
    
    //在自己handler处理完后自觉把别人的handler注册回去,规规矩矩的传递
    if (previousUncaughtExceptionHandler) {
        previousUncaughtExceptionHandler(exception);
    }
    
    // 杀掉程序,这样可以防止同时抛出的SIGABRT被SignalException捕获
    kill(getpid(), SIGKILL);
}

@end

注意:

在自己的程序里集成多个Crash日志收集服务实是否是明智之举?

通常情况下,第三方功能性SDK都会集成一个Crash收集服务,以及时发现自己SDK的问题。当各家的服务都以保证自己的Crash统计正确完整为目的时,难免出现时序手脚,强行覆盖等等的恶意竞争,就会导致在其之前注册过的日志收集服务写出的Crash日志因为取不到NSException而丢失Last Exception Backtrace等信息。

因此,如果同时有多方通过NSSetUncaughtExceptionHandler注册异常处理程序,正确的作法是:后注册者通过NSGetUncaughtExceptionHandler将先前别人注册的handler取出并备份,在自己handler处理完后自觉把别人的handler注册回去,规规矩矩的传递

未设置NSSetUncaughtExceptionHandler的NSException最后会转成Unix信号吗?

无论设置NSSetUncaughtExceptionHandler与否,只要未被try catch,最终都会被转成Unix信号,只不过设置了无法在其ExceptionHandler中无法获得最终发送的Unix信号类型

Mach异常方式

这种基本没用过。

Unix信号

Unix信号:signal(SIGSEGV,signalHandler);

SIGABRT is a BSD signal sent by an application to itself when an NSException or obj_exception_throw is not caught.

SIGABRT是应用程序在未捕获NSException或obj_exception_抛出时向自身发送的BSD信号。

但是,这并不能代表SIGABRT就是 NSException导致,因为SIGABRT是调用abort()生成的信号。
若程序因NSException而Crash,系统日志中的Last Exception Backtrace信息是完整准确的。

#import "NWCrashSignalExceptionHandler.h"
#import <execinfo.h>
#import "NWCrashTool.h"

typedef void(*SignalHandler)(int signal, siginfo_t *info, void *context);

static SignalHandler previousABRTSignalHandler = NULL;
static SignalHandler previousBUSSignalHandler = NULL;
static SignalHandler previousFPESignalHandler = NULL;
static SignalHandler previousILLSignalHandler = NULL;
static SignalHandler previousPIPESignalHandler = NULL;
static SignalHandler previousSEGVSignalHandler = NULL;
static SignalHandler previousSYSSignalHandler = NULL;
static SignalHandler previousTRAPSignalHandler = NULL;

@implementation NWCrashSignalExceptionHandler

+ (void)registerHandler {
    // 将先前别人注册的handler取出并备份
    [self backupOriginalHandler];
    
    [self signalRegister];
}

+ (void)backupOriginalHandler {
    struct sigaction old_action_abrt;
    sigaction(SIGABRT, NULL, &old_action_abrt);
    if (old_action_abrt.sa_sigaction) {
        previousABRTSignalHandler = old_action_abrt.sa_sigaction;
    }
    
    struct sigaction old_action_bus;
    sigaction(SIGBUS, NULL, &old_action_bus);
    if (old_action_bus.sa_sigaction) {
        previousBUSSignalHandler = old_action_bus.sa_sigaction;
    }
    
    struct sigaction old_action_fpe;
    sigaction(SIGFPE, NULL, &old_action_fpe);
    if (old_action_fpe.sa_sigaction) {
        previousFPESignalHandler = old_action_fpe.sa_sigaction;
    }
    
    struct sigaction old_action_ill;
    sigaction(SIGILL, NULL, &old_action_ill);
    if (old_action_ill.sa_sigaction) {
        previousILLSignalHandler = old_action_ill.sa_sigaction;
    }
    
    struct sigaction old_action_pipe;
    sigaction(SIGPIPE, NULL, &old_action_pipe);
    if (old_action_pipe.sa_sigaction) {
        previousPIPESignalHandler = old_action_pipe.sa_sigaction;
    }
    
    struct sigaction old_action_segv;
    sigaction(SIGSEGV, NULL, &old_action_segv);
    if (old_action_segv.sa_sigaction) {
        previousSEGVSignalHandler = old_action_segv.sa_sigaction;
    }
    
    struct sigaction old_action_sys;
    sigaction(SIGSYS, NULL, &old_action_sys);
    if (old_action_sys.sa_sigaction) {
        previousSYSSignalHandler = old_action_sys.sa_sigaction;
    }
    
    struct sigaction old_action_trap;
    sigaction(SIGTRAP, NULL, &old_action_trap);
    if (old_action_trap.sa_sigaction) {
        previousTRAPSignalHandler = old_action_trap.sa_sigaction;
    }
}

+ (void)signalRegister {
    NWSignalRegister(SIGABRT);
    NWSignalRegister(SIGBUS);
    NWSignalRegister(SIGFPE);
    NWSignalRegister(SIGILL);
    NWSignalRegister(SIGPIPE);
    NWSignalRegister(SIGSEGV);
    NWSignalRegister(SIGSYS);
    NWSignalRegister(SIGTRAP);
}

#pragma mark - Private

#pragma mark Register Signal

static void NWSignalRegister(int signal) {
    struct sigaction action;
    action.sa_sigaction = NWSignalHandler;
    action.sa_flags = SA_NODEFER | SA_SIGINFO;
    sigemptyset(&action.sa_mask);
    sigaction(signal, &action, 0);
}

#pragma mark SignalCrash Handler

static void NWSignalHandler(int signal, siginfo_t* info, void* context) {
    NSMutableString *mstr = [[NSMutableString alloc] init];
    [mstr appendString:@"Signal Exception:\n"];
    [mstr appendString:[NSString stringWithFormat:@"Signal %@ was raised.\n", signalName(signal)]];
    [mstr appendString:@"Call Stack:\n"];
    
    // 这里过滤掉第一行日志
    // 因为注册了信号崩溃回调方法,系统会来调用,将记录在调用堆栈上,因此此行日志需要过滤掉
    for (NSUInteger index = 1; index < NSThread.callStackSymbols.count; index++) {
        NSString *str = [NSThread.callStackSymbols objectAtIndex:index];
        [mstr appendString:[str stringByAppendingString:@"\n"]];
    }
    
    [mstr appendString:@"threadInfo:\n"];
    [mstr appendString:[[NSThread currentThread] description]];
    
    // 保存崩溃日志到沙盒cache目录
    [NWCrashTool saveCrashLog:[NSString stringWithString:mstr] fileName:@"Crash(Signal)"];
    
    NWClearSignalRegister();
    
    // 调用之前崩溃的回调函数
    // 在自己handler处理完后自觉把别人的handler注册回去,规规矩矩的传递
    previousSignalHandler(signal, info, context);
    
    kill(getpid(), SIGKILL);
}

#pragma mark Signal To Name

static NSString *signalName(int signal) {
    NSString *signalName;
    switch (signal) {
        case SIGABRT:
            signalName = @"SIGABRT";
            break;
        case SIGBUS:
            signalName = @"SIGBUS";
            break;
        case SIGFPE:
            signalName = @"SIGFPE";
            break;
        case SIGILL:
            signalName = @"SIGILL";
            break;
        case SIGPIPE:
            signalName = @"SIGPIPE";
            break;
        case SIGSEGV:
            signalName = @"SIGSEGV";
            break;
        case SIGSYS:
            signalName = @"SIGSYS";
            break;
        case SIGTRAP:
            signalName = @"SIGTRAP";
            break;
        default:
            break;
    }
    return signalName;
}

#pragma mark Previous Signal

static void previousSignalHandler(int signal, siginfo_t *info, void *context) {
    SignalHandler previousSignalHandler = NULL;
    switch (signal) {
        case SIGABRT:
            previousSignalHandler = previousABRTSignalHandler;
            break;
        case SIGBUS:
            previousSignalHandler = previousBUSSignalHandler;
            break;
        case SIGFPE:
            previousSignalHandler = previousFPESignalHandler;
            break;
        case SIGILL:
            previousSignalHandler = previousILLSignalHandler;
            break;
        case SIGPIPE:
            previousSignalHandler = previousPIPESignalHandler;
            break;
        case SIGSEGV:
            previousSignalHandler = previousSEGVSignalHandler;
            break;
        case SIGSYS:
            previousSignalHandler = previousSYSSignalHandler;
            break;
        case SIGTRAP:
            previousSignalHandler = previousTRAPSignalHandler;
            break;
        default:
            break;
    }
    
    if (previousSignalHandler) {
        previousSignalHandler(signal, info, context);
    }
}

#pragma mark Clear

static void NWClearSignalRegister() {
    signal(SIGSEGV,SIG_DFL);
    signal(SIGFPE,SIG_DFL);
    signal(SIGBUS,SIG_DFL);
    signal(SIGTRAP,SIG_DFL);
    signal(SIGABRT,SIG_DFL);
    signal(SIGILL,SIG_DFL);
    signal(SIGPIPE,SIG_DFL);
    signal(SIGSYS,SIG_DFL);
}

@end

常用的Unix信号, iOS信号量报错_gcs的博客-CSDN博客

1.SIGABRT是调用abort()生成的信号,有可能是NSException也有可能是Mach异常
2.SIGBUS:非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。比如:

char *s = "hello world";
*s = 'H';

3.SIGSEGV:试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。比如:给已经release的对象发送消息
4.SIGILL:执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。
5.SIGPIPE:管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。
6.SIGSYS:非法的系统调用。
7.SIGTRAP:由断点指令或其它trap指令产生. 由debugger使用。

iOS Mach 异常、Unix 信号 和NSException 异常

iOS-底层原理: 内存优化和野指针探测

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Android和Harmony都是基于Linux内核的操作系统。 Android是一个开放源代码的移动操作系统,由Google开发。它使用基于Linux内核的系统架构,因此可以充分利用Linux的强大功能和稳定性。在Android中,Linux内核提供了硬件抽象层(Hardware Abstraction Layer,HAL),用于管理设备驱动程序和硬件抽象。HAL可以将硬件和软件分离,使得应用程序可以在不同的设备上运行,而不需要做出任何修改。 Harmony是由华为开发的操作系统,它也是基于Linux内核。Harmony的目标是为各种设备提供一个统一的操作系统,包括智能手机、平板电脑、智能穿戴设备、智能家居等。Harmony的设计理念是“分布式”,它采用了微内核架构,将操作系统拆分成多个模块,每个模块都可以独立地运行在不同的设备上,从而实现了设备间的协同工作。 iOS是由苹果公司开发的操作系统,它使用基于Unix的Darwin内核。Darwin内核是一个开放源代码的内核,它是FreeBSD和Mach内核的组合。iOS的设计理念是简洁、直观、易用,它采用了图形用户界面和触摸屏输入,为用户提供了流畅的操作体验。在iOS中,Darwin内核提供了高级的网络、文件系统和安全性能,使得iOS具有出色的稳定性和安全性。 总之,Android、Harmony和iOS都是基于强大的开源内核构建的操作系统,它们的设计理念和目标不同,但它们都为用户提供了出色的使用体验和良好的性能。 ### 回答2: Android采用的是基于Linux的内核。Linux内核是一个开源的操作系统内核,为Android系统提供了基础的硬件驱动支持和系统管理功能。它具有高度的灵活性和易扩展性,能够运行在各种不同的设备上,包括手机、平板电脑和智能电视等。Android的开源性使得它可以快速适应不同的硬件平台和技术需求,吸引了众多开发者参与其生态系统的建设。 HarmonyOS(鸿蒙操作系统)也是基于Linux的内核。与Android类似,HarmonyOS利用了Linux内核的开源特性和高度可定制化的能力。它是华为在Android基础上发展的全场景分布式操作系统,旨在实现不同设备之间的快速无缝连接和协同工作。HarmonyOS同样可以适配各种智能设备,包括智能手机、智能手表、智能音箱等。 相反,iOS采用的是基于Unix的Darwin内核。Darwin是苹果公司开发的开源操作系统,是Mac OS X和iOS的基础。它是一个经过优化的稳定内核,与Unix相似,具有高度安全性和稳定性。Darwin内核还采用了苹果独特的技术,如Mach微内核和实时内核技术。iOS通过在这个基础上进行定制和优化,为iPhone和iPad等苹果设备提供了高效、稳定和安全的操作系统。 总而言之,Android和HarmonyOS都采用了基于Linux的内核,具有高度的灵活性和开放性,使得它们适用于各种设备和技术需求。而iOS则采用了基于Unix的Darwin内核,确保了苹果设备的稳定性和安全性。这些内核的选择反映了各自操作系统的设计理念和技术定位。 ### 回答3: Android和Harmony操作系统都使用Linux内核。Linux内核是由Linus Torvalds于1991年开发的一个开源操作系统内核。它是一个免费且具有众多功能的操作系统内核,可运行在各种设备上,包括移动设备、个人电脑和服务器等。 Android操作系统基于Linux内核,是由谷歌开发和维护的移动操作系统。它提供了丰富的功能和应用程序生态系统,支持多种硬件设备和应用程序的开发。Android系统拥有广泛的用户群体,是目前全球最流行的移动操作系统之一。 Harmony操作系统也使用Linux内核,是由华为公司开发的分布式操作系统。它旨在构建一个统一、灵活、高效的操作系统生态系统,可以应用于各种设备,包括智能手机、智能家居、车联网等。Harmony OS采用了分布式架构,可以支持多设备之间的协同工作和资源共享。 而iOS操作系统则采用基于Unix的Darwin内核。Darwin内核是苹果公司开发的开源操作系统内核,它是基于FreeBSD和Mach微内核技术的组合。iOS是苹果公司为其移动设备(如iPhone和iPad)开发的操作系统。它提供了独特的用户界面和一系列专有的应用程序,以确保良好的性能和安全性。 总结来说,Android和Harmony使用的是Linux内核,而iOS采用的是基于Unix的Darwin内核。这些操作系统都具有各自特点和优势,在不同的设备和用户需求中发挥着重要的作用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值