Abstract

Objective – C is the application layer of the anomaly, we can use @ the try @ the catch (captured) or NSSetUncaughtExceptionHandler (record) function to capture or record abnormal (handle exceptions), An Objective-C exception is an application-level exception because when an Objective-C exception occurs, only an NSException is raised /@throw and only the NSException is handled if it is not caught Object, and the process ends when the system calls abort, Abort calls (void)pthread_kill(pthread_self(), SIGABRT) and sends SIGABRT to the current thread. In addition to objective-C exceptions, Mach exceptions caused by errors such as memory access errors and repeated release need to be handled in other ways (such as wild pointer access, repeated release under MRC, etc.), resulting in EXC_BAD_ACCESS Mach The process is interrupted due to an exception. We call abort and we call pthread_kill and we call SIGABRT to the current thread and the process is aborted, Alternatively, we can call pthread_kill(pthread_self(), SIGSEGV) manually to abort the process and then receive the Signal callback. In this article we’ll start learning about Mach exception handling and Signal handling.

Objective-c exception handling:

  • Generate an exception object: if an exception occurs in @try, the system will generate an NSException exception object. If the object is submitted to the system, the system will throw an exception.
  • Exception handling process: When the runtime receives an NSException, it passes the exception to @catch if there is an @catch block that can handle the exception. This process catches the exception. If there is no @catch block to handle the exception, the program is terminated (abort()).
  • The process of catching @catch code block: When the runtime environment receives an exception object, it will successively determine whether the type of the exception object is an exception in the @catch code block or its subclass instance. If the match is successful, the matched @catch will process the exception and compare it with the next @catch code block.
  • @catch handle exceptions: The system passes the exception object of NSException to the @catch parameter. The @catch parameter obtains detailed information about the exception object of NSException. After some processing, the process can continue. You can also call raise/@throw to continue throwing an NSException using the program to terminate the exception object.

Some connections between Objective-C exceptions, Mach exceptions, and Signal signals

Objective-c exception (NSException) is an application-level exception. The biggest difference between It and other two exceptions is that Mach exception and Unix signal exception are hardware level exceptions, while NSException is software level exceptions, and there are some transition relationships between them.

  • When an Objective-C exception occurs and no capture is made, the final program terminates because the current thread receives SIGABRT, At this point we can only use the try catch or NSSetUncaughtExceptionHandler to record NSException exception handling, and finally throw SIGABRT signal at the termination of the program, We use signal(SIGABRT, SignalHandler); The Signal callback cannot be grabbed. If we call abort() manually in our program, we can grab the SIGABRT Signal. (NSException -> Signal)

  • Normally Mach exceptions are converted to Signal, but for example, after collecting Mach exceptions, calling exit() directly causes the process to exit without producing Signal, or Mach exceptions have not yet been converted to Signal. The program has already been killed (such as a memory overflow caused by an infinite loop), and Signal cannot be captured. (Mach -> Signal)

  • Some exceptions are not caught by a Mach Exception or NSException, but only by Signal because the underlying __pthread_kill function sends Signal directly to a thread.

To process signal, you need to use the SIGNAL mechanism of the Unix standard, and register the processing functions such as SIGABRT, SIGBUS, and SIGSEGV when signal occurs.

Mach exception Example

NSLog(@”✳️✳️✳️ objc: %@”, objc); NSLog(@✳️ ✳️✳️ objc: %@”, objc); The line displays the red error message Thread 1: EXC_BAD_ACCESS (code=1, address= 0x3402e8D4C25c) (EXC_BAD_ACCESS (code=1, address= 0x3402e8D4C25C) This indicates that our program has an EXC_BAD_ACCESS exception, which is a standard Mach exception that causes the process to exit, And can be found at this time we through setting the NSSetUncaughtExceptionHandler uncaught exception handler Before the suspension process is not implemented, Void (*signal(int, void(*)(int)))(int) is not executed because the exception is just a Mach exception. We can’t catch it using Objective-C exception handling and Signal processing.

__unsafe_unretained NSObject *objc = [[NSObject alloc] init];
NSLog(@"✳ ️ ✳ ️ ✳ ️ objc: % @", objc);
Copy the code

If we select None[-o0], we will crash. If we select None[-o0], we will crash. Thread 1: EXC_ARITHMETIC (code=EXC_I386_DIV, subcode=0x0) exception, also a standard Mach exception, And in any other Fast, Faster[-O2],Smallest[-O3],Smallest,Aggressive Optimizations[-ofast],Smallest,Aggressive Size Under Optimizations[-oz], the program runs normally without crashing, and the result value is a large random number.

int a = 0;
int b = 1;
int result = b / a;
NSLog(@"🏵 🏵 🏵 % d", result);
Copy the code

For the crash caused by the above two codes, after the program exits, we input bt instruction in the debugging console at the bottom of Xcode and press enter. It can be seen that the reasons for the program to stop running are EXC_BAD_ACCESS and EXC_ARITHMETIC respectively. For comparison, when the array is accessed beyond bounds, it is: Signal SIGABRT, another small detail, is that the program exit is actually caused by the exit of a thread, either the main thread or a sliver thread. When we put the above code on a sliver thread, we can see that the console output is the cause of the stop of a sliver thread.

(EXC_ARITHMETIC) :

// The main thread crashes
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_ARITHMETIC (code=EXC_I386_DIV, subcode=0x0)
  * frame #0: 0x0000000102d06daf dSYMDemo`-[AppDelegate application:didFinishLaunchingWithOptions:](self=0x0000600002bb82c0, _cmd="application:didFinishLaunchingWithOptions:", application=0x00007fe1dbc06f50, launchOptions=0x0000000000000000) at AppDelegate.m:59:20.// The current child thread crashes
(lldb) bt all
  thread #1, queue = 'com.apple.main-thread'
    frame #0: 0x00007fff203b69a4 CoreFoundation`__CFStringHash + 151. * thread #8, queue = 'com.apple.root.default-qos', stop reason = EXC_ARITHMETIC (code=EXC_I386_DIV, subcode=0x0)
  * frame #0: 0x00000001029a0daf dSYMDemo`__57-[AppDelegate application:didFinishLaunchingWithOptions:]_block_invoke(.block_descriptor=0x00000001029a6048) at AppDelegate.m:60:24.Copy the code

Wild pointer access (EXC_BAD_ACCESS) :

// The main thread crashes
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x539e0d66c11c)
    frame #0: 0x00007fff20190d18 libobjc.A.dylib`objc_opt_respondsToSelector + 16.// The current child thread crashes
thread #1, queue = 'com.apple.main-thread'
  frame #0: 0x00007fff2468f57e UIKitCore`+[_UIBackgroundTaskInfo backgroundTaskAssertionQueue]
  ...
  
* thread #4, queue = 'com.apple.root.default-qos', stop reason = EXC_BAD_ACCESS (code=1, address=0xbc637211426c)
    frame #0: 0x00007fff20190d18 libobjc.A.dylib`objc_opt_respondsToSelector + 16.Copy the code

As a comparison, add an out-of-bounds array crash (which ends with the abort call to pthread_kill that sends a SIGABRT signal to stop a thread, and the program aborts) :

// The main thread crashes
(lldb) bt 
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x00007fff61131462 libsystem_kernel.dylib`__pthread_kill + 10
    frame #1: 0x00007fff6116a610 libsystem_pthread.dylib`pthread_kill + 263
    frame #2: 0x00007fff200fab94 libsystem_c.dylib`abort + 120.// The current child thread crashes
(lldb) bt all
  thread #1, queue = 'com.apple.main-thread'
    frame #0: 0x00007fff204318a9 CoreFoundation`-[NSSet anyObject]
    ...

* thread #5, queue = 'com.apple.root.default-qos', stop reason = signal SIGABRT
  * frame #0: 0x00007fff61131462 libsystem_kernel.dylib`__pthread_kill + 10
    frame #1: 0x00007fff6116a610 libsystem_pthread.dylib`pthread_kill + 263
    frame #2: 0x00007fff200fab94 libsystem_c.dylib`abort + 120.Copy the code

An Objective-C exception, if left unhandled (try catch), will eventually trigger abort exit, which is caused by the program sending itself a SIGABRT signal. (in the case of Objective – C uncaught exception, we can through setting uncaught exception handler NSSetUncaughtExceptionHandler function in which the record store abnormal log, Then upload in the APP starts next time (uncaught exception handler function, program will also be suspended, at this time no chance to us to upload data network request), if an exception logging is proper, then cooperate with some exception occurs when the user’s behavior data, you can analyze and solve the collapse of most of the questions.)

Overview of Mach (What Mach is)

Mach is a bit of a cover story, so let’s start with a little bit of a primer. BSD, Mach, GUI, NeXTSTEP, macOS, POSIX, IPC, System Call, Kernel, macro Kernel, microkernel, hybrid Kernel, XNU, Darwin, and some of the relationships between them.

Here’s an excerpt from wikipedia:

BSD Berkeley Software Package: Berkeley Software Distribution BSD, also known as Berkeley Unix or Berkeley Unix, is a Unix (Unix-like) derived operating system pioneered in the 1970s by University of California, Berkeley student Bill Joy, and is also used to represent the various packages derived from it.

Mach (ie: [mʌk]) is a microkernel for computer operating systems developed by Carnegie Mellon University. The Mach development project ran at Carnegie Mellon University from 1985 to 1994, ending with Version 3.0 of Mach. Many others continued to work on Mach. Mach was developed to replace the UNIX core of BSD and is therefore the basis for the design of many new operating systems. Research on Mach seems to have stalled until now, although many commercial operating systems, such as NEXTSTEP and OPENSTEP, and especially Mac OS X (which uses the XNU core) use Mach or its derivatives.

Graphical User Interface (GUI) is a Graphical User Interface used to operate computers. Compared with the command line interface used by early computers, in addition to reducing the operation burden of users, for new users, the graphical interface is more visually acceptable to users, and the learning cost is greatly reduced, which also enables the popularization of computers to be realized.

NeXTSTEP (also known as NeXTSTEP, NeXTSTEP, NeXTSTEP) is an operating system developed by next. Inc. NeXT is the company that Jobs founded after he left Apple in 1985. The system is based on Mach and BSD, with objective-C as its native language and a very advanced GUI. Version 1.0 was released on September 18, 1989. Apple bought NeXT in February 1997, and it became the basis for Mac OS X.

“MacOS” is a gui-based operating system developed by Apple inc., the main operating system for the Macintosh series of computers. Classic Mac OS refers to the series of operating systems that apple developed for the Macintosh from 1984 to 2001, starting with System 1 and ending with Mac OS 9,1997, Steve Jobs returned to Apple, and after four years of development, Apple replaced Classic Mac OS with a new operating system, Mac OS X, in 2001. It retains most of the GUI design elements of Classic Mac OS, and there is some overlap in the application framework for compatibility, but the origins and structure of the two operating systems and the underlying code are completely different. Simply put, Mac OS X is an offshoot of Mac OS version 10, but it is completely independent of the earlier Mac OS releases in the history of Mac OS. The name Mac OS X has changed a bit over time since it was introduced in 2001. It was called Mac OS X from 2001 to 2011, OS X from 2012 to 2015, and in June 2016, Apple announced that OS X would be renamed macOS to keep the naming style consistent with apple’s other operating systems, iOS, watchOS and tvOS.

Here is a macOS architecture diagram:

POSIX Portable Operating System Interface POSIX is a series of related standards defined by IEEE for running software on UNIX operating systems. POSIX is formally known as IEEE Std 1003 and the international standard is ISO/IEC 9945. This standard stems from a project that began around 1985. POSIX is an easy-to-remember name proposed by Richard Stallman (RMS) at the request of IEEE. It is basically an abbreviation for Portable Operating System Interface, with the last letter X indicating its lineage to the Unix API.

Mac OS X is a completely separate operating system from the previous Mac OS, and its underlying code is completely different from previous versions. The new core of Mac OS X, named Darwin, is an open source, POSIX-compliant operating system with a standard Unix command line and powerful application tools. MacOS consists of two main parts:

  1. Core: Named Darwin, based on BSD source code and a Mach microcore, it was developed by Apple in collaboration with the indie community
  2. GUI: A patented graphical user interface developed by Apple called Aqua. Aqua is the trademark name for the GUI of macOS (formerly Mac OS X and OS X)

IPC: Inter-process Communication, abbreviated AS IPC, refers to some technique or method of transmitting data or signals between at least two processes or threads. IPC is very important to the design process of microkernels and nano kernels. Microkernels reduce the number of functions provided by the kernel, which are then acquired by communicating with the server through IPC, which is a significant increase in the number of IPC’s compared to regular macro kernels.

System call In computers, system call (System call) refers to a program running in user space asking the operating system kernel for a service that requires higher permissions to run. System calls provide an interface between user programs and the operating system. Most system interactive operations require execution in kernel mode. For example, device IO operations or interprocess communication. The process space of an operating system can be divided into user space (user-mode) and kernel space (kernel-mode), which require different execution permissions, where system calls run in kernel space. System calls are very similar to normal library function calls, except that system calls are provided by the operating system kernel and run in kernel mode, whereas normal library function calls are provided by the library or the user themselves and run in user mode. (Kernel mode and user mode switch through interrupt, the follow-up also need to learn.)

Kernel Kernel/core: the Kernel, in computer science is used for sending data management software I/O (input and output) computer program requirements, these requirements are translated into data processing instructions and to the central processing unit (CPU) and other electronic components in the computer for processing, is the most basic part of modern operating system. It is a piece of software that provides many applications with secure access to computer hardware, which is limited, and the kernel determines when and for how long a program operates on a piece of hardware. Operating directly on the hardware is very complex, so the kernel usually provides a hardware abstraction method to accomplish these operations. With this, application processes can indirectly control required hardware resources (especially processors and IO devices) through interprocess communication mechanisms (IPC) and system calls. (The kernel presumably provides a bridge between application processes and computer hardware.)

Computer software and hardware architecture and relationship diagram, you can see the kernel is the application software and computer hardware interaction work.

Kernel can be divided into macro kernel and micro kernel in design. The design compromise between macrokernel and microkernel is called hybrid kernel, but whether hybrid kernel can be considered as the third architecture is still controversial.

The macro kernel structure defines a high-level abstract interface on hardware and applies a set of primitives (or System calls) to implement operating System functions, such as process management, file System management, and storage management, which are performed by multiple modules running in the kernel mode. Although each module serves these operations individually, the kernel code is highly integrated and difficult to write correctly. Because all modules run in the same kernel space, a small bug can crash the entire system. However, if developed smoothly, the macro kernel structure can benefit from operational efficiency. Macrokernel architecture is an attractive design because it is more efficient to implement all complex low-level operating system control code in the same address space than in different address Spaces.

The microkernel microkernel structure consists of a very simple hardware abstraction layer and a set of relatively critical primitives or system calls. These primitives contain only a few parts necessary to create a system; Such as thread management, address space and interprocess communication. The goal of the microkernel is to separate the implementation of system services from the basic operating rules of the system. For example, the input/output locking service for a process can be provided by a service component running outside of the microcore. These very modular user-mode servers are used to perform more advanced operations in the operating system, making the design of the innermost part of the kernel easier. The failure of a service component does not cause the entire system to crash; all the kernel needs to do is restart the component without affecting the rest of the system.

Hybrid kernel, also known as Hybrid core or Hybrid kernel, refers to an operating system kernel architecture. The traditional operating system kernel can be divided into two basic architectures, namely, macro kernel and Micro kernel. The hybrid core combines these two types of core architectures. The basic design concept of the hybrid core is to design the operating system core based on the Micro core architecture, but the implementation of the macro kernel is adopted. The hybrid core is essentially a microcore, but it makes the core more efficient by having some of the code executed in user space execute in core space. This was a compromise, and the designers referenced the theory that microcore-structured systems perform poorly.

XNU XNU is an operating system kernel developed by Apple for the macOS operating system. It is part of the Darwin operating system and is released as free and open source software along with Darwin. It is also the kernel for iOS, tvOS, and watchOS operating systems. XNU is short for X is Not Unix. XNU was originally developed by NeXT for the NeXTSTEP operating system. It is a Hybrid kernel that combines the Mach 2.5 version, 4.3BSD, developed at Carnegie Mellon University, with an object-oriented programming application interface called the Driver Kit. After apple acquired NeXT, the Mach microkernel of XNU was upgraded to Mach 3.0, parts of BSD were upgraded to FreeBSD, and the Driver Kit was changed to the I/O Kit, a set of application interfaces written in C++. XNU is a hybrid kernel, micro to macro kernel and the kernel properties of both eclectic, in order to have the advantages of two kinds of kernel – at the same time, such as the increase in micro-kernel operating system modular part and that the operating system more accept memory protection mechanism of message passing, and macro kernel performance of high performance under high load. As of 2007, XNU supports single-core and ARM, IA-32, and x86-64 processors with symmetric multiprocessing. PowerPC is no longer supported after version 10 (Mac OS X 10.6).

Mach in XNU: The XNU kernel is based on a deeply customized Mach 3.0 kernel. This allows it to run the core of the operating system as a separate process, giving it great flexibility (multiple operating systems can run in parallel on top of the Mach core). However, this design often degrades performance because of the extra time consumed by kernel-user context switches and the reduced efficiency of the messaging between the kernel and server processes. In Mac OS X, the BSD part was built into the core along with Mach for efficiency. The deeply customized “hybrid” Mach 3.0 kernel combined with fusion in traditional BSD is a “hybrid” kernel with both advantages and disadvantages.

