iOS_Crash收集之Signal

前面主要是看下xnu的源码,要是对源码不感兴趣的童鞋,可以直接看捕捉。

什么是signal

POSIX

POSIX表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX ),POSIX标准定义了操作系统应该为应用程序提供的接口标准。

POSIX标准意在期望获得源代码级别的软件可移植性。换句话说,为一个POSIX兼容的操作系统编写的程序,应该可以在任何其它的POSIX操作系统(即使是来自另一个厂商)上编译执行。

很明显iOS和macOS也是兼容这个标准的。这个标准中有一块信号机制就是signal。

和Mach的关系

Mach已经通过异常机制提供了底层的陷阱处理,详见《iOS_Crash收集之Mach》。而BSD则在异常机制之上建立了信号处理机制。硬件产生的信号被Mach层捕捉,然后转换为对应的Unix信号。为了维护一个统一的机制,操作系统和用户产生的信号首先被转换为Mach异常,然后再转换为信号。

以上的东西讲的太宽泛,我们可以查看下xnu的源码。

//bsd/uxkern/ux_exception.c
void
ux_handler_init(void)
{
    thread_t    thread = THREAD_NULL;

    ux_exception_port = MACH_PORT_NULL;
    //启动一个新线程,开启ux_handler任务
    (void) kernel_thread_start((thread_continue_t)ux_handler, NULL, &thread);
    thread_deallocate(thread);
    //加锁,等待ux_exception_port注册成功
    proc_list_lock();
    if (ux_exception_port == MACH_PORT_NULL)  {
        (void)msleep(&ux_exception_port, proc_list_mlock, 0, "ux_handler_wait", 0);
    }
    proc_list_unlock();
}


//bsd/kern/bsd_init.c

void
bsdinit_task(void)
{
    proc_t p = current_proc();
    struct uthread *ut;
    thread_t thread;
    //设置进程名称为 init
    process_name("init", p);

    //开启 Unix异常处理线程,注册ux_exception_port
    ux_handler_init();

    thread = current_thread();
    //设置exception监听端口,这里不就是mach中的 XXXX_set_exception_ports么?
    (void) host_set_exception_ports(host_priv_self(),
                    EXC_MASK_ALL & ~(EXC_MASK_RPC_ALERT),//pilotfish (shark) needs this port
                    (mach_port_t) ux_exception_port,
                    EXCEPTION_DEFAULT| MACH_EXCEPTION_CODES,
                    0);

    ut = (uthread_t)get_bsdthread_info(thread);

#if CONFIG_MACF
    mac_cred_label_associate_user(p->p_ucred);
#endif

    vm_init_before_launchd();


    bsd_init_kprintf("bsd_do_post - done");
    //加载程序
    load_init_program(p);
    lock_trace = 1
}

通过调用host_set_exception_ports函数,bsdinit_task将所有的Mach异常消息都重定向到
ux_exception_port,这个端口被ux_handler持有,并且在ux_handler中处理异常消息。

接下来看看ux_handler是个啥?

