关于ios:iOS-Mach异常和signal信号

1次阅读

共计 6606 个字符,预计需要花费 17 分钟才能阅读完成。

摘要:本着探索下 iOS Crash 捕捉的目标,学习了下 Crash 捕捉相干的 Mach 异样和 signal 信号处理,记录下相干内容,并提供对应的测试示例代码。Mach 为 XNU 的微内核,Mach 异样为最底层的内核级异样,在 iOS 零碎中,底层 Crash 先触发 Mach 异样,而后再转换为对应的 signal 信号。

iOS 开发交换技术群:563513413,不论你是大牛还是小白都欢送入驻,分享 BAT, 阿里面试题、面试教训,探讨技术,大家一起交流学习成长!

本着探索下 iOS Crash 捕捉的目标,学习了下 Crash 捕捉相干的 Mach 异样和 signal 信号处理,记录下相干内容,并提供对应的测试示例代码。Mach 为 XNU 的微内核,Mach 异样为最底层的内核级异样,在 iOS 零碎中,底层 Crash 先触发 Mach 异样,而后再转换为对应的 signal 信号。

  1. iOS Mach 异样

1.1 XNU

Darwin 是 Mac OS 和 iOS 的操作系统,而 XNU 是 Darwin 操作系统的内核局部。XNU 是混合内核,兼具宏内核和微内核的个性,而 Mach 即为其微内核。

Darwin 操作系统和 MacOS、iOS 零碎版本号的对应如上图所示,Mac 可执行下述命令查看 Darwin 版本号。

system_profiler SPSoftwareDataType

1.2 Mach

Mach:[mʌk],操作系统微内核,是许多新操作系统的设计根底。

Mach 微内核中有几个根底概念:
Tasks,领有一组系统资源的对象,容许 ”thread” 在其中执行。
Threads,执行的根本单位,领有 task 的上下文,并共享其资源。
Ports,task 之间通信的一组受爱护的音讯队列;task 可对任何 port 发送 / 接收数据。
Message,有类型的数据对象汇合,只能够发送到 port。

1.3 模仿 Mach Message 发送

● Mach 提供大量 API,苹果文档介绍较少。

// 内核中创立一个音讯队列,获取对应的 port
mach_port_allocate();
// 授予 task 对 port 的指定权限
mach_port_insert_right();
// 通过设定参数:MACH_RSV_MSG/MACH_SEND_MSG 用于接管 / 发送 mach message
mach_msg();

下述代码模仿向 Mach Port 发送 Message,接管 Message 后做解决:
● 首先调用 createPortAndAddListener 创立 Mach Port;
● 调用 sendMachPortMessage: 向已创立的 Mach Port 发送音讯;
● 执行后果示例:
2018-02-27 09:33:37.797435+0800 xxx[54456:5198921] create a port: 41731
2018-02-27 09:33:37.797697+0800 xxx[54456:5198921] Send a mach message: [100].
2018-02-27 09:33:37.797870+0800 xxx[54456:5199525] Receive a mach message:[100], remote_port: 0, local_port: 41731, exception code: 28672
● 示例代码:
// 创立 Mach Port 并监听音讯

  • (mach_port_t)createPortAndAddListener {
    mach_port_t server_port;
    kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &server_port);
    assert(kr == KERN_SUCCESS);
    NSLog(@”create a port: %d”, server_port);

    kr = mach_port_insert_right(mach_task_self(), server_port, server_port, MACH_MSG_TYPE_MAKE_SEND);
    assert(kr == KERN_SUCCESS);

    [self setMachPortListener:server_port];

    return server_port;

}

  • (void)setMachPortListener:(mach_port_t)mach_port {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    mach_message mach_message;

    mach_message.Head.msgh_size = 1024;
    mach_message.Head.msgh_local_port = server_port;

    mach_msg_return_t mr;

    while (true) {
    mr = mach_msg(&mach_message.Head,
    MACH_RCV_MSG | MACH_RCV_LARGE,
    0,
    mach_message.Head.msgh_size,
    mach_message.Head.msgh_local_port,
    MACH_MSG_TIMEOUT_NONE,
    MACH_PORT_NULL);

    if (mr != MACH_MSG_SUCCESS && mr != MACH_RCV_TOO_LARGE) {
    NSLog(@”error!”);
    }

    mach_msg_id_t msg_id = mach_message.Head.msgh_id;
    mach_port_t remote_port = mach_message.Head.msgh_remote_port;
    mach_port_t local_port = mach_message.Head.msgh_local_port;

    NSLog(@”Receive a mach message:[%d], remote_port: %d, local_port: %d, exception code: %d”,
    msg_id,
    remote_port,
    local_port,
    mach_message.exception);

    abort();
    }
    });

}