Darwin is an open Source operating system released by Apple in 2000 (In July 2003, Apple released Darwin under the 2.0 version of the Apple Public Source License). Darwin is the operating system part of the macOS and iOS operating environments. Darwin is a Unix operating system whose kernel is XNU (XNU is a hybrid kernel design that gives it the flexibility of a microkernel and the performance of a macro kernel). It implements Mach with a microcore-based core architecture, while the operating system’s services and user-space tools are BSD based. Like other Unix-like operating systems, Darwin has the benefits of symmetrical multiprocessors, high-performance networking facilities, and support for multiple integrated file systems. The benefit of integrating Mach into the XNU kernel is portability, or the ability to use software on different forms of systems. For example, an operating system core that integrates the Mach microcore is able to provide multiple binary formats for different CPU architectures into a single file (such as x86 and PowerPC) because it uses the Mach-O binary format. Mach-O (Mach-O)

Running the uname -r command in the MAC command terminal will display the Darwin version number, running the uname -v command will display the string of the XNU build version, This includes the Darwin version number (a-m-n-p-r-s-v, uname). The system_profiler SPSoftwareDataType command displays the software information of the MAC computer. The following output:

➜  ~ uname -r
21.1. 0
➜  ~ uname -v
Darwin Kernel Version 21.1. 0: Wed Oct 13 17:33:23 PDT 2021; root:xnu8019.41. 5~1/ RELEASE_X86_64 ➜ ~Copy the code
➜  ~ system_profiler SPSoftwareDataType
Software:

    System Software Overview:

      System Version: macOS 12.01. (21A559)
      Kernel Version: Darwin 21.1. 0
      Boot Volume: Macintosh HD
      Boot Mode: Normal
      Computer Name: HM的MacBook Pro
      User Name: HM C (hmc)
      Secure Virtual Memory: Enabled
      System Integrity Protection: Disabled
      Time since boot: 2:25

➜  ~ 
Copy the code

This should give us a summary of where Mach is: Darwin is the operating system part of the macOS and iOS operating environments. XNU is a hybrid kernel design that gives it the flexibility and performance of a microkernel. The microkernel part of XNU is a deeply customized Mach 3.0 kernel. So we can understand that Mach exception is the lowest level of kernel exception.

Learn Mach from the Kernel Programming Guide

Let’s revisit the Overview of Mach in the Kernel Programming Guide documentation to further understand the Mach microkernel.

Mach Overview

Fundamental Services and Primitives for the OS X kernel are based on Mach 3.0. Apple has modified and extended Mach to better meet OS X’s functional and performance goals. Mach 3.0 was originally conceived as a simple, extensible communication microkernel. It can run as a standalone kernel, while other traditional operating system services (such as I/O, file systems, and network stacks) run as user-mode services.

In OS X, however, Mach is linked to other kernel components in a single kernel address space, mainly for performance reasons. Making direct calls between linked components is much faster than sending messages or performing remote Procedure calls (RPC) between separate tasks, and this modular structure makes the system more powerful and scalable than a monolithic kernel allows. Without the performance penalty of pure microkernels (performance penalty due to communication).

Thus, In OSX, Mach is not primarily a communication hub between the client and the server. Instead, its value lies in its abstraction, extensibility and flexibility. In particular, Mach provides:

  • An object-based API that uses communication channels (such as ports) as object references
  • Highly parallel execution, including preemptive thread scheduling and SMP support
  • Flexible scheduling framework to support real-time use
  • A complete set of IPC primitives, including messaging, RPC, synchronization, and notification
  • Support for large virtual address Spaces, shared memory areas, and memory objects supported by persistent storage
  • Proven scalability and portability, such as across instruction set architectures and in distributed environments
  • Security and resource management are the basic principles of design. All resources are virtualized

Mach Kernel Abstractions

Mach provided a small number of Abstractions that were designed to be both simple and powerful. The following are the main Kernel Abstractions:

  • The Tasks. Units of ownership of resources; Each task consists of a virtual address space, a port permissions namespace, and one or more threads. (Similar to process)
  • The Threads. Unit of CPU execution in a task.
  • The Address space. Mach implemented the concept of sparse virtual address space and shared memory in conjunction with the memory manager.
  • The Memory objects. The internal unit of memory management. Memory objects include named entries and regions; They are representations of potentially persistent data that might be mapped to the address space.
  • Ports. A secure simplex communication channel that can only be accessed through send and receive functions called port permissions.
  • The IPC. Message queues, remote procedure calls, notifications, semaphore, and lock sets.
  • The Time. Clocks, timers, and waiting.

At the trap level, most Mach abstract interfaces consist of messages sent to and from the kernel ports that represent these objects. Trap-level interfaces (such as mach_MSG_overwrite_trap) and the message format itself are abstracted by the Mach interface generator (MIG) in normal use. MIG is used to compile process interfaces for message-based apis based on their descriptions.

Tasks and Threads

OS X processes and POSIX threads (PThreads) are implemented on top of Mach tasks and threads, respectively. Threads are a point of control flow within a task, and there is a task that provides resources for the threads it contains. This split is done to provide parallelism and resource sharing.

A thread

  • Is a point of control flow in a Task.
  • All elements containing the task can be accessed.
  • Parallel execution with other threads (possibly), even within the same task.
  • Has minimal state information to achieve low overhead.

A task

  • Is a collection of system resources. These resources (other than address Spaces) are referenced by ports. If port permissions are assigned this way, these resources can be shared with other tasks.
  • Provide a large, possibly sparse address space referenced by virtual addresses. A portion of this space can be shared through inheritance or external memory management.
  • Contains a certain number of threads.

Note that the task itself has no life cycle, only threads execute instructions. When you say “task Y does X”, what you really mean is” the thread contained in task Y does X”.

A task is a rather expensive entity that is a collection of resources. All threads in the task share everything. If there is no explicit action (although the action is often simple), nothing will be shared between the two tasks, and some resources (such as port receive permissions) cannot be shared between the two tasks at all.

A thread is a fairly lightweight entity. It’s fairly cheap to create, and low overhead to operate. This is true because the thread has almost no state information (mainly its register state). It has tasks that bear the burden of resource management. On a multiprocessor computer, multiple threads in a task can execute in parallel. Even if parallelism is not the goal, multiple threads have the advantage that each thread can use a synchronous programming style rather than try asynchronous programming with a single thread trying to provide multiple services.

Threads are basic computational entities. A thread belongs to only one task that defines its virtual address space. To affect the structure of the address space or reference any resources outside the address space, threads must execute special trap instructions that cause the kernel to perform operations on behalf of the thread or send messages to an agent on behalf of the thread. Typically, these traps operate on resources associated with tasks that contain threads. The kernel can be asked to manipulate these entities: create them, delete them, and affect their state.

Mach provides a flexible framework for thread scheduling policies. Earlier versions of OS X supported time-sharing and fixed-priority policies. Increase and decrease the priority of time-sharing threads to balance their resource consumption with other time-sharing threads.

Fixed priority threads execute for a period of time and are then placed at the end of a queue of threads with the same priority. Setting the quantum level of a fixed-priority thread to infinity will allow the thread to run until it blocks, or until it is preempted by a higher-priority thread. High-priority real-time threads are usually fixed priority.

OS X also provides time-constrained scheduling for real-time performance. This schedule allows you to specify that a thread must acquire a certain amount of time within a certain period of time.

Mach Scheduling is further described in Mach Scheduling and Thread Interfaces.

Ports, Port Rights, Port Sets, and Port Namespaces

Except for the task’s virtual address space, all other Mach resources are accessed through an indirection level called a port. A port is the end point of a one-way communication channel between a client requesting a service and a server providing it. If you want to provide a reply to such a service request, you must use a second port. This is the equivalent of a (one-way) pipe in UNIX terms.

In most cases, the resources accessed by a port (that is, the resources named by it) are called objects. Most objects named by port have a receiver and (possibly) multiple transmitters. That is, for a typical object such as a message queue, there is only one receive port and at least one send port.

The services provided by an object are determined by the manager that receives the requests sent to the object. Thus, the kernel is a sink for the ports associated with kernel-provided objects, and the sink for the ports associated with task-provided objects is the task that provides those objects.

For a port that names the object provided by a task, you can change the request recipient for that port to a different task, for example by passing the port to the task in a message. A single task may have multiple ports that reference the resources it supports. Thus, any given entity can have multiple ports representing it, each representing a different set of allowed operations. For example, many objects have name ports and control ports (sometimes called privileged ports). Access control ports allow manipulation of objects; Access to the name port simply names the object so that you can get information about it or perform other non-privileged operations on it.

A task has access to a port in a specific way (send, receive, send once); These are called port permissions. Ports can only be accessed by right-clicking. Ports are typically used to grant clients access to objects within Mach. An IPC port that has the right to send to an object indicates the right to manipulate the object in a specified manner. Therefore, port permission ownership is the basic security mechanism of Mach. Having permission on an object means having the ability to access or manipulate it.

Port permissions can be copied and moved between tasks via IPC. Doing so actually passes functionality to some object or server.

