We often encounter Crash in the development process, which is normal, but as an excellent iOS developer, I will minimize the user experience.

Offline Crash, we can debug directly, combined with the stack information, it is not difficult to locate! Of course, there is some information about the online Crash, after all, Apple dad’s products are still very good!

Crashlog on the iPhone can also analyze some, This requires the user to go to diagnostics and Dosings on the phone and check Auto Send and then in Xcode Window->Organizer->Crashes the latest version of the app crash log According to the crash stack and other related information, especially the hyperlink at the program code level, one-click can directly jump to the relevant code of the program crash, so it is easier to locate the source of the bug.

In order to be able to detect application problems first, applications need to implement their own crash log collection service. There are many mature open source projects, such as **KSCrash, PLCrashReporter, CrashKit, etc. The pursuit of convenience and worry, for the confidentiality requirements of the program is not high, you can also choose a variety of one-stop Crash statistical products, such as Crashlytics, Hockeyapp, Friendly Alliance, Bugly and so on

But, all buts, it’s not enough! Because we are no longer a simple iOS developer, we will go to the bottom, understand the principle, master the content and skills of installation is our required course

First, let’s take a lookCrashThe underlying principles of

ExceptionType usually contains two elements: a Mach exception and a Unix signal.

Exception Type:         EXC_BAD_ACCESS (SIGSEGV)    
Exception Subtype:      KERN_INVALID_ADDRESS at 0x041a6f3
Copy the code

What are Mach exceptions? How does it relate to Unix signals?

Mach is an XNU microkernel core. Mach exceptions are the lowest level kernel-level exceptions defined below. Each thread, task, and host has an array of exception ports. Part of the API of Mach is exposed to user mode. The developer of user mode can directly set the exception ports of Thread, task, and host through MachAPI to catch Mach exceptions and grab Crash events.

All Mach exceptions are converted to the appropriate Unix signal at the Host layer by ux_Exception and posted to the offending thread via ThreadSignal. The POSIX API in iOS is implemented through the BSD layer on top of Mach.

Therefore, EXC_BAD_ACCESS(SIGSEGV) means that EXC_BAD_ACCESS exceptions in the Mach layer are converted into SIGSEGV signals at the host layer and sent to the faulty thread.

An iOS Crash occurred. Procedure

  • KVO problem

  • The NSNotification thread is faulty

  • An array

  • Wild pointer

  • Background Task Timeout

  • Memory came out

  • The main thread is stuck beyond the threshold

  • Deadlock… Let’s take the two most common types of Crash and analyze them

  • Exception

  • Signal

Crash analysis and processing

As we know above, since the thread is finally sent as a signal, we can register the corresponding function to catch the signal. arriveHookThe effect of

+ (void)installUncaughtSignalExceptionHandler{
    NSSetUncaughtExceptionHandler(&LGExceptionHandlers);
    signal(SIGABRT, LGSignalHandler);
}
Copy the code

About Signal Reference

We Hook the information from the above function, and now we start wrapping. Again, this is for unified encapsulation, because we need to consider Signal later

void LGExceptionHandlers(NSException *exception) {
    NSLog(@"%s",__func__);
    
    NSArray *callStack = [LGUncaughtExceptionHandle lg_backtrace];
    NSMutableDictionary *mDict = [NSMutableDictionary dictionaryWithDictionary:exception.userInfo];
    [mDict setObject:callStack forKey:LGUncaughtExceptionHandlerAddressesKey];
    [mDict setObject:exception.callStackSymbols forKey:LGUncaughtExceptionHandlerCallStackSymbolsKey];
    [mDict setObject:@"LGException" forKey:LGUncaughtExceptionHandlerFileKey];
    
    // exception - myException

    [[[LGUncaughtExceptionHandle alloc] init] performSelectorOnMainThread:@selector(lg_handleException:) withObject:[NSException exceptionWithName:[exception name] reason:[exception reason] userInfo:mDict] waitUntilDone:YES];
}
Copy the code

Now to handle the wrapped myException, do two things

  • Storage, upload: easy for developers to check and repair
  • We can’t afford to let Crash happenBUGFlash back on the mobile phone in use, hoping to “bring the dead back to life.”