// 向指定 Mach Port 发送音讯

  • (void)sendMachPortMessage:(mach_port_t)mach_port {
    kern_return_t kr;
    mach_msg_header_t header;
    header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
    header.msgh_size = sizeof(mach_msg_header_t);
    header.msgh_remote_port = mach_port;
    header.msgh_local_port = MACH_PORT_NULL;
    header.msgh_id = 100;

    NSLog(@”Send a mach message: [%d].”, header.msgh_id);

    kr = mach_msg(&header,
    MACH_SEND_MSG,
    header.msgh_size,
    0,
    MACH_PORT_NULL,
    MACH_MSG_TIMEOUT_NONE,
    MACH_PORT_NULL);

}

1.4 捕捉 Mach 异样
● task_set_exception_ports() 设置内核接管 Mach 异样音讯的 Port,替换为自定义的 Port 后,即可捕捉程序执行过程中产生的异样音讯。
● 执行后果示例:
2018-02-27 09:52:11.483076+0800 xxx[55018:5253531] create a port: 23299
2018-02-27 09:52:14.484272+0800 xxx[55018:5253531] Make a [BAD MEM ACCESS] now.
2018-02-27 09:52:14.484477+0800 xxx[55018:5253611] Receive a mach message:[2405], remote_port: 23555, local_port: 23299, exception code: 1
● 示例代码:

  • (void)createAndSetExceptionPort {
    mach_port_t server_port;
    kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &server_port);
    assert(kr == KERN_SUCCESS);
    NSLog(@”create a port: %d”, server_port);

    kr = mach_port_insert_right(mach_task_self(), server_port, server_port, MACH_MSG_TYPE_MAKE_SEND);
    assert(kr == KERN_SUCCESS);

    kr = task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS | EXC_MASK_CRASH, server_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE);

    [self setMachPortListener:server_port];

}

// 结构 BAD MEM ACCESS Crash

  • (void)makeCrash {
    NSLog(@” Make a [BAD MEM ACCESS] now. “);
    ((int )(0x1234)) = 122;

}

1.5 Runloop

Mach Port 的利用不止于内核级别,在 Cocoa Foundation 和 Core Foundation 层同样有其利用,比如说:Runloop。

Runloop sources 分两类:
1.Input sources
Port-Based sources
Custom Input sources

2.Timer sources
其中 Port-Based sources 即基于 Mach Port,在 Runloop 中实现消息传递。
上述的 Mach API 为内核层透出接口,Cocoa Foundation 和 Core Foundation 层别离封装了 Mach Port 的接口供调用,参考:Apple – Runloop Programming Guard,有具体的示例代码。

  1. signal 信号

signal 是一种软中断信号,提供异步事件处理机制。signal 是过程间互相传递信息的一种毛糙办法,应用场景:
过程终止相干;
终端交互;
编程谬误或硬件谬误相干,零碎遇到不可复原的谬误时触发解体机制让程序退出,比方:除 0、内存写入谬误等。
这里咱们次要思考零碎遇到不可复原的谬误时即 Crash 时,信号相干的利用。signal 信号处理是 UNIX 操作系统机制,所以 Android 平台实践上也是应用的,能够基于 signal 来捕捉 Android Native Crash。

