文档章节

iOS Mach异常和signal信号

 猫耳m
发布于 03/13 15:39
字数 1916
阅读 96
收藏 0
点赞 0
评论 0

摘要: 本着探究下iOS Crash捕获的目的,学习了下Crash捕获相关的Mach异常和signal信号处理,记录下相关内容,并提供对应的测试示例代码。Mach为XNU的微内核,Mach异常为最底层的内核级异常,在iOS系统中,底层Crash先触发Mach异常,然后再转换为对应的signal信号。

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

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

本着探究下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分两类:

Input sources

Port-Based sources 
Custom Input sources 
Timer sources 
其中Port-Based sources即基于Mach Port,在Runloop中完成消息传递。

上述的Mach API为内核层透出接口,Cocoa Foundation和Core Foundation层分别封装了Mach Port的接口供调用,参考:Apple - Runloop Programming Guard,有详细的示例代码。

2. 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: %d\n }",
          si->si_signo,
          si->si_errno,
          si->si_code,
          si->si_pid,
          si->si_uid,
          si->si_status,
          si->si_value.sival_int);
}

3. 参考

Apple - Understanding and Analyzing Application Crash Reports 
Apple - Runloop Programming Guard 
Apple - Mach Overview 
漫谈iOS Crash收集框架 
The LLDB Debugger

识别以下二维码,阅读更多干货 

© 著作权归作者所有

共有 人打赏支持
粉丝 2
博文 255
码字总数 496934
作品 0
iOS逆向工程- 学习整理(工具详解)

前言 一、逆向工程的要求 具备丰富的 iOS 开发经验 最好能非常熟悉 iOS 设备的硬件构成,iOS 系统的运行原理。 拿到任意一个 App 之后能够大致推断出它的项目规模和使用的技术,比如它的MVC模...

_小迷糊 ⋅ 05/11 ⋅ 0

你知道我为什么特别讨厌程序员吗?

你知道我为什么特别讨厌程序员吗? 2018-05-28 11:20编辑: 枣泥布丁分类:程序人生来源:程序师 程序员修电脑装系统 招聘信息: C++工程师 Cocos2d-x游戏客户端开发 iOS开发工程师 京东招聘...

枣泥布丁 ⋅ 05/28 ⋅ 0

【AR】开始使用Vuforia开发iOS(2)

原 设置iOS开发环境 安装Vuforia iOS SDK 如何安装Vuforia iOS示例 编译并运行Vuforia iOS示例 支持iOS金属 iOS 64位迁移 设置iOS开发环境 适用于iOS的Vuforia引擎目前支持运行iOS 9及更高版...

lichong951 ⋅ 06/11 ⋅ 0

HDU ~ 6297 ~ CCPC直播 (模拟,输出格式控制)

思路:模拟就行了,注意Running和RTE的开头字母一样。 iomanip是I/O流控制头文件,就像printf的格式化输出一样。 以下是一些常用的: dec 置基数为10 相当于"%d" hex 置基数为16 相当于"%X" oc...

zscdst ⋅ 05/29 ⋅ 0

苹果对体无完肤的 iOS 11 最后的弥补

点击上方“CSDN”,选择“置顶公众号” 关键时刻,第一时间送达! 距离万众瞩目的苹果开发者大会(WWDC 2018)的召开还有不到一周时间,苹果公司于今天凌晨,正式发布了 iOS 11 的第十四次更...

csdnnews ⋅ 05/30 ⋅ 0

面试官自述:面向高级开发人员的iOS面试问题

当您准备进行技术性iOS面试时,了解您可能会询问哪些主题以及经验丰富的iOS开发人员期望什么是非常重要的。 这是许多硅谷公司用来衡量iOS候选人资历水平的一系列问题。 这些问题涉及iOS开发的...

菇哒微课 ⋅ 04/26 ⋅ 0

天生不凡ios怎么在电脑上玩 天生不凡ios电脑版玩法教程

天生不凡ios怎么在电脑上玩呢?现在有很多小伙伴都在玩天生不凡手游,不过相对于传统的在手机上进行游戏,很多人都倾向于新玩法,就是在电脑上玩天生不凡手游。下面小编就给亲们介绍下天生不...

kaopu8520 ⋅ 05/29 ⋅ 0

又拍云Prism CDN 超低价 低延时 超强兼容的P2P-CDN

又拍云Prism CDN 超低价 低延时 超强兼容的P2P-CDN 2018-05-15 10:26编辑: 枣泥布丁分类:业界动态来源:https://www.upyun.com/products CDN又拍云 招聘信息: C++工程师 Cocos2d-x游戏客户...