One type of object referenced by a port is a port set. As the name implies, a port set is a set of port permissions that can be treated as a single unit when receiving messages or events from any member of the group. Port sets allow a thread to wait for multiple sources of messages and events, such as in a working loop.

Traditionally, in Mach, the communication channel represented by a port was always a message queue. However, OS X supports other types of communication channels, and these new types of IPC objects are also represented by ports and port permissions. For more details on messages and other IPC types, see the Interprocess Communication (IPC) section.

Ports and port permissions There is no system-wide name that allows direct manipulation of any port or permission. A task can manipulate a port only if it has port permissions in its port namespace. Port permissions are specified by the port name, which is an integer index of the 32-bit port namespace. Each task has a port namespace associated with it.

When another task explicitly inserts port permissions into its namespace, by creating objects that return object permissions when the task receives permissions in a message, And when some special ports (mach_thread_self, mach_task_self, and mach_reply_port) are called through Mach, the task obtains port permissions.

Memory Management

Like most modern operating systems, Mach provides addressing for large, sparse virtual address Spaces. Runtime access is made through virtual addresses that may not correspond to locations in physical memory at the initial time of the attempted access. Mach is responsible for getting the virtual address of the request and assigning it a location in physical memory. It does this through requirements paging.

When a memory object is mapped to the scope of the virtual address space, the scope is populated with data. All data in the address space is ultimately supplied through in-memory objects. When a page is created in physical memory, Mach asks the owner of the memory object (the pager) for the contents of the page and returns the data that might be modified to the pager before the page is reclaimed. OS X includes two built-in pagers, the default pager and the VNode pager.

The default pager handles non-persistent memory, called anonymous memory. Anonymous memory is initialized to zero and exists only in task lifetime memory. The VNode pager maps files to memory objects. Mach exports the interface to in-memory objects to allow user-mode tasks to provide its contents. This interface is called the external Memory Management interface or EMMI.

The memory management subsystem exports virtual memory handles called named entries or named memory bars. Like most kernel resources, these resources are represented by ports. Having a named memory input handle allows the owner to map the underlying virtual memory object or to pass permissions to map the underlying object to other objects. Mapping named entries in two different tasks results in a shared memory window between the two tasks, providing a flexible way to set up shared memory.

Starting with OS X V10.1, the EMMI system has been enhanced to support “no-port” EMMI. In traditional EMMI, you create two Mach ports for each memory region, as well as two ports for each cached VNode. The gratuitous interface EMMI replaced this with a direct memory reference (basically a pointer) in its initial implementation. In future releases, ports will be used to communicate with pagers outside the kernel, as well as using direct references to communicate with pagers residing in kernel space. The net result of these changes was that earlier versions of the unprovoked EMMI did not support pagers running outside of kernel space. This support is expected to be restored in a future release.

The address range of virtual memory space can also be filled by direct allocation (using VM_ALLOCATE). The underlying virtual memory object is anonymous and is supported by the default pager. The shared scope of the address space can also be set by inheritance. When new tasks are created, they are cloned from their parent. This clone is also related to the underlying memory address space. The mapped portion of an object can be inherited as a copy, as a shared inheritance, or not at all, depending on the attributes associated with the mapping. Mach practices a form of delayed replication called copy-on-write to optimize the performance of inherited copies when tasks are created.

Instead of copying ranges directly, copy-on-write optimization is implemented through protected shares. The two tasks share the memory to be copied, but have read-only access. When any task tries to modify a part of the scope, that part is copied at this time. This delayed evaluation of in-memory replicas is an important optimization that allows for simplification in several areas, particularly the messaging API.

Mach provides another form of sharing by exporting named regions. Named regions are a form of named entries, but are supported not by virtual memory objects, but by virtual map fragments. This fragment may contain mappings to a large number of virtual memory objects. It can be mapped to other virtual mappings, providing a way to inherit not only a set of virtual memory objects, but also their existing mapping relationships. This feature provides significant optimization in task Settings, for example, when sharing complex areas of the address space used for shared libraries.

Interprocess Communication (IPC)

Communication between tasks is an important element of Mach Philosophy. Mach supports a client/server system architecture in which tasks (clients) make requests to other tasks (servers) to access services via messages sent over a communication channel.

The endpoints of these communication channels in Mach are called ports, and port rights represent permissions to use that channel. The forms of IPC provided by Mach include:

  • message queues
  • semaphores
  • notifications
  • lock sets
  • remote procedure calls (RPCs)

The type of IPC object represented by a port determines what operations are allowed on that port, and how (and if) data transfers occur.

Important: The IPC device in OS X is in transition state. In earlier versions of the system, not all of these IPC types could be implemented.

There are two fundamentally different Mach apis for raw operations on ports – the mach_ipc family and the mach_msg family. Within reason, both families can be used with any IPC object, but the mach_IPC call is preferred in new code. The MACH_IPC calls maintain state information, where appropriate, to support the concept of transactions. The old code supported mach_msg calls, but they are deprecated and stateless.

IPC Transactions and Event Dispatching

When the thread calls mach_IPC_dispatch, it repeatedly processes events passed in on the registered port set. These events can be a block of arguments from an RPC object (as a result of a client call), a lock object being acquired (as a result of some other thread releasing the lock), a notification or semaphore being published, or a message from a traditional message queue.

These events are handled through the mach_MSG_dispatch annotation. Some events mean that a transaction exists during the lifetime of the annotation. For locks, the state is the ownership of the lock. When the annotation returns, the lock is released. For remote procedure calls, the status is the client’s identity, parameter block, and reply port. When the annotation returns, a reply is sent. When the annotation returns, the transaction (if any) completes and the thread waits for the next event. The MACH_IPC_DISPATCH facility is designed to support work cycles.

Message Queues

Initially, the only style of interprocess communication in Mach was message queues. Only one task can reserve receive privileges for the port representing the message queue. Allows this task to receive (read) messages from the port queue. Multiple tasks can have permissions on ports that allow them to send (write) messages to queues.

One task communicates with another by building a data structure that contains a set of data elements and then performing a message send operation on a port that it has send permission to. Later, tasks with receive permissions on that port will perform the message receive operation.

The message may contain some or all of the following:

  • Pure data
  • A copy of the memory range
  • The port authority
  • Kernel implicit properties, such as the sender’s security token

Message transfer is an asynchronous operation. Messages are logically copied to the receiving task, possibly with write-time copy-optimizations. Multiple threads in the receive task can try to receive messages from a given port, but only one thread can receive any given message.

Semaphores

Semaphore IPC objects support wait, commit, and commit all operations. These are counting semaphores, because posts are saved (counted) if there are no threads currently waiting in the wait queue for that semaphore. The Post All operation wakes up all currently waiting threads.

Notifications

Like semaphores, notification objects support publish and wait operations, but add a status field. A state is a fixed-size, fixed-format field defined when a notification object is created. Each post updates the status field, and each post overwrites a status.

Locks

A lock is an object that provides mutually exclusive access to a critical part. The primary interface for locking is transaction-oriented (see IPC transaction and event scheduling). During a transaction, the thread holds the lock. When it returns from the transaction, the lock is released.

Remote Procedure Call (RPC) Objects

As the name implies, RPC objects are designed to facilitate and optimize remote procedure calls. The primary interface of RPC objects is transaction-oriented (see IPC transactions and event Scheduling)

When an RPC object is created, a set of parameter block formats is defined. When the client does RPC (sending on objects), it causes a message to be created and queued on the object in one of the predefined formats and then eventually delivered to the server (receiver). When the server returns from the transaction, the reply is returned to the sender. Mach tries to optimize transactions by using the client’s resources to execute the server, which is called thread migration.

Time Management

The traditional time abstraction in Mach is the clock, which provides a set of asynchronous alarm services based on Mach_TIMespec_t. There are one or more clock objects, each of which defines a monotonically increasing time value expressed in nanoseconds. Real-time clocks are built in and are the most important, but there may be other clocks in the system for other time concepts. Clock You can obtain the current time, sleep within a specified period, and set an alarm (notification sent at a specified time).

The MACH_TIMespec_T API is deprecated in OS X. The newer and preferred apis are based on timer objects, which in turn use AbsoluteTime as their base data type. AbsoluteTime is a computer-dependent type, usually platform-based. Routines are provided to convert AbsoluteTime values to and from other data types, such as nanoseconds. Timer objects support asynchrony, drift free notification, cancellation, and premature alarm. They are more efficient than clocks and allow for higher resolution.

Ports are used in Mach communication. If you remember, you have seen a lot of ports in Runloop communication. Source1 in CFRunLoopSource is implemented internally based on port. (Source1: Contains a mach_port and a callback (function pointer) that is used to send messages to each other through the kernel and other threads. This Source actively wakes up the RunLoop thread.)

Communication mechanism in Mach: Port

In this section we’ll look at ports. The first things that come to mind are port-based source1 in Runloop and port-based thread to thread communication in iOS. Instead of looking at mach_port_t, let’s take a look at NSMachPort, which is used in Cocoa. Let’s review the use of NSMachPort for thread to thread communication in iOS with some sample code.

NSMachPort Example

NSMachPort is a subclass of NSPort that encapsulates MachPort and is the basic communication port in macOS, The @property (readonly) Uint32_t machPort attribute of the NSMachPort class is used to fetch the Mach port of the NSMachPort object. NSMachPort allows only local (on the same machine) communication. The companion class NSSocketPort allows local and remote distributed object communication, but can be more expensive than NSMachPort for local cases. To use NSMachPort effectively, you should be familiar with Mach Ports, port access rights, and Mach Messages.

NSMachPort works by adding an NSMachPort object to a thread’s RunLoop and assigning the NSMachPort object to a proxy. When the NSMachPort object is called in another thread to send a message, the associated proxy method is executed in the thread associated with the NSMachPort. Example code is as follows:

#import "ViewController.h"
#import "MyWorkClass.h"

@interface ViewController (a) <NSPortDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    // 1. Create a port object in the main thread and pass it directly to the child thread. The child thread sends messages to the main thread through this port object
    NSPort *myPort = [NSMachPort port];
    
    // 2. Set the callback proxy for the port object
    myPort.delegate = self;
    
    // 3. Add the port object to the main thread runloop to receive messages
    [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
    
    NSLog(@"🍀🍀🍀 add port object to main thread runloop: %@", myPort);
    
    // 4. Create a child thread and pass the port object from the main runloop to the child thread
    MyWorkClass *work = [[MyWorkClass alloc] init];
    [NSThread detachNewThreadSelector:@selector(launchThreadWithPort:) toTarget:work withObject:myPort];
}

// This is the delegate method that subclasses should send to their delegates, unless the subclass has something more specific that it wants to try to send first
- (void)handlePortMessage:(NSMessagePort *)message {
    NSLog(@"🍀🍀🍀 received a message from the child thread via port: %@ Currently at: %@", message, [NSThread currentThread]);
    
    // 1. Message ID
    NSUInteger msgID = [[message valueForKeyPath:@"msgid"] integerValue];
    
    // 2. Port of the current main thread
    NSPort *localPort = [message valueForKeyPath:@"localPort"];
    
    NSLog(@"🍀🍀🍀 received a message from the child thread via port, localPort: %@", localPort);
    
    // 3. The port that received the message (from another thread)
    NSPort *remotePort = [message valueForKeyPath:@"remotePort"];
    
    NSLog(@"🍀🍀🍀 receives message from child thread via port, remotePort: %@", remotePort);
    
    if (msgID == 100) {
        // Send a message to the child thread's port
// [remotePort sendBeforeDate:[NSDate date] msgid:200 components:nil from:localPort reserved:0];
        
    } else if (msgID == 200) {
        NSLog(@"Operation 2... \n"); }} - (void)dealloc {
    NSLog(@"🍀 🍀 🍀 % @"The @"ViewController");
}

@end

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface MyWorkClass : NSObject

- (void)launchThreadWithPort:(NSPort *)port;

@end

NS_ASSUME_NONNULL_END

#import "MyWorkClass.h"

@interface MyWorkClass (a) <NSMachPortDelegate>

@property (nonatomic, strong) NSPort *remotePort;

@property (nonatomic, strong) NSPort *myPort;

@end

@implementation MyWorkClass