__attribute__((noreturn))
static void
ux_handler(void)
{
    task_t      self = current_task();
    mach_port_name_t    exc_port_name;
    mach_port_name_t    exc_set_name;

    /* self->kernel_vm_space = TRUE; */
    ux_handler_self = self;


    /*
     *  分配端口,通过这个端口接受异常信息
     */
    if (mach_port_allocate(get_task_ipcspace(ux_handler_self), MACH_PORT_RIGHT_PORT_SET,  &exc_set_name) != MACH_MSG_SUCCESS)
        panic("ux_handler: port_set_allocate failed");

    /*
     *  分配异常端口,并且通过ipc_object_copyin将端口转换成全局的名称
     *  将端口放入集合
    */
    if (mach_port_allocate(get_task_ipcspace(ux_handler_self), MACH_PORT_RIGHT_RECEIVE, &exc_port_name) != MACH_MSG_SUCCESS)
    panic("ux_handler: port_allocate failed");
    if (mach_port_move_member(get_task_ipcspace(ux_handler_self),
                exc_port_name,  exc_set_name) != MACH_MSG_SUCCESS)
    panic("ux_handler: port_set_add failed");

    if (ipc_object_copyin(get_task_ipcspace(self), exc_port_name,
            MACH_MSG_TYPE_MAKE_SEND, 
            (void *) &ux_exception_port) != MACH_MSG_SUCCESS)
        panic("ux_handler: object_copyin(ux_exception_port) failed");

    proc_list_lock();
    thread_wakeup(&ux_exception_port);
    proc_list_unlock();

    /*消息处理循环 */

    for (;;) {
    struct rep_msg {
        mach_msg_header_t Head;
        NDR_record_t NDR;
        kern_return_t RetCode;
    } rep_msg;
    struct exc_msg {
        mach_msg_header_t Head;
        /* start of the kernel processed data */
        mach_msg_body_t msgh_body;
        mach_msg_port_descriptor_t thread;
        mach_msg_port_descriptor_t task;
        /* end of the kernel processed data */
        NDR_record_t NDR;
        exception_type_t exception;
        mach_msg_type_number_t codeCnt;
        mach_exception_data_t code;
        /* some times RCV_TO_LARGE probs */
        char pad[512];
    } exc_msg;
    mach_port_name_t    reply_port;
    kern_return_t    result;

    exc_msg.Head.msgh_local_port = CAST_MACH_NAME_TO_PORT(exc_set_name);
    exc_msg.Head.msgh_size = sizeof (exc_msg);
#if 0
    result = mach_msg_receive(&exc_msg.Head);
#else
    result = mach_msg_receive(&exc_msg.Head, MACH_RCV_MSG,
                 sizeof (exc_msg), exc_set_name,
                 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL,
                 0);
#endif
    if (result == MACH_MSG_SUCCESS) {
        reply_port = CAST_MACH_PORT_TO_NAME(exc_msg.Head.msgh_remote_port);
        // mach_exc_server 会调用 mach_exception_raise,
        // 然后会被 mach_catch_exception_raise捕获,
        //信号处理逻辑就在mach_catch_exception_raise
        if (mach_exc_server(&exc_msg.Head, &rep_msg.Head)) {
        result = mach_msg_send(&rep_msg.Head, MACH_SEND_MSG,
            sizeof (rep_msg),MACH_MSG_TIMEOUT_NONE,MACH_PORT_NULL);
        if (reply_port != 0 && result != MACH_MSG_SUCCESS)
            mach_port_deallocate(get_task_ipcspace(ux_handler_self), reply_port);
        }

    }
    else if (result == MACH_RCV_TOO_LARGE)
        /* ignore oversized messages */;
    else
        panic("exception_handler");
    }
}

消息被mach_catch_exception_raise 捕捉到,mach_catch_exception_raise主要就是将异常转换成signal,那就看看源码吧。

kern_return_t
catch_mach_exception_raise(
        __unused mach_port_t exception_port,
        mach_port_t thread,
        mach_port_t task,
        exception_type_t exception,
        mach_exception_data_t code,
        __unused mach_msg_type_number_t codeCnt
)
{
    task_t          self = current_task();
    thread_t        th_act;
    ipc_port_t      thread_port;
    struct proc     *p;
    kern_return_t       result = MACH_MSG_SUCCESS;
    int         ux_signal = 0;
    mach_exception_code_t   ucode = 0;
    struct uthread      *ut;
    mach_port_name_t thread_name = CAST_MACH_PORT_TO_NAME(thread);
    mach_port_name_t task_name = CAST_MACH_PORT_TO_NAME(task);

    /*
     *  Convert local thread name to global port.
     */
   if (MACH_PORT_VALID(thread_name) &&
       (ipc_object_copyin(get_task_ipcspace(self), thread_name,
               MACH_MSG_TYPE_PORT_SEND,
               (void *) &thread_port) == MACH_MSG_SUCCESS)) {
        if (IPC_PORT_VALID(thread_port)) {
       th_act = convert_port_to_thread(thread_port);
       ipc_port_release_send(thread_port);
    } else {
       th_act = THREAD_NULL;
    }

    /*
     *  Catch bogus ports
     */
    if (th_act != THREAD_NULL) {

        /*
         *  将异常转换成signal和code
         */
        ux_exception(exception, code[0], code[1], &ux_signal, &ucode);

        ut = get_bsdthread_info(th_act);
        p = proc_findthread(th_act);

        /* Can't deliver a signal without a bsd process reference */
        if (p == NULL) {
            ux_signal = 0;
            result = KERN_FAILURE;
        }

        /*
         * 堆栈溢出需要特别处理
         */
        if (code[0] == KERN_PROTECTION_FAILURE &&
        ux_signal == SIGBUS) {
            user_addr_t     sp, stack_min, stack_max;
            int         mask;
            struct sigacts  *ps;

            sp = code[1];

            stack_max = p->user_stack;
            stack_min = p->user_stack - MAXSSIZ;
            if (sp >= stack_min &&
            sp < stack_max) {
                /*
                 * This is indeed a stack overflow.  Deliver a
                 * SIGSEGV signal.
                 */
                ux_signal = SIGSEGV;

                /*
                 * If the thread/process is not ready to handle
                 * SIGSEGV on an alternate stack, force-deliver
                 * SIGSEGV with a SIG_DFL handler.
                 */
                mask = sigmask(ux_signal);
                ps = p->p_sigacts;
                if ((p->p_sigignore & mask) ||
                (ut->uu_sigwait & mask) ||
                (ut->uu_sigmask & mask) ||
                (ps->ps_sigact[SIGSEGV] == SIG_IGN) ||
                (! (ps->ps_sigonstack & mask))) {
                    p->p_sigignore &= ~mask;
                    p->p_sigcatch &= ~mask;
                    ps->ps_sigact[SIGSEGV] = SIG_DFL;
                    ut->uu_sigwait &= ~mask;
                    ut->uu_sigmask &= ~mask;
                }
            }
        }
        /*
         *  发送信号
         */
        if (ux_signal != 0) {
            ut->uu_exception = exception;
            //ut->uu_code = code[0]; // filled in by threadsignal
            ut->uu_subcode = code[1];           
            threadsignal(th_act, ux_signal, code[0], TRUE);
        }
        if (p != NULL) 
            proc_rele(p);
        thread_deallocate(th_act);
    }
    else
        result = KERN_INVALID_ARGUMENT;
    }
    else
        result = KERN_INVALID_ARGUMENT;

    /*
     *  Delete our send rights to the task port.
     */
    (void)mach_port_deallocate(get_task_ipcspace(ux_handler_self), task_name);

    return (result);
}