2.1 signal 注册和解决
signal()
#import<sys/signal.h>;

注册 signal handler;
调用胜利时,会移除 signo 信号以后的操作,以 handler 指定的新信号处理程序代替;
信号处理函数返回 void,因为没有中央给该函数返回。注册自定义信号处理函数,结构 Crash 后,发出信号并执行自定义信号处理逻辑。

【附】:Xcode Debug 运行时,增加断点,在 Crash 触发前,执行 pro hand -p true -s false SIGABRT 命令。
(lldb) pro hand -p true -s false SIGABRT
NAME         PASS   STOP   NOTIFY
===========  =====  =====  ======
SIGABRT      true   false  true
2018-02-27 12:57:25.284651+0800 xxx[58061:5651844] Make a ‘NSRangeException’ now.
2018-02-27 12:57:25.294945+0800 xxx[58061:5651844] Terminating app due to uncaught exception ‘NSRangeException’, reason: ‘ -[__NSSingleObjectArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]’
2018-02-27 12:57:25.888332+0800 xxx[58061:5651844] [signal handler] – handle signal: 6
● 示例代码:
// 设置自定义信号处理函数

  • (void)setSignalHandler {
    signal(SIGABRT, test_signal_handler);

}

static void test_signal_handler(int signo) {
NSLog(@”[signal handler] – handle signal: %d”, signo);
}

// 结构 NSRangeException 异样,触发 SIGABRT 信号发送

  • (void)makeCrash {
    NSLog(@” Make a ‘NSRangeException’ now. “);
    NSArray *array = @[@”aaa”];

}

2.2 LLDB Debugger

Xcode Debug 模式运行 App 时,App 过程 signal 被 LLDB Debugger 调试器捕捉;须要应用 LLDB 调试命令,将指定 signal 解决抛到用户层解决,不便调试。
● 查看全副信号传递配置:
// process handle 缩写
pro hand
● 批改指定信号传递配置:
// option:
//   -P: PASS
//   -S: STOP
//   -N: NOTIFY
pro hand -option false 信号名

// 例:SIGABRT 信号处理在 LLDB 不进行,可持续抛到用户层
pro hand -s false SIGABRT

2.3 可重入

向内核发送信号时,过程可能执行到代码的任意地位,例:过程在执行重要操作,中断后可能产生不统一状态,或过程正在解决另一信号。因而要确保信号处理程序只执行可重入操作:
● 写中断处理程序时,假设中断过程可能处于不可重入函数中。
● 谨慎批改全局数据。

2.4 高级信号处理

signal() 函数十分根底,只提供了最低限度的信号治理的规范。而 sigaction() 零碎调用,提供更弱小的信号治理能力。当信号处理程序运行时,能够用来阻塞特定信号的接管,也能够用来获取信号发送时各种操作系统和过程状态的信息。
● 示例代码:
// 设置自定义信号处理函数

  • (void)setSignalHandlerInAdvance {
    struct sigaction act;
    // 当 sa_flags 设为 SA_SIGINFO 时,设定 sa_sigaction 来指定信号处理函数
    act.sa_flags = SA_SIGINFO;
    act.sa_sigaction = test_signal_action_handler;
    sigaction(SIGABRT, &act, NULL);

}

static void test_signal_action_handler(int signo, siginfo_t si, void ucontext) {
NSLog(@”[sigaction handler] – handle signal: %d”, signo);

// handle siginfo_t
NSLog(@”siginfo: {n si_signo: %d,n si_errno: %d,n si_code: %d,n si_pid: %d,n si_uid: %d,n si_status: %d,n si_value: %dn}”,
si->si_signo,
si->si_errno,
si->si_code,
si->si_pid,
si->si_uid,
si->si_status,
si->si_value.sival_int);
}

作者:阿里云 - 挪动云 - 大前端团队

原文链接:http://click.aliyun.com/m/43672/

正文完
 0