- (void)lg_handleException:(NSException *)exception{// NSDictionary *userInfo = [exception userInfo]; [self saveCrash:exception file:[userInfo objectForKey:LGUncaughtExceptionHandlerFileKey]]; }Copy the code

The following are some of the helper functions that are wrapped

  • Save crash information or upload: local storage for encapsulated data, and upload the server accordingly!
- (void)saveCrash:(NSException *)exception file:(NSString *)file{ NSArray *stackArray = [[exception userInfo] objectForKey:LGUncaughtExceptionHandlerCallStackSymbolsKey]; NSString *reason = [exception reason]; NSString *name = [exception name]; // NSLog(@); // NSLog(@); // NSLog(@"crash: %@", exception);
    
    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 *dat = [NSDate dateWithTimeIntervalSinceNow:0]; NSTimeInterval a=[dat 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);
}
Copy the code
  • Get function stack information, where you can get symbolic information about the response call stack, passed back through an array
+ (NSArray *)lg_backtrace{ void* callstack[128]; int frames = backtrace(callstack, 128); Char ** STRS = backtrace_symbols(callstack, frames); // The information retrieved from backtrace is converted to an array of strings int I; NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];for (i = LGUncaughtExceptionHandlerSkipAddressCount;
         i < LGUncaughtExceptionHandlerSkipAddressCount+LGUncaughtExceptionHandlerReportAddressCount;
         i++)
    {
        [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
    }
    free(strs);
    return backtrace;
}

Copy the code
  • Get application information. This function provides Siganl data encapsulation
NSString *getAppInfo(){
    NSString *appInfo = [NSString stringWithFormat:@"App : %@ %@(%@)\nDevice : %@\nOS Version : %@ %@\n",
                         [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"],
                         [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
                         [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],
                         [UIDevice currentDevice].model,
                         [UIDevice currentDevice].systemName,
                         [UIDevice currentDevice].systemVersion];
    //                         [UIDevice currentDevice].uniqueIdentifier];
    NSLog(@"Crash!!!! % @", appInfo);
    return appInfo;
}
Copy the code

After all this preparation, you can see the program crash very clearly, hahaha! (as if the rout hadn’t been clear before), here’s the thing: I mean you know exactly what the rout was doing! Here is the message stored in our sandbox before we crashed: error.log

Let’s do a little bit of snooping: when a Runloop comes in, we listen for all modes and open the loop (a parallel space to our application’s self-initiated Runloop).

SCLAlertView * alert = [[SCLAlertView alloc] initWithNewWindowWidth: 300.0 f]; [alert addButton:@"Crash"actionBlock:^{ self.dismissed = YES; }]; [alert showSuccess:exception.name subTitle:exception.reason closeButtonTitle:nil duration:0]; CFRunLoopRef runloop = CFRunLoopGetCurrent(); CFArrayRef allMode = CFRunLoopCopyAllModes(runloop);while(! Self. Dismissed) {// machO // background update -log
    // kill
    // 
    for (NSString *mode in(__bridge NSArray *)allMode) {CFRunLoopRunInMode((CFStringRef)mode, 0.0001,false);
    }
}

CFRelease(allMode);
Copy the code

In this parallel space we open a popbox, and this popbox, with our application alive, has the corresponding responsiveness, so far: right now who else! Isn’t that what it is? As long as our conditions hold, then continue to do some of our work in the corresponding parallel space, the program dies: What is dead may never die,but rises again harder and stronger

A workaround that signal cannot intercept

In debug mode, if you trigger a crash, the application crashes directly to the main function. Breakpoints are not used, and no log information is displayed. If you want to see log information, you need to use SIGABRT in the LLDB. Type prohand-ptrue-sfalsesigabrt or you won’t see anything.

The breakpoint is then broken, the program listens, and the rest of the operation wraps the Exception, like Exception

Finally, we need to pay attention to the corresponding memory reclamation for our listener:

  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])
   {
       kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);
   }
   else
   {
       [exception raise];
   }
Copy the code

So far, the Crash handling we’ve responded to is pretty basic, but if you want to explore more, there are many places like:

  • Whether we can snap hook system, the method of abnormal NSSetUncaughtExceptionHandler, has refused to transfer UncaughtExceptionHandler effect

  • Is there a more appropriate way to use Runloop to reflect the exception

  • How do we continue to ensure stable application execution