到这里源码上面的mach exception 转 signal 的流程就走完了。看图大概更直观:


mach-signal流程.jpg

signal类型

signal类型.jpg
signal类型2.jpg
signal解释.jpg

signal异常捕获

  • 获取已存在的异常处理句柄
  • 设置新的异常处理句柄
    //用于储存已存在的异常处理句柄
    static struct sigaction* dt_previousSignalHandlers = NULL;
    #define PreviousSignalHandlersSize sizeof(*dt_previousSignalHandlers)

    int getPreviousSignalHandlers() {
        //初始化内存。
        /*
            self.signals = @[@SIGABRT,
                     @SIGBUS,
                     @SIGFPE,
                     @SIGILL,
                     @SIGPIPE,
                     @SIGSEGV,
                     @SIGSYS,
                     @SIGTRAP];
        */
        dt_previousSignalHandlers = malloc(PreviousSignalHandlersSize * self.signals.count);
        
        struct sigaction action = {{0}};
        action.sa_flags = SA_SIGINFO | SA_ONSTACK;
    #if defined(__LP64__)
        action.sa_flags |= SA_64REGSET;
    #endif
        sigemptyset(&action.sa_mask);
        action.sa_sigaction = &handleSignal;
        
        for (int i = 0; i < self.signals.count;  i++) {
        int signum =((NSNumber *)self.signals[i]).intValue;
        //保存原来的异常处理句柄,设置新的异常处理句柄
        int result = sigaction(signum,
                               &action,
                               &dt_previousSignalHandlers[i]);
        if (result != 0) {
            for (i -- ; i >= 0 ; i --) {
                sigaction(signum,
                 &dt_previousSignalHandlers[i], 
                 NULL);
            }
            return -1;
        }
        return 0;
    }
  • 实现异常处理方法
    static void handleSignal(int sigNum, siginfo_t* signalInfo, void* userContext) {
                #ifdef __arm64__
    #define UC_MCONTEXT uc_mcontext64
        typedef ucontext64_t SignalUserContext;
    #else
    #define UC_MCONTEXT uc_mcontext
        typedef ucontext_t SignalUserContext;
    #endif
        //可以从uc中拿到backtrace

        _STRUCT_MCONTEXT *uc = ((SignalUserContext*)userContext)->UC_MCONTEXT;
        
      int signalNum = signalInfo->si_signo;
      int signalCode = signalInfo->si_code;
      uintptr_t  signalAddress = (uintptr_t)signalInfo->si_addr;    
      raise(sigNum);

    }
  • 重置异常处理句柄

      void resetSignalHandler() {
          for (int i = 0; i < self.signals.count; ++ i) {
              int signum =((NSNumber *)self.signals[i]).intValue;
    
              int result = sigaction(signum, &cuckoo_previousSignalHandlers[i], NULL);
              if (result != 0) {
                  CuckooError(@"Sigaction uninstall failure:using default signal %d",signum);
                  struct sigaction sa;
                  
                  memset(&sa, 0, sizeof(sa));
                  sa.sa_handler = SIG_ERR;
                  sigemptyset(&sa.sa_mask);
                  sigaction(signum, &sa, NULL);
              }
          }
      }
    

堆栈解析###

详见堆栈解析

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

推荐阅读更多精彩内容