`
猫耳呀
  • 浏览: 165694 次
社区版块
存档分类
最新评论

iOS Mach异常和signal信号

ios 
阅读更多
摘要: 本着探究下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分两类:
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,有详细的示例代码。

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
  ● Wiki - Mach
  ● Apple - Mach Overview
  ● 漫谈iOS Crash收集框架
  ● The LLDB Debugger

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

分享到:
评论

相关推荐

    iOS Crash日志收集上报

    iOS Crash日志收集上报 iOS Crash日志收集上报是指在iOS系统中,收集和上报...iOS Crash日志收集上报是指在iOS系统中,收集和上报应用程序崩溃日志的过程,涉及到Mach异常、Unix Signal、NSException等多个技术层面。

    mach3雕刻机软件

    对于描述中提到的BL-MACH-V1.1接口板,这是专为MACH3设计的一款硬件接口,能够将电脑的数字信号转换为驱动雕刻机电机所需的模拟信号。在设置过程中,特别需要注意的是,此接口板采用共阳极接线方式。这意味着电源的...

    Mach3_Software_MACH3_cnc_

    2. **实时性能**:软件具备实时操作系统的特点,能快速响应输入信号,保证了CNC设备的精度和安全性。 3. **用户界面**:Mach3拥有直观的图形化用户界面,使得操作人员可以轻松编程和监控设备状态。 4. **配置灵活**...

    MACH3并口板使用说明

    4. 所有输入信号都经过光耦隔离,这可以保护电脑免受外部信号的干扰,同时也支持接急停、对刀、限位等信号,进一步保障设备和操作人员的安全。 5. 设备具有一路继电器输出端口,可以控制主轴开关,输出口为P17口。 ...

    Mach3Chinese-Documents.rar_CNC MACH3_MACH3_mach3 cnc_雕刻_雕刻机

    Mach3是一款广泛应用在数控机床和雕刻机领域的控制软件,尤其在DIY和小型工坊中享有盛誉。这款软件以其易用性和强大的功能,使得用户能够高效地控制设备进行精确的雕刻操作。本文将深入探讨Mach3的中文使用文档,...

    MACH3步进电机驱动说明书

    通常,MACH3的步进信号输出端口(如STEP和DIR)需要连接到驱动器的相应输入端,同时还需要设置合适的电源电压和电流来确保电机正常工作。 **配置MACH3** 在使用MACH3驱动步进电机之前,需要进行一系列的配置。首先...

    XCTAssertCrash:使用Mach异常处理程序或POSIX信号处理程序断言表达式崩溃

    XCTAssertCrash 通过使用Mach异常处理程序或POSIX信号处理程序来声明表达式崩溃。 在除tvOS之外的Apple平台上,它使用Mach Exception Handler。 在Linux或tvOS等其他平台上,它使用POSIX Signal Handler。用法/// ...

    MachRemote.zip_mach3 二次开发_mach3开发_mach3编程实例_machremote_二次开发Mach3

    MACH3软件二次开发的一个实例程序,希望对喜欢MACH3二次开发的朋友有帮助。

    mastercam出mach3代码的后处理文件

    MasterCAM是一款广泛应用于机械设计和制造领域的CAD/CAM软件,它集成了计算机辅助设计与计算机辅助制造的功能,能够帮助用户创建、编辑和转换CAD模型,并生成用于数控机床(CNC)的加工代码。MACH3则是一种流行的CNC...

    Mach3 V3.043.066 汉化版

    汉化的界面涵盖了所有功能模块,包括参数设置、运动控制、信号输入输出等,使得用户在设置和操作过程中无需依赖翻译,大大提高了工作效率。 二、兼容性:支持WIN10系统 在不断更新换代的Windows操作系统中,Mach3 ...

    mach3车螺纹攻丝自动换刀实现

    例如,当刀具更换完成后,可以通过M代码触发一个信号,使得警示灯亮起,提示操作员。 主轴模式切换M代码,如"Mach3-nMotion动态变换管脚做法"中所述,允许用户在不同的主轴速度和方向之间切换,适应不同的加工需求...

    mach3六轴界面文件.zip

    Mach3是一款流行的计算机数字控制(CNC)软件,主要用于驱动和控制各种数控设备,如雕刻机、铣床和车床。"mach3六轴界面文件.zip" 是一个包含Mach3六轴控制界面相关组件的压缩包。六轴意味着系统能够同时控制六个...

    Mach3nastroika_MACH3_cnc_

    1. **硬件配置**:MACH3支持多种类型的步进电机和伺服驱动器,正确配置硬件接口参数至关重要,如脉冲频率、方向信号延迟、使能状态等。 2. **运动控制参数**:包括速度控制、加减速曲线设置、电子齿轮比等,这些...

    工业级以太网接口Mach3运动控制卡使用说明.pdf

    网络连接可以实现实时监控和控制,使得工业自动化生产更加智能化和高效化。 电机驱动模块 电机驱动模块是 Mach3 运动控制卡的重要组件,负责驱动电机的运行。电机驱动模块可以驱动四轴 stepper 或 servo 电机, ...

    iOS 拦截奔溃 使程序不在崩溃

    在这个例子中,我们添加了一个Mach端口作为Runloop的观察者,当接收到SIGABRT或其他异常信号时,Runloop会触发通知。我们在`handleSignal:`方法中处理异常,比如记录日志、发送错误信息到服务器,然后安全地结束程序...

    Mach3.04破解及汉化

    DIY数控雕刻机 cnc机床的软件。...2.把(Mach3.04汉化)目录下汉化文件Mach复制到安装要目录下 3.把(Mach3.04破解证书)目录下的Mach1Lic.dat复制到安装要目录下 4.运行桌面上的Mach3Mill (OK) http://tooling.5d6d.com/

    mach3 中文版软件与使用说明书

    安装完成后,按照说明书的步骤进行配置,包括设置接口类型、脉冲频率、方向信号等。 4. **Mach3操作界面** 软件界面分为多个部分,如工具栏、状态栏、坐标轴显示区、G代码窗口等,用户需了解每个部分的功能并掌握...

    mach3_OEM代码____功能

    Mach3_OEM代码是一种为数控机床系统设计的OEM代码,旨在提供一系列的功能模块,以满足不同行业和应用场景的需求。根据提供的内容,我将对Mach3_OEM代码的主要功能点进行总结和解释。 1. 位置读取器:Mach3_OEM代码...

Global site tag (gtag.js) - Google Analytics