Talking about App Crash and Hook Solution for iOS Performance Optimization

This article will explain the crash monitoring and anti-crash processing.

  • how to collect crashes

  • Use bugly, Youmeng and other third-party collection

  • Monitoring crash principle

  • Anti-Crash Handling

  • Common Crash Types

  • Anti-crash solution

  • hook scheme

  • security interface

How to collect crash

In the normal development process, due to the impreciseness of the code, such as not checking the input parameters, using C++ wild pointers, etc. will cause the program to crash. Crash should be regarded as the most serious bug, especially online crash. If there are a large number of app users, it may cause a great impact. Therefore, a mechanism is needed to collect crashes in the project and solve them in time.

Use bugly, Youmeng and other third-party collection

Most companies use third-party platforms to collect crashes. Bugly, Youmeng, and talkingdata are mostly used in the industry. The author recommends bugly, which is developed by Tencent and is relatively lightweight. It is very convenient to monitor crashes and freezes.

Monitoring crash principle

Most of the first-line manufacturers will develop their own crash capture frameworks. At this time, it is necessary to understand the principle of crash capture. You can read the open source library kscrash or plcrashreporter. In fact, the principle of crash capture is very simple. There are mainly two situations to deal with:

1. OC abnormalities. NSException is a crash caused by OC code. We can first save the previously registered exception handler through NSGetUncaughtExceptionHandler, and then set our own exception handler through NSSetUncaughtExceptionHandler. We don’t monitor anymore and need to set back to the principle exception handler. In our own uncaughtHandleException handler, we need to manually Call the original handler.

static NSUncaughtExceptionHandler* g_previousUncaughtExceptionHandler;
void installUncaughtExceptionHandler(void){
    g_previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
    NSSetUncaughtExceptionHandler( & uncaughtHandleException);
}
void uninstallUncaughtExceptionHandler(void){
    if(g_previousUncaughtExceptionHandler){
        NSSetUncaughtExceptionHandler(g_previousUncaughtExceptionHandler);
    }
}
void uncaughtHandleException(NSException *exception)
{
    // exception stack information
    NSArray *stackArray = [exception callStackSymbols];
    // the reason for the exception
    NSString *reason = [exception reason];
    // exception name
    NSString *name = [exception name];
    NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason: %@\
Exception name: %@\
Exception stack: %@", name, reason, stackArray];
    NSLog(exceptionInfo);
    if (g_previousUncaughtExceptionHandler != NULL)
    {
        g_previousUncaughtExceptionHandler(exception);
    }
}

2. Signal signal capture. The Signal signal is an exception thrown by the signal signal after the exception conversion of the iOS underlying mach signal. Since it is an exception compatible with the posix standard, we can also register the corresponding signal through the sigaction function.

static struct sigaction* g_previousSignalHandlers = NULL; //Old signal processing function structure array
static int g_fatalSignals[] = {
    SIGHUP,
    SIGINT,
    SIGQUIT,
    SIGABRT,
    SIGILL,
    SIGSEGV,
    SIGFPE,
    SIGBUS,
    SIGPIPE
};
static int g_fatalSignalsCount = (sizeof(g_fatalSignals) / sizeof(g_fatalSignals[0]));
const int* kssignal_fatalSignals(void){
    return g_fatalSignals;
}
int kssignal_numFatalSignals(void){
    return g_fatalSignalsCount;
}
void signalExceptionHandler(int signo, siginfo_t *info, void *uapVoid){
    void *frames[128];
    int i, len = backtrace(frames, 128);
    //Stack information
    char **symbols = backtrace_symbols(frames, len);
    NSMutableString *exceptionContent = [[NSMutableString alloc] init];
    [exceptionContent appendFormat:@"signal name: %d \
 signal stack:\
",signo];
    for (i = 0; i < len; + + i)
    {
        [exceptionContent appendFormat:@"%s\r\
", symbols[i]];
    }
    // release cache
    free(symbols);
    raise(signo);
}
void installSignalHandler(void){
    const int* fatalSignals = kssignal_fatalSignals();
    int fatalSignalsCount = kssignal_numFatalSignals();
    if(g_previousSignalHandlers == NULL){
        g_previousSignalHandlers = (struct sigaction *)malloc(sizeof(*g_previousSignalHandlers)
                                                              * (unsigned)fatalSignalsCount);
    }
    //Initialize the handler function structure
    struct sigaction action = {<!-- -->{0}};
    action.sa_flags = SA_SIGINFO | SA_ONSTACK;
    sigemptyset( & action.sa_mask);
    action.sa_sigaction = & signalExceptionHandler;
    for(int i = 0; i < fatalSignalsCount; i ++ )
    {
        if(sigaction(fatalSignals[i], &action, &g_previousSignalHandlers[i]) != 0)
        {
            // cancel the listened handler
            for(i--;i >= 0; i--)
            {
                sigaction(fatalSignals[i], &g_previousSignalHandlers[i], NULL);
            }
            break;
        }
    }
}
void uninstallSignalHandler(void){
    const int* fatalSignals = kssignal_fatalSignals();
    int fatalSignalsCount = kssignal_numFatalSignals();
    for(int i = 0; i < fatalSignalsCount; i ++ )
    {
        sigaction(fatalSignals[i], &g_previousSignalHandlers[i], NULL);
    }
}

Anti-crash handling

Common crash types

According to the author’s experience, most of the crashes in oc are caused by the failure to judge the input parameters of the calling method, such as adding an object to an array without judging empty, accessing array elements out of bounds, etc.; there are also some C++ crashes, such as using wild pointers .

Anti-crash solution

Since most of the crashes in oc are caused by not judging the input parameters, calling the method to judge the input parameters can solve the crash. How to solve this kind of crash in a unified way, there are two solutions: hook solution and security interface

hook scheme

This solution hooks the methods of the common classes of the system to make parameter judgments. For example, for the addObject method of hook NSMutableArray, perform a null operation.

@implementation NSMutableArray (akSafe)
 + (void) load {
    [self swizzMethodOriginalSelector:@selector(addObject:)
                     swizzledSelector:@selector(akSafe_addObject:)];
}
 + (void)swizzMethodOriginalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {
    Method originalMethod = class_getInstanceMethod(self. class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(self. class, swizzledSelector);
    BOOL didAddMethod = class_addMethod(self. class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {
        class_replaceMethod(self. class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}
- (void)aksafe_AddObject:(id)anObject {
    if (anObject) {
        [self aksafe_AddObject:anObject];
    }
}
@end

Security interface

This solution encapsulates the methods of the common classes of the system in one layer, and performs parameter judgment. Everyone calls the security interface uniformly. For example, the addObject method that encapsulates NSMutableArray is aksafe_AddObject, and everyone calls aksafe_AddObject to add objects.

@implementation NSMutableArray (aksafe)
- (void)aksafe_AddObject:(id)anObject {
    if (anObject) {
        [self addObject:anObject];
    }
}
@end

The two solutions have their own advantages and disadvantages. The advantage of the hook solution is that the business side can directly call the system method. The disadvantage is that the performance is damaged due to the hook. The security interface solution is that the business side needs to call the security interface uniformly, and the advantage is that it is lightweight. The author recommends option 2, which is lightweight and can be used as a coding standard.