Abstract: In order to explore the iOS Crash capture, this paper studied the Mach exception and signal processing related to Crash capture, recorded the relevant content, and provided the corresponding test example code. Mach is the microkernel of XNU, and Mach exception is the lowest kernel-level exception. In the iOS system, low-level Crash triggers Mach exception first, and then converts it into the corresponding signal.


Author: Ali Cloud – Mobile Cloud – big front end team


The original link: click.aliyun.com/m/43672/


For the purpose of exploring iOS Crash capture, we learn Mach exceptions and signal processing related to Crash capture, record relevant content, and provide the corresponding test example code. Mach is the microkernel of XNU, and Mach exception is the lowest kernel-level exception. In the iOS system, low-level Crash triggers Mach exception first, and then converts it into the corresponding signal.


1. The iOS Mach exception occurs


1.1 XNU


Darwin is the Mac OS and iOS operating system, and XNU is the kernel part of Darwin. XNU is a hybrid kernel, with both macro and microkernel features, and Mach is its microkernel.
The figure shows the mapping between the Darwin operating system and the MacOS and iOS versions. On a Mac, run the following command to view the Darwin version.


system_profiler SPSoftwareDataType


1.2 Mach


Mach:[mʌk], the operating system microkernel, is the basis for the design of many new operating systems.


There are several basic concepts in the Mach microkernel:
Tasks, an object that has a set of system resources and allows “thread” to execute in it.
Threads, the basic unit of execution, owns the context of the task and shares its resources.
Ports, a set of protected message queues for communication between tasks; Task can send/receive data to any port.
Message, a collection of typed data objects that can only be sent to port.


1.3 Simulating Mach Message Sending


  • Mach provides a few apis and less documentation from Apple.


// Create a message queue, get the corresponding port mach_port_allocate(); // Grant task access to port mach_port_insert_right(); MACH_RSV_MSG/MACH_SEND_MSG Is used to receive/send Mach message mach_msg();


The following code simulates sending a Message to a Mach Port.
  • First call createPortAndAddListener to create a Mach Port;
  • Call sendMachPortMessage: Send a message to the MachPort you created;
  • Example execution result:
2018-02-27 09:33:37.797435+0800 XXX [54456:5198921] create a port: 2018-02-27 09:33:37.797435+0800 XXX [54456:5198921] 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: 0 41731, exception code: 28672
  • Sample code:
+ (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(); }}); } // send a message to the specified MachPort + (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 Catching Mach exceptions
  • Task_set_exception_ports () sets the Port for the kernel to receive Mach exception messages. After replacing the Port with a user-defined one, exception messages generated during program execution can be captured.
  • Example execution result:
2018-02-27 09:52:11.483076+0800 XXX [55018:5253531] create a port: [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
  • Sample code:
+ (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]; } / / construct BAD MEM ACCESS Crash – (void) makeCrash {NSLog (@ “* * * * * * * * * * Make a [BAD MEM ACCESS] now. * * * * * * * * * *”); *((int *)(0x1234)) = 122; }


1.5 Runloop


Mach Port is not only used at the kernel level, but also in the Cocoa Foundation and Core Foundation layers, such as Runloop.
Runloop sources come in two categories:
1.Input sources
Port-Based sources
Custom Input sources


2.Timer sources
Port-based Sources is Based on Mach Port to complete message transmission in Runloop.
The Above Mach API is an interface to the kernel layer. The Cocoa Foundation and Core Foundation layers respectively encapsulate the Interface to the Mach Port for call. See Apple-Runloop Programming Guard for detailed code examples.


2. Signal signal


Signal is a soft interrupt signal that provides asynchronous event processing. Signal is a crude way of communicating information between processes.
Process termination related;
Terminal interaction;
Related to programming errors or hardware errors, the system encounters an unrecoverable error that triggers a crash mechanism to exit the program, such as: division by zero, memory write errors, etc.
Here we mainly consider signal-related applications when the system encounters unrecoverable errors, namely, when Crash occurs. Signal signal processing is a UNIX operating system mechanism, so the Android platform is also theoretically used, and can be used to capture Android Native Crash based on signal.


2.1 Signal Registration and processing
Signal ()
#import<sys/signal.h>;


Register signal Handler;
When the call succeeds, the current operation of the SIGNO signal is removed and replaced by a new signal handler specified by handler.
The signal handler returns void because there is no place to return to the function. The custom signal processing function is registered, and after Crash is constructed, the signal is sent and the custom signal processing logic is executed.


[Side] : Xcode Debug runs, add breakpoints, before Crash, run pro hand -p true -s false SIGABRT command.
(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
  • Sample code:
SetSignalHandler {signal(SIGABRT, test_signal_handler); } static void test_signal_handler(int signo) { NSLog(@”[signal handler] – handle signal: %d”, signo); } // Construct NSRangeException, Trigger SIGABRT signal – (void) makeCrash {NSLog (@ “* * * * * * * * * * Make a ‘NSRangeException now. * * * * * * * * * *”); NSArray *array = @[ @”aaa” ]; }


2.2 LLDB Debugger


When an App runs in Xcode Debug mode, the App signal process is captured by the LLDB Debugger Debugger. You need to run the LLDB debugging command to switch the signal processing to the user layer for easy debugging.
  • View all signalling configurations:
// Process handle. Pro hand
  • Modify the specified signaling configuration:
// option: // -P: PASS // -S: STOP // -N: NOTIFY pro hand-option False Signal name // Example: The SIGABRT signal processing does not stop in the LLDB and can continue to be thrown to the user layer


2.3 reentrant


When sending signals to the kernel, the process may execute anywhere in the code, for example, the process may be performing an important operation, an inconsistent state may occur after an interrupt, or the process may be processing another signal. So make sure that signal handlers only perform reentrant operations:
  • Interrupt handlers are written on the assumption that the interrupt process may be in a non-reentrant function.
  • Modify global data with caution.


2.4 Advanced signal processing


The signal() function is very basic and provides only minimal standards for signal management. The sigAction () system call provides more powerful signal management capabilities. When the signal handler is running, it can be used to block the reception of a particular signal, or to obtain information about the status of various operating systems and processes at the time the signal is sent.
  • Sample code:
/ / set custom signal processing function + (void) setSignalHandlerInAdvance {struct sigaction act; // When sa_flags is set to SA_SIGINFO, set sa_sigAction to specify the signal handler function 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); }


Reference 3.
  • Apple – Understanding and Analyzing Application Crash Reports
  • Apple – Runloop Programming Guard
  • Wiki – Mach
  • Apple – Mach Overview
  • Ramble on the iOS Crash collection framework
  • The LLDB Debugger


Identify the qr code below to read more about dry goods