- (void)launchThreadWithPort:(NSPort *)port {
    @autoreleasepool {
        // 1. Save the port object sent by the main thread, and then use this port object to send messages to the main thread
        self.remotePort = port;
        
        // 2. Set the name of the child thread
        [[NSThread currentThread] setName:@"MYWORKERCLASSTHREAD"];
        
        // 3. Enable runloop
        [[NSRunLoop currentRunLoop] run];
        
        // create your own port
        self.myPort = [NSPort port];
        
        NSLog(@"🍀🍀🍀 Child thread: %@ Port object added to child thread Runloop: %@", [NSThread currentThread], self.myPort);
        
        // 5. Set the proxy for your port
        self.myPort.delegate = self;
        
        // 6. Add your port to the runloop of the current thread
        // Function 1: Prevent the current thread's runloop from exiting (exit automatically if the child thread's runloop does not have any timer/source/observer)
        Function 2: Can be used to receive messages sent by the main thread through this port object
        [[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode];
        
        // 7. Send message from child thread to main thread:
        // First we use the port object in the main thread runloop to send the message, and we pass to the main thread the port object created above and put into the child thread runloop.
        // Then the main thread can send messages to the child thread through this port object[self sendPortMessage]; }} - (void)sendPortMessage {
    NSMutableArray *array = [[NSMutableArray alloc] initWithArray:@[@"1"The @"2"]].// Send message to main thread, operation 1
    [self.remotePort sendBeforeDate:[NSDate date] msgid:100 components:array from:self.myPort reserved:0];
}

- (void)handlePortMessage:(NSPortMessage *)message {
    NSLog(@"🍀🍀🍀 Receive messages from the main thread!");
    
    // Handle messages sent by the main thread, such as stopping the child thread's runloop
}

@end
Copy the code

In the example code above, we demonstrated the use of NSMachPort, which simplifies the use of port by encapsulating mach_port_t in an object-oriented manner. Instead of a direct system call, the user process requests access to a port from the kernel, and then sends messages to that port using the IPC mechanism. While sending a message is also a system call, the Mach kernel works differently — the work of the handler can be handed off to other processes.

The flow of Mach exceptions

In the book “In-depth Analysis of Mac OS X & iOS operating System”, the system describes the process of handling exceptions, as well as the following diagram:

And detailed exception mechanisms, hardware/software exceptions, etc., which are not excerpted here.

What are the types of Mach exceptions

We can download the latest xNU kernel source code from the xNU release list. The current latest version is XNU-7195.141.2. The Mach abnormal types were defined in xnu 7195.141.2 / osfmk/Mach/exception_types. H.

Here are some of the more common Types of Mach exceptions in EXCEPtion_types.h:

  • EXC_BAD_ACCESS
/* * Machine-independent exception definitions. */

/* Could not access memory. */
/* Code contains kern_return_t describing error. */
/* Subcode contains bad memory address. */
#define EXC_BAD_ACCESS          1       
Copy the code

This is usually caused by accessing memory that should not be accessed. EXC_BAD_ACCESS is usually caused by accessing memory that should not be accessed. Depending on the situation, it is converted to SIGSEGV when Mach code is equal to KERN_INVALID_ADDRESS and SIGBUG in other cases.

SIGSEGV: The corresponding Mach code is KERN_INVALID_ADDRESS (invalid address access), commonly known as the wild pointer. This is usually an attempt to access memory that has not been allocated to itself, or to write data to a memory address that has no write permission. *((int*)(0x1234)) = 122; You can make an EXC_BAD_ACCESS(SIGSEGV) exception.

SIGBUG: Indicates invalid addresses, including incorrect alignment of memory addresses. Such as accessing a four-word integer whose address is not a multiple of 4. It differs from SIGSEGV in that the latter is triggered by illegal access to a valid storage address (such as access to a non-own storage space or read-only storage space). char *s = “hello world”; *s = ‘H’; You can make an EXC_BAD_ACCESS(SIGBUS) exception.

Stack overflow causes SIGSEGV signals, but when caught by the Mach layer the corresponding Mach code is KERN_PROTECTION_FAILURE(address protection error) because stack overflow accesses the protected page at the top of the stack. This situation needs to be considered at the Mach level when catching exceptions to convert to the corresponding signal.

  • EXC_BAD_INSTRUCTION
/* Instruction failed */
/* Illegal or undefined instruction or operand */
#define EXC_BAD_INSTRUCTION     2       
Copy the code

Such exceptions are usually caused by threads executing illegal instructions. EXC_BAD_INSTRUCTION is an instruction related exception, usually an attempt to execute an illegal instruction, and the corresponding signal is SIGILL for Illeague Instruction.

  • EXC_ARITHMETIC
/* Arithmetic exception */
/* Exact nature of exception is in code field */
#define EXC_ARITHMETIC          3       
Copy the code

Arithmetic exceptions thrown by division by zero errors. EXC_ARITHMETIC is an arithmetic related exception that is emitted when a fatal arithmetic operation error occurs. This includes not only floating-point errors, but also overflow and all other arithmetic errors such as divisor zero. The corresponding signal is SIGFPE.

  • EXC_EMULATION
/* Emulation instruction */
/* Emulation support instruction encountered */
/* Details in code and subcode fields */
#define EXC_EMULATION           4       
Copy the code

EXC_EMULATION is a hardware-related exception, and the corresponding signal is SIGEMT, which is almost invisible.

  • EXC_SOFTWARE
/* Software generated exception */
/* Exact exception is in code field. */

/* Codes 0 - 0xFFFF reserved to hardware */
/* Codes 0x10000 - 0x1FFFF reserved for OS emulation (Unix) */

#define EXC_SOFTWARE            5       
Copy the code

EXC_SOFTWARE is a software-specific exception that is converted to four different types of signals: SIGSYS, SIGPIPE, SIGABRT, and SIGKILL.

SIGSYS: The corresponding Mach code is EXC_UNIX_BAD_SYSCALL, usually an illegal system call.

SIGPIPE: Pipe break. This signal is usually generated in interprocess communication. For example, two processes that communicate using FIFO(pipe) will receive a SIGPIPE signal if they write to the pipe before the read pipe is opened or terminates unexpectedly. In addition, for the two processes that use the Socket to communicate, the writing process terminates while writing the Socket. The corresponding Mach code is EXC_UNIX_BAD_PIPE.

SIGABRT: is a signal generated by calling ABORT. Usually because the application layer occurs an NSException and is not caught, the program sends SIGABRT signal to itself and crashes. Abort calls (void)pthread_kill(pthread_self(), SIGABRT); . The corresponding Mach code is EXC_UNIX_ABORT.

SIGKILL: Used to terminate the program immediately. This signal cannot be blocked, processed, or ignored. The corresponding Mach code is EXC_SOFT_SIGNAL.

  • EXC_BREAKPOINT
/* Trace, breakpoint, etc. */
/* Details in code field. */
#define EXC_BREAKPOINT          6       
Copy the code

EXC_BREAKPOINT is generated by breakpoint instruction or other trap instruction and used by the debugger. The corresponding signal is SIGTRAP.

Mach exception catching

We have seen that Mach uses port for thread to thread communication, and it is the port-based communication mechanism that catches Mach exceptions. We can use the API provided by Mach to register a custom port (thread /task /host) to replace the port where the kernel receives Mach exception messages, and then use the mach_msg function to receive exception messages. Finally, mach_msg function is used to forward the abnormal message without affecting the original process.

There are three important functions involved in replacing the port on which the kernel receives Mach exception messages. We can set ports in host, Task, and Thread respectively.

  • Sets exception handlers for one or more exception types in the Host layer. If there are no Task or Threadspecific exception handlers, or if they return an error, these handlers are called for all threads on the host: host_set_EXCEPtion_ports

  • Set the exception port for the specified task: task_set_EXCEPtion_ports

  • Set exception ports for specified threads: thread_set_EXCEPtion_ports

Here are a few more caveats: Thread_set_exception_ports can only be used for specific threads. For example, if we set the main thread in thread_set_Exception_ports, Mach exceptions that occur in child threads cannot be called back through our port. Task_set_exception_ports receives all Mach exceptions from the current process, regardless of which thread caused the Mach exception.

Here is sample code to catch Mach exceptions:

#import "AppDelegate.h"

#import <pthread.h>
#import <mach/mach_init.h>
#import <mach/mach_port.h>
#import <mach/task.h>
#import <mach/message.h>
#import <mach/thread_act.h>
#import <mach/host_priv.h>

@interface AppDelegate (a)

@end

@implementation AppDelegate

// register the port that caught the exception
// Customize the port number
mach_port_name_t myExceptionPort = 10086;

- (void)catchMACHExceptions {
    // Initialize a port with a custom port number
    mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &myExceptionPort);
    // Insert send permission to the port
    mach_port_insert_right(mach_task_self(), myExceptionPort, myExceptionPort, MACH_MSG_TYPE_MAKE_SEND);
    // Set the type of Mach exception
    exception_mask_t excMask = EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_SOFTWARE;
    
    // Set the thread Port on which the kernel receives Mach exception messages
    thread_set_exception_ports(mach_thread_self(), excMask, myExceptionPort, EXCEPTION_DEFAULT, MACHINE_THREAD_STATE);
// task_set_exception_ports(mach_task_self(), excMask, myExceptionPort, EXCEPTION_DEFAULT, MACHINE_THREAD_STATE);
// host_set_exception_ports(<#host_priv_t host_priv#>, <#exception_mask_t exception_mask#>, <#mach_port_t new_port#>, <#exception_behavior_t behavior#>, <#thread_state_flavor_t new_flavor#>)
    
    // Create a new thread to handle the exception message
    pthread_t thread;
    pthread_create(&thread, NULL, exc_handler, NULL);
}

/// Receive the abnormal message
static void *exc_handler(void *ignored) {
    / / the result
    mach_msg_return_t rc;
    // See ux_Handler () [BSD/uxkern/ux_exception.c] for the exception message that the kernel will send us
    typedef struct {
        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;
        integer_t code[2];
        int flavor;
        mach_msg_type_number_t old_stateCnt;
        natural_t old_state[144];
    } exc_msg_t;
    
    // message processing loop, where the dead loop is not a problem because exc_handler runs in a separate child thread and the mach_msg function also blocks.
    for (;;) {
        exc_msg_t exc;
        
        // It blocks until an exception message is received or the thread is interrupted
        rc = mach_msg(&exc.Head, MACH_RCV_MSG | MACH_RCV_LARGE, 0.sizeof(exc_msg_t), myExceptionPort, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
        if(rc ! = MACH_MSG_SUCCESS) {//
            break;
        };
        
        // Prints an exception message
        NSLog(@"🍀🍀🍀 CatchMACHExceptions % D. Exception: %d Flavor: % D. Code % D /% D. State count is % D", exc.Head.msgh_id, exc.exception, exc.flavor, exc.code[0], exc.code[1], exc.old_stateCnt);
        
        // Define the type of message to forward
        struct rep_msg {
            mach_msg_header_t Head;
            NDR_record_t NDR;
            kern_return_t RetCode;
        } rep_msg;
        rep_msg.Head = exc.Head;
        rep_msg.NDR = exc.NDR;
        rep_msg.RetCode = KERN_FAILURE;
        kern_return_t result;
        if (rc == MACH_MSG_SUCCESS) {
            // Forward the exception message
            result = mach_msg(&rep_msg.Head, MACH_SEND_MSG, sizeof(rep_msg), 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); }}return NULL;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    // Custom catch Mach exceptions
    [self catchMACHExceptions];
    
    The EXC_BAD_ACCESS exception occurs under ARC. Note that the EXC_handler callback can only be received if xcode's Debug executable option is disabled
    __unsafe_unretained NSObject *objc = [[NSObject alloc] init];
    NSLog(@"✳ ️ ✳ ️ ✳ ️ objc: % @", objc);
    
    return YES;
}

@end
Copy the code

Running the code, we can see that the console prints something like this:

🍀 🍀 🍀 CatchMACHExceptions2401. Exception : 1 Flavor: 0. Code 13/0. State count is 8
Copy the code

It was also found that Mach exceptions need to be turned off under M1 MAC to catch Mach exceptions, and that the Debug Executable option can be turned on or off under Intel MAC to catch Mach exceptions.

Unix signals

In the example above, we caught the Mach exception with the first mach_msg, and then we forwarded the Mach exception message with the second mach_msg. Where did the forwarded Mach exception message go? This is converted to the corresponding UNIX signal (the Mach exception type and UNIX signal mappings were described above in Mach exception types, which we’ll look at in more detail below). The Mach exception is catch_mach_Exception_raise at the BSD layer after being caught and thrown at the Mach layer, and converted to the corresponding UNIX signal via ux_exception. The POSIX API in iOS is implemented through the BSD layer on top of Mach. We can look at the ux_Exception function implementation, which has the mapping between Mach exception types and UNIX signals.

Mach exceptions are converted to Unix signal

The explicit correspondence in the ux_exception function:

/* * Translate Mach exceptions to UNIX signals. * * ux_exception translates a mach exception, code and subcode to a signal. Calls machine_exception (machine dependent) to attempt translation first. */
static int
ux_exception(int                        exception,
    mach_exception_code_t      code,
    mach_exception_subcode_t   subcode)
{
    int machine_signal = 0;

    /* Try machine-dependent translation first. */
    if ((machine_signal = machine_exception(exception, code, subcode)) ! =0) {
        return machine_signal;
    }

    switch (exception) {
    case EXC_BAD_ACCESS:
        if (code == KERN_INVALID_ADDRESS) {
            return SIGSEGV;
        } else {
            return SIGBUS;
        }

    case EXC_BAD_INSTRUCTION:
        return SIGILL;

    case EXC_ARITHMETIC:
        return SIGFPE;

    case EXC_EMULATION:
        return SIGEMT;

    case EXC_SOFTWARE:
        switch (code) {
        case EXC_UNIX_BAD_SYSCALL:
            return SIGSYS;
        case EXC_UNIX_BAD_PIPE:
            return SIGPIPE;
        case EXC_UNIX_ABORT:
            return SIGABRT;
        case EXC_SOFT_SIGNAL:
            return SIGKILL;
        }
        break;

    case EXC_BREAKPOINT:
        return SIGTRAP;
    }

    return 0;
}
Copy the code

I’ll show it in a table to make it clear:

Mach Exception Type Unix Signal
EXC_BAD_ACCESS SIGSEGV
SIGBUS
EXC_BAD_INSTRUCTION SIGILL
EXC_ARITHMETIC SIGFPE
EXC_EMULATION SIGEMT
EXC_SOFTWARE SIGSYS(EXC_UNIX_BAD_SYSCALL)
SIGPIPE(EXC_UNIX_BAD_PIPE)
SIGABRT(EXC_UNIX_ABORT)
SIGKILL(EXC_SOFT_SIGNAL)
EXC_BREAKPOINT SIGTRAP

What are Unix signals

All Unix signals defined in xnu – 7195.141.2 / BSD/machine/signal. H.

#define SIGHUP  1       /* hangup */
#define SIGINT  2       /* interrupt */
#define SIGQUIT 3       /* quit */
#define SIGILL  4       /* illegal instruction (not reset when caught) */
#define SIGTRAP 5       /* trace trap (not reset when caught) */
#define SIGABRT 6       /* abort() */
#if(defined(_POSIX_C_SOURCE) && ! defined(_DARWIN_C_SOURCE))
#define SIGPOLL 7       /* pollable event ([XSR] generated, not supported) */
#else   / * (! _POSIX_C_SOURCE || _DARWIN_C_SOURCE) */
#define SIGIOT  SIGABRT /* compatibility */
#define SIGEMT  7       /* EMT instruction */
#endif  / * (! _POSIX_C_SOURCE || _DARWIN_C_SOURCE) */
#define SIGFPE  8       /* floating point exception */
#define SIGKILL 9       /* kill (cannot be caught or ignored) */
#define SIGBUS  10      /* bus error */
#define SIGSEGV 11      /* segmentation violation */
#define SIGSYS  12      /* bad argument to system call */
#define SIGPIPE 13      /* write on a pipe with no one to read it */
#define SIGALRM 14      /* alarm clock */
#define SIGTERM 15      /* software termination signal from kill */
#define SIGURG  16      /* urgent condition on IO channel */
#define SIGSTOP 17      /* sendable stop signal not from tty */
#define SIGTSTP 18      /* stop signal from tty */
#define SIGCONT 19      /* continue a stopped process */
#define SIGCHLD 20      /* to parent on child stop or exit */
#define SIGTTIN 21      /* to readers pgrp upon background tty read */
#define SIGTTOU 22      /* like TTIN for output if (tp->t_local&LTOSTOP) */
#if(! defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE))
#define SIGIO   23      /* input/output possible signal */
#endif
#define SIGXCPU 24      /* exceeded CPU time limit */
#define SIGXFSZ 25      /* exceeded file size limit */
#define SIGVTALRM 26    /* virtual time alarm */
#define SIGPROF 27      /* profiling time alarm */
#if(! defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE))
#define SIGWINCH 28     /* window size changes */
#define SIGINFO 29      /* information request */
#endif
#define SIGUSR1 30      /* user defined signal 1 */
#define SIGUSR2 31      /* user defined signal 2 */
Copy the code

Unix Signal capture

Here is an excerpt of the sample code from Handling Unhandled and Signals with some minor modifications:

#import "UncaughtExceptionHandler.h"

#import <UIKit/UIDevice.h>
#import <libkern/OSAtomic.h>
#import <execinfo.h>
#import <stdatomic.h>

NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";
NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";
NSString * const UncaughtExceptionHandlerFileKey = @"UncaughtExceptionHandlerFileKey";

atomic_int UncaughtExceptionCount = 0;
const int32_t UncaughtExceptionMaximum = 10;

// Skip four frames in the function call stack:
/* "0 dSYMDemo 0x00000001042541eb +[UncaughtExceptionHandler backtrace] + 59", "1 dSYMDemo 0x0000000104253edc mySignalHandler + 76", "2 libsystem_platform.dylib 0x000000010e774e2d _sigtramp + 29", "3??? 0x0000600002932720 0x0 + 105553159464736", */
const NSInteger UncaughtExceptionHandlerSkipAddressCount = 4;
//const NSInteger UncaughtExceptionHandlerReportAddressCount = 5;

void mySignalHandler(int signal);

@implementation UncaughtExceptionHandler

+ (void)installUncaughtExceptionHandler {
    // Remove and back up previously registered uncaught exception handlers to prevent overwriting
    previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler(a);// Objective-C exception catching (out of bounds, invalid arguments, etc.)
    NSSetUncaughtExceptionHandler(&UncaughtExceptionHandlers);
    
    // The semaphore is truncated. MySignalHandler is called when a signal is thrown
    signal(SIGABRT, mySignalHandler);
    signal(SIGILL, mySignalHandler);
    signal(SIGSEGV, mySignalHandler);
    signal(SIGFPE, mySignalHandler);
    signal(SIGBUS, mySignalHandler);
    signal(SIGPIPE, mySignalHandler);
}

+ (void)setSignalHandlerInAdvance {
    struct sigaction act;
    // When sa_flags is set to SA_SIGINFO, sa_sigAction is set 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);
}

// Get function stack information
+ (NSArray *)backtrace {
    void* callstack[128];
    
    // To get the current thread's function call stack, return the actual number of Pointers fetched
    int frames = backtrace(callstack, 128);
    // The information obtained from backtrace is converted to an array of strings
    char **strs = backtrace_symbols(callstack, frames);
    
    NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
    
    // Go past the first four frames
    if (frames > UncaughtExceptionHandlerSkipAddressCount) {
        for (inti = UncaughtExceptionHandlerSkipAddressCount; i < frames; ++i) { [backtrace addObject:[NSString stringWithUTF8String:strs[i]]]; }}NSLog(@"🏵🏵🏵 stack when exception occurs: %@", backtrace);
    
    free(strs);
    
    return backtrace;
}

- (void)saveCreash:(NSException *)exception file:(NSString *)file {
    // Stack information when an exception occurs
    NSArray *stackArray = [exception callStackSymbols];
    if(! stackArray || stackArray.count <=0) {
        stackArray = [exception.userInfo objectForKey:UncaughtExceptionHandlerAddressesKey];
    }
    
    // The cause of the exception
    NSString *reason = [exception reason];
    // Exception name
    NSString *name = [exception name];
    
    NSString * _libPath  = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:file];
    
    if(! [[NSFileManager defaultManager] fileExistsAtPath:_libPath]){ [[NSFileManager defaultManager] createDirectoryAtPath:_libPath withIntermediateDirectories:YES attributes:nil error:nil]; } NSDate* date = [NSDate dateWithTimeIntervalSinceNow:0];
    NSTimeInterval a = [date timeIntervalSince1970];
    NSString *timeString = [NSString stringWithFormat:@"%f", a];
    
    NSString * savePath = [_libPath stringByAppendingFormat:@"/error%@.log", timeString];
    NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason: %@\nException name: %@\nException stack: %@", name, reason, stackArray];
    BOOL sucess = [exceptionInfo writeToFile:savePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
    
    NSLog(@"🏵🏵🏵 Save crash log sucess:%d, %@", sucess, savePath);
}

// Exception handling method
- (void)handleException:(NSException *)exception {
    NSDictionary *userInfo = [exception userInfo];
    [self saveCreash:exception file:[userInfo objectForKey:UncaughtExceptionHandlerFileKey]];
    
    NSSetUncaughtExceptionHandler(NULL);
    signal(SIGABRT, SIG_DFL);
    signal(SIGILL, SIG_DFL);
    signal(SIGSEGV, SIG_DFL);
    signal(SIGFPE, SIG_DFL);
    signal(SIGBUS, SIG_DFL);
    signal(SIGPIPE, SIG_DFL);
    
    if ([[exception name] isEqual:UncaughtExceptionHandlerSignalExceptionName]) {
        int signalNumber = [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue];
        
        NSLog(@"🏵🏵🏵 caught signal exception: %d", signalNumber);
        
        // If signal is abnormal
        kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);
    } else {
        NSLog(@"🏵🏵🏵 Catch Objective-C exception: %@", exception);
        
        // If it is an Objective-C exception
        [exception raise];
        
        // After your own exception handling operation is complete, call the previously registered uncaught exception handler and pass the original exception
        if (previousUncaughtExceptionHandler) {
            previousUncaughtExceptionHandler(exception);
        } else {
            // If it is an Objective-C exception
            kill(getpid(), SIGKILL); }}}// Get application information
NSString* getAppInfo(void) {
    NSString *appInfo = [NSString stringWithFormat:@"App : %@ %@(%@) Device : %@ OS Version : %@ %@",
                         [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"],
                         [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
                         [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],
                         [UIDevice currentDevice].model,
                         [UIDevice currentDevice].systemName,
                         [UIDevice currentDevice].systemVersion];
    return appInfo;
}

static NSUncaughtExceptionHandler *previousUncaughtExceptionHandler = NULL;

/ / NSSetUncaughtExceptionHandler catch exceptions the invocation of the method, using NSSetUncaughtExceptionHandler, when a program anomaly on exit, can be processed first, and then do some custom action
void UncaughtExceptionHandlers (NSException *exception) {
    // The atom increases by 1
    int32_t exceptionCount = atomic_fetch_add(&UncaughtExceptionCount, 1);
    if (exceptionCount > UncaughtExceptionMaximum) { return; }
    
    // The function stack when the exception occurs
    NSArray *callStack = [UncaughtExceptionHandler backtrace];
    
    // Assemble userInfo data
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
    [userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
    [userInfo setObject:@"Objective-C Crash" forKey:UncaughtExceptionHandlerFileKey];
    
    NSException *medianException = [NSException exceptionWithName:[exception name]
                                                           reason:[exception reason]
                                                         userInfo:userInfo];
    
    // Objective-C exceptions and signal are handled in the handleException: function
    [[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:) withObject:medianException waitUntilDone:YES];
}

// Signal processing method
void mySignalHandler(int signal) {
    // The atom increases by 1
    int32_t exceptionCount = atomic_fetch_add(&UncaughtExceptionCount, 1);
    if (exceptionCount > UncaughtExceptionMaximum) { return; }
    
    // The function stack when the exception occurs
    NSArray *callStack = [UncaughtExceptionHandler backtrace];
    
    // Assemble userInfo data
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey];
    [userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
    [userInfo setObject:@"Signal Crash" forKey:UncaughtExceptionHandlerFileKey];
    
    // Construct an NSException object
    NSException *medianException = [NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName
                                                     reason:[NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.\n" @"% @", nil), signal, getAppInfo()]
                                                   userInfo:userInfo];
    
    // Objective-C exceptions and signal are handled in the handleException: function
    [[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:) withObject:medianException  waitUntilDone:YES];
}

@end
Copy the code

Backtrace & Backtrace_symbols functions:

Here’s an extension: + (NSArray *)backtrace {… The} function is used to retrieve traceback information for the current function, that is, the function call stack at the time the exception occurred. Backtrace and backtrace_symbols are used:

int backtrace(void**int) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

char** backtrace_symbols(void* const*,int) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
void backtrace_symbols_fd(void* const*,int.int) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
Copy the code

The backtrace function is used to retrieve information about the current function in the program, that is, a series of function calls, in the void** argument. Void ** is a pointer to an array. Each element of the array holds the return address of the function called at each level. The int argument specifies the number of return addresses that can be stored in void**. If the actual number of tracebacks in a function is greater than int, void** can only hold the most recent function call relationship, so make sure the void** argument is large enough to get the full traceback information.

The backtrace function returns the number of entries in void**, which is not necessarily equal to int, because if int is large enough to get the full traceback, the function returns the actual number of return addresses in void**.

After obtaining void** from the backtrace function, backtrace_symbols can map the return addresses of these symbols to specific function names. The parameter int is the number of items in void**. The backtrace_symbols function can translate each returned value into “function name + internal offset + function return value”, so that the calling relationship of functions can be obtained more intuitively. The translated function traceback information is placed in the return value of backtrace_symbols, or NULL on failure. Note that the return value itself is malloc inside the backtrace_symbols function, so it must be explicitly free later.

The void* const* and int parameters for backtrace_symbols_fd are the same as those for the backtrace_symbols function, except that the function tracebacks are placed line by line in the file descriptor fd instead of the return value.

Then add our Catch code for Mach exceptions above:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    // Objective-C exception handling /signal processing
    [UncaughtExceptionHandler installUncaughtExceptionHandler];
    // Custom catch Mach exceptions
    [self catchMACHExceptions];
}
Copy the code

We can catch exceptions in three cases.

In addition to turning off the Debug Executable option above, we can also receive a Signal callback by turning off interception in the LLDB. When you run an App in Xcode Debug mode, the App signal process is captured by the LLDB Debugger. You can run the LLDB debugging command to switch the specified signal processing to the user layer for easy debugging.

(lldb) process handle --notify trueDo you really want to update all the signals? : [y/N] y ... (lldb) process handle --stopfalseDo you really want to update all the signals? : [y/N] y ... (lldb) process handle --passtrueDo you really want to update all the signals? : [y/N] y NAME PASS STOP NOTIFY =========== ===== ===== ====== SIGHUPtrue   false  true 
SIGINT       true   false  true 
SIGQUIT      true   false  true 
SIGILL       true   false  true 
SIGTRAP      true   false  true 
SIGABRT      true   false  true 
SIGEMT       true   false  true 
SIGFPE       true   false  true 
SIGKILL      true   false  true 
SIGBUS       true   false  true 
SIGSEGV      true   false  true 
SIGSYS       true   false  true 
SIGPIPE      true   false  true 
SIGALRM      true   false  true 
SIGTERM      true   false  true 
SIGURG       true   false  true 
SIGSTOP      true   false  true 
SIGTSTP      true   false  true 
SIGCONT      true   false  true 
SIGCHLD      true   false  true 
SIGTTIN      true   false  true 
SIGTTOU      true   false  true 
SIGIO        true   false  true 
SIGXCPU      true   false  true 
SIGXFSZ      true   false  true 
SIGVTALRM    true   false  true 
SIGPROF      true   false  true 
SIGWINCH     true   false  true 
SIGINFO      true   false  true 
SIGUSR1      true   false  true 
SIGUSR2      true   false  true 
Copy the code
(lldb) help process handle
     Manage LLDB handling of OS signals for the current target process. Defaults to showing current policy.

Syntax: process handle <cmd-options> [<unix-signal> [<unix-signal> [...]]]

Command Options Usage:
  process handle [-n <boolean>] [-p <boolean>] [-s <boolean>] [<unix-signal> [<unix-signal> [...]]]

       -n <boolean> ( --notify <boolean> )
            Whether or not the debugger should notify the user if the signal is received.

       -p <boolean> ( --pass <boolean> )
            Whether or not the signal should be passed to the process.

       -s <boolean> ( --stop <boolean> )
            Whether or not the process should be stopped if the signal is received.

If no signals are specified, update them all.  If no update option is specified, list the current values.
     
     This command takes options and free-form arguments.  If your arguments
     resemble option specifiers (i.e., they start with a - or-), you must use
     ' -- ' between the end of the command options and the beginning of the
     arguments.
Copy the code

Summarize Objective-C exceptions, Mach exceptions, Unix Signals

An uncaught Objective-C exception eventually causes the program to abort by sending itself a SIGABRT signal, which we can’t catch, If we call (void)pthread_kill(pthread_self(), SIGABRT)/kill(getpid(), SIGABRT) manually, we can receive SIGABRT signals, and they are application level exceptions. So the flow of Mach exceptions doesn’t go anywhere.

Generally Mach and objective-c exceptions will eventually be converted to Unix signals, but there are special cases, such as EXC_GUARD exceptions, and when Stackoverflow occurs, Unix Signals is crashing the thread callback, but there is no condition (stack space) to execute the callback code anymore.

Refer to the link

Reference link :🔗

  • Mach- Wikipedia
  • IOS abnormal signal thinking
  • Process thread termination functions in Linux multithreaded environment
  • Pthread_kill controversy
  • Thread signal pthread_kill()
  • The atom operation atomic_fetch_add
  • IOS Crash analysis guide
  • Handling unhandled exceptions and signals
  • Apple source file download list
  • IOS @try @catch exception mechanism
  • Read the crash principle
  • SDK development for Software Testing (ios) — Mach capture
  • IOS Crash/ Crash/ exception capture
  • Obtain iOS Crash/ Crash/ exception stack
  • KSCrash source code analysis
  • Examples of iOS thread and process communication (NSMachPort and NSTask, NSPipe)
  • RunLoop (input source, timing source, observer, inter-thread communication, inter-port communication, NSPort, NSMessagePort, NSMachPort, NSPortMessage)
  • Delivering Notifications To Particular Threads
  • MachPort communication between iOS development threads and Notification forwarding in child threads
  • Analysis of the technical principle of mobile terminal monitoring system
  • IOS performance optimization practice: How does Toutiao Douyin reduce OOM crash rate by 50%+
  • IOS NSInvalidArgumentException Crash
  • IOS call reloadRowsAtIndexPaths Crash abnormal NSInternalInconsistencyException
  • The quality of iOS development
  • NSException throws an exception &NSError
  • NSException: Error handling mechanism – how to collect error logs for products during debugging and after launch
  • Exception Programming Topics
  • IOS developers forget about NSException- it’s powerful
  • IOS Runtime Features and common crashes say bye!
  • Use of Exception Handling NSException
  • Exception statistics – IOS collects crash information NSEXCEPTION class
  • NSException Exception processing
  • IOS NSGenericException Crash
  • IOS Exception Handling
  • IOS Exception Handling
  • IOS crash classification,Mach exception, Unix signal and NSException exception
  • IOS Mach exceptions and signal signals