0x1: Previously

In the previous chapter, the processing process of Crash was divided into four links, and the methods of Crash prevention were analyzed. The remaining three links are discussed in this chapter.

0x2: Crash interception

All undefended crashes will eventually end up here, where we must ensure the comprehensiveness and stability of interception and intercept all types of exceptions as much as possible, while the interception logic itself cannot generate exceptions. Then we need to consider through the following aspects.

I: Crash types

Like most operating systems, iOS exceptions fall into three categories: user-level system low-level signals. Let’s take a look at what each exception does

  • Mach exceptions are hardware or system-level exceptions. Mach is a microkernel. The other is the logic that’s unique to iOS or apple. Even user-level exceptions sink first into the Mach layer and then are issued, which is equivalent to Mach exceptions in another sense. Apple’s official documentation states that the move was made for the sake of uniformity, but does not give a specific reason. His trigger flow looks something like the following

    Then I looked at the Runtime source code to further prove this statement. Runloop uses this method to listen for Mach exception messages extensively. If Crash is ready to break the loop, because the system also needs to listen for Crash, the unified exit will be very convenient for listening.

    According to the code context, Apple will listen to the unified exception port and perform the corresponding operation after the exception occurs, which also confirms my inference at that time.

  • Exception

    A very common exception that triggers a flow of about

  • signal

    There are several scenarios for signal generation

    • Signal converted from MachExcption

    • Abort signal due to Exception

    • User-defined signal

    If a signal is received, a Crash may occur

II: Crash transfer process

Each type of Crash is analyzed above, so how are these three types of Crash transmitted in the App life cycle? How do they translate into each other and how do they relate to each other?

Extract some key information from the picture above

  • 1: Exception eventually converts to Mach Exception

  • 2: More comprehensive interception through Mach port

  • Abort () is thrown if an exception occurs. Signal abort() is thrown.

  • 4: Exception cannot be intercepted by catching Signal.

III: Choice of interception

Based on the above analysis, you can certainly argue that interception through Mach ports is more comprehensive, after all, Apple also uses them. In practice, however, Mach intercepts all exceptions and semaphores. In other words, any operation (such as sending a custom signal) can be caught by Mach, and if caught in its capture callback, it is very likely to deadlock and conflict with the system processing. When I read PLCrash’s document, I also saw a sentence written by the developer:

That means you’ve been screwed.

Then there are only signal and exception. In fact, careful students have already found that the advantages and disadvantages of these two states are complementary

  • Singal can catch all exceptions except Exception.

  • Exception can only fetch application layer exceptions and cannot handle semaphores

Then the final capture method is singnal + exception, and the final process is as follows:

IV: pit

It can be seen from the above flow chart that there is a PreviousHandle before each CustomHandle, actually because there can only be one customHandel in iOS system. If you have, or plan to have, more than one Crash protection RELATED SDK in your project (although this is not recommended), then the multiple Handles will clash, causing the stack to be ambiguous or lost. Therefore, before registering our Handle, we should first save the previous Handle pointer and call it back through the function pointer after our Handle is processed. In this way, we can ensure that each Handle can be called normally.

  • Exception: before via NSGetUncaughtExceptionHandler handle pointer, then through NSSetUncaughtExceptionHandler (oldHandler); Call back.

  • Signal: Get the previous Handle pointer using the sigAction function.

0x3: Stack fetch

Because Apple uses Address Space Layout Randomization, the online stack must be interpreted through symbol table stack restore, or otherwise the memory Address. So when we use NSThread related functions in Debug, although we can see the stack of readable lines, but not on the online package, how do we get the stack? Let’s look at the construction of the symbol table:

Before we get such symbol table, we usually manually restore, find a real machine of the same system, find the base address of the corresponding library and calculate according to the offset of the function on the symbol table (through the relevant function of LLDB).

By looking at mach-O related interfaces, relevant functions can be found to restore the intra-end symbol table. The general process is as follows:

  • Get function address:

    • Iterate over all images in Mach- O

    • Gets the base address of each image

    • Gets the stack frame function address from the stack offset address

  • Translate function addresses into function names

    • Find nLIST_64 structure of symple table segment corresponding to Image

    • Nlist_64.n_un.n_strx gets the string corresponding to the function

The end result:

0 x4: Crash later

Usually, some reporting operations are performed in Handle after AppCrash.

But there are two problems with this:

  • Apple does not recommend doing too many operations in Handle. Moreover, network requests such as data reporting are time-consuming operations, and the App may be killed before they are completed.

  • App directly flash back, bad experience

The current runloop is broken after Crash

Note: The runloop continues, but the loop has been broken, and the app exits only after the loop ends. RetVal is set to NO

After iOS Crash occurs, the condition of the do-while loop in runloop will be set to NO. Then, after the Handler function is finished, the current loop will end directly and the next loop will not be carried out. At this point, we just need to restart runloop in Handler to continue executing the code. It can still accept all kinds of events, such as interactive events, as long as each model is opened, because different operations happen in different phases. However, the contents of the previous runloop are in an uncontrollable state, and the previous items are left in memory forever and cannot be recovered. Therefore, the App should be terminated immediately after the relevant operation to avoid other exceptions. This is similar to a safe mode, which deals with the relevant items in the safe mode.

Function call:

void continueAfterCrash() { CFRunLoopRef runLoop = CFRunLoopGetCurrent(); CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop); For (NSString *mode in (__bridge NSArray *)allModes) {CFRunLoopRunInMode((CFStringRef)mode, 1.0e10, false); }}Copy the code

In the new Runloop we call abort to exit the App after doing some operations, such as a friendly prompt to tell the user that the App is about to exit. However, this operation has risks. Note the following

  • After a new runloop is opened, the contents of the previous Runloop will remain in the memory forever and become uncontrollable. Once accessed, there may be exceptions. Therefore, we should end the App in time after completing the necessary operations.

  • The safe mode must be stable, and any reporting, pop-ups, or other logic executed in the new Runloop must use the system’s native API and not rely on any third party.

  • Try not to do too much and finish in time.

0x5: Resources

  • Apple iOS Api

  • iOS Open Sourcre

  • CFRunloop

  • XNU 3248.60.10 source

  • Understanding Crash Reports on iPhone OS

  • MAC OS X & IOS

0 x6: at last

This is probably all the process of Crash protection. Through the explanation of two articles, I hope you can have some understanding of the Crash process of iOS system. There is not too much source code, but the decoupling degree is not enough, and the idea is very simple with the code. The article is just a personal introduction, there may be incorrect place, if you have any different views welcome to leave a comment discussion.