枣泥布丁 ⋅ 05/15 ⋅ 0

Dhar/YTTInjectedContentKit

YTTInjectedContentKit iOS壳版本场景下的批量修改类名、属性名、插入混淆代码、修改项目名称的shell脚本 具体的实现和使用方法请参考我的博客文章: iOS使用shell脚本注入混淆内容 iOS使用S...

Dhar ⋅ 05/04 ⋅ 0

如何判断你是合格的高级iOS开发工程师?

前言 随着移动互联网的高速发展泄洪而来,有意学习移动开发的人越来越多了,竞争也是越来越大,需要学习的东西很多。如何才能在激烈的移动开发者竞争中一枝独秀,成为一名真正合格的高级iOS...

_小迷糊 ⋅ 05/26 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

vim基础-编辑模式-命令模式

编辑模式:可以编辑修改文件。编辑模式下 按“esc”键返回一般模式。 按一次“Insert”键 (一般在键盘回格键右边)作用和“i”一样表示“插入”。按两次“Insert”键表示“替换”,作用为:...

ZHENG-JY ⋅ 16分钟前 ⋅ 0

MaxCompute读取分析OSS非结构化数据的实践经验总结

摘要: 本文背景 很多行业的信息系统中,例如金融行业的信息系统,相当多的数据交互工作是通过传统的文本文件进行交互的。此外,很多系统的业务日志和系统日志由于各种原因并没有进入ELK之类...

阿里云云栖社区 ⋅ 21分钟前 ⋅ 0

Linux操作系统有何优势?Linux学习

  当今世界流行的操作系统有3大类,Linux、Mac OS和Windows操作系统,Linux操作系统因其开源、免费、跨平台、良好的界面等特性,深受广大程序员们的青睐!   Linux操作系统被广泛的应用于...

老男孩Linux培训 ⋅ 22分钟前 ⋅ 0

Spring Cloud Spring Boot mybatis分布式微服务云架构 开发Web应用

静态资源访问 在我们开发Web应用的时候,需要引用大量的js、css、图片等静态资源。 默认配置 Spring Boot默认提供静态资源目录位置需置于classpath下,目录名需符合如下规则: /static /pub...

itcloud ⋅ 26分钟前 ⋅ 0

6月19日任务 设置更改root密码、连接mysql、mysql常用命令

13.1 设置更改root密码 1. /usr/local/mysql/bin/mysql -uroot 设置环境变量 : export PATH=$PATH:/usr/local/mysql/bin/ 永久生效: vim /etc/profile 加入 export PATH=$PATH:/usr/local/m......

吕湘颖 ⋅ 28分钟前 ⋅ 0

MaxCompute读取分析OSS非结构化数据的实践经验总结

摘要: 本文背景 很多行业的信息系统中,例如金融行业的信息系统,相当多的数据交互工作是通过传统的文本文件进行交互的。此外,很多系统的业务日志和系统日志由于各种原因并没有进入ELK之类...

猫耳m ⋅ 29分钟前 ⋅ 0

Spring MVC controller,return重定向redirect:

@RequestMapping(value="/save",method=RequestMethod.POST)public String doSave(Course course) {log.debug("Info of Course");log.debug(ReflectionToStringBuilder.toStr......

颖伙虫 ⋅ 37分钟前 ⋅ 0

JavaSE——线程介绍

声明:本栏目所使用的素材都是凯哥学堂VIP学员所写,学员有权匿名,对文章有最终解释权;凯哥学堂旨在促进VIP学员互相学习的基础上公开笔记。 线程: 介绍:管线程叫多任务处理,首先你得知道...

凯哥学堂 ⋅ 40分钟前 ⋅ 0

ORM——使用spring jpa data实现逻辑删除

前言 在业务中是忌讳物理删除数据的,数据的这个对于一个IT公司可以说是最核心的资产,如果删除直接就物理删除,无疑是对核心资产的不重视,可能扯的比较远,本文最主要是想通过spring jpa ...

alexzhu592 ⋅ 47分钟前 ⋅ 0

CDN caching

Incapsula应用感知CDN使用智能分析和频率分析来动态缓存内容,并最大限度地提高效率。确保可直接从RAM获取最常访问的资源,而不依赖于较慢的访问机制。 1、 静态内容缓存 Incapsula缓存静态内...

上树的熊 ⋅ 50分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部