Crash优化与建议


本文对iOS应用上经常遇到的Crash(常规signal触发的闪退)进行分析,并结合笔者的优化经验,给出Crash分析和建议。

一、Crash与信号

1.1 Crash是如何产生的

在iOS上Crash最终都是通过signal的形式发送给应用,应用可以通过注册signal handler来选择处理或忽略大部分信号。Crash的原因有多种,系统通过不同的signal来告知app这大概是什么原因造成的,常见的信号比如SIGSEGV,SIGTRAP,SIGABRT。

1.1.1 signal

signal是一个4字节的整形数字,在iOS/OSX中定义了31个已知的信号;
crash仅仅是singal触发的一个行为。signal的用途/产生包括但不限于:

  • 显式调用kill,killpg触发signal
  • 改变子进程的状态
  • 致命性中断
  • job控制
  • timer过期
  • 各种通知,如cpu resource limit或file size limit等

signal会导致以下几种行为(action):

  • Terminate 杀死进程
  • Dump core 杀死进程并创建一个core file
  • Stop 暂停进程
  • Continue 恢复进程
  • Ignore 忽略/丢弃该信号

每个signal号都有默认的action,可以通过sigaction() 系统调用来修改signal actions,为SIG_DFL(use the default action),或者修改为SIG_IGN(ignore the signal),或者指定signal handler function(捕获signal)

常见信号与原因如下:

信号 官方注释 可能原因
SIGILL 4 illegal instruction (not reset when caught) ILL_ILLTRP at 0xxxx通常是二进制出错,典型比如app升级前后或者二进制缓存出错
SIGTRAP 5 trace trap (not reset when caught) __builtin_trap()系统调用brk触发软中断结束进程,一般是数据或参数校验异常
SIGABRT 6 abort() 调用abort(),比如典型的NS异常或C++异常
SIGKILL 9 kill (cannot be caught or ignored) 系统升级,app升级,0x8badf00d,Jetsam,XCode调试杀死app,摄像头权限变更等
SIGBUS 10 bus error 总线错误,内存访问未对齐
SIGSEGV 11 segmentation violation 内存访问越界,内存crash或者地址错误,如栈溢出等
SIGPIPE 13 write on a pipe with no one to read it 管道异常,socket通信异常
SIGSTOP 17 sendable stop signal not from tty XCode调试时pause操作可触发

其中SIGKILL,SIGSTOP信号是无法被捕获或忽略等自定义处理的;

1.1.2 signal处理流程

signal的一般流程大概如下:

在这里插入图片描述

我们可以简单的使用kill函数来向应用/线程发送信号:

int kill(pid_t pid, int sig);
int pthread_kill(pthread_t thread, int sig);
1.1.3 signal捕获

一般的signal异常捕获工具会基于上述方式来修改signal的actions或执行signal handler,从而达到异常捕获的目的。我们也可以通过修改signal actions来达到让应用直接忽略某个信号,而不发生异常退出。但是这也有个别例外情况,比如SIGKILL,SIGSTOP 无法被捕获或忽略。如下:

    struct sigaction action = {
  {0}};
    action.sa_flags = SA_SIGINFO | SA_ONSTACK;
#if KSCRASH_HOST_APPLE && defined(__LP64__)
    action.sa_flags |= SA_64REGSET;
#endif
    sigemptyset(&action.sa_mask);
    action.sa_sigaction = &handleSignal;

    for(int i = 0; i < fatalSignalsCount; i++)
    {
        KSLOG_DEBUG("Assigning handler for signal %d", fatalSignals[i]);
        if(sigaction(fatalSignals[i], &action, &g_previousSignalHandlers[i]) != 0)
        {
            char sigNameBuff[30];
            const char* sigName = kssignal_signalName(fatalSignals[i]);
            if(sigName == NULL)
            {
                snprintf(sigNameBuff, sizeof(sigNameBuff), "%d", fatalSignals[i]);
                sigName = sigNameBuff;
            }
            KSLOG_ERROR("sigaction (%s): %s", sigName, strerror(errno));
            // Try to reverse the damage
            for(i--;i >= 0; i--)
            {
                sigaction(fatalSignals[i], &g_previousSignalHandlers[i], NULL);
            }
            goto failed;
        }
    }
    
    static void handleSignal(int sigNum, siginfo_t* signalInfo, void* userContext)
{
    KSLOG_DEBUG("Trapped signal %d", sigNum);
    //...异常捕获
}

以及

    KSLOG_DEBUG(@"Backing up original handler.");
    g_previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
            
    KSLOG_DEBUG(@"Setting new handler.");
    NSSetUncaughtExceptionHandler(&handleException);
1.2 Crash日志简析

当发生Crash时我们需要尽可能的收集更多的信息,包括crash的堆栈信息以及crash前的一些用户操作记录或者等其它附加信息。
Crash日志就是我们处理Crash时最重要的信息,它记录了一个app是因为什么原因发生了闪退,我们可以根据这些信息去进行分析。

一个Crash日志大概如下:

{"app_name":"mttlite","timestamp":"2019-03-26 13:04:38.95 +0800","app_version":"9.2.0","slice_uuid":"9c96c0a4-9019-39ea-992b-da986ae8cc2d","adam_id":0,"build_version":"9.2.0.8735","bundleID":"com.tencent.mttlite","share_with_app_devs":false,"is_first_party":false,"bug_type":"109","os_version":"iPhone OS 12.0 (16A366)","incident_id":"733801EE-A7D3-44E7-9D12-06BED94B4FAC","name":"mttlite"}
Incident Identifier: 733801EE-A7D3-44E7-9D12-06BED94B4FAC
CrashReporter Key:   f4d479f63324962b4280137a
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值