This is the 7th day of my participation in the November Gwen Challenge. Check out the details: The last Gwen Challenge 2021

1. The container is out of bounds

The following code will cause the container to crash when clicked on the button.

@interface ViewController () { NSArray *dataArr; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. DataArr = @[@" 1st ",@" 2nd ",@" 3rd ",@" 4th "]; } - (IBAction)btnAction:(UIButton *)sender { NSLog(@"%@" ,dataArr[4]); } @endCopy the code

So how do you handle out-of-bounds container crashes here? Here we need to write an NSArray classification, and then use method-Swizzing again to handle it. ObjectAtIndexedSubscript = objectAtIndexedSubscript = objectAtIndexedSubscript

ObjectAtIndexedSubscript: objectAtIndexedSubscript: objectAtIndexedSubscript: objectAtIndexedSubscript: objectAtIndexedSubscript: objectAtIndexedSubscript: objectAtIndexedSubscript: objectAtIndexedSubscript

@implementation NSArray (LSArray) + (void)load { Method originalMethod = class_getInstanceMethod(self, @selector(objectAtIndexedSubscript:)); Method swizzleMethod = class_getInstanceMethod(self, @selector(lsobjectAtIndexedSubscript:)); method_exchangeImplementations(originalMethod, swizzleMethod); } - (id)lsobjectAtIndexedSubscript:(NSUInteger)idx{ if(idx < self.count) { return [self lsobjectAtIndexedSubscript:idx];  } NSLog(@" %lu >= %lu",idx,self.count); return nil; } @endCopy the code

Here we use didAddMethod to determine if objectAtIndexedSubscript is present, replace it if it is present, and swap imp if not.

+ (void)load { Method originalMethod = class_getInstanceMethod(self, @selector(objectAtIndexedSubscript:)); Method swizzleMethod = class_getInstanceMethod(self, @selector(lsobjectAtIndexedSubscript:)); bool didAddMethod = class_addMethod(self, @selector(objectAtIndexedSubscript:), method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod)); if (didAddMethod) { class_replaceMethod(self, @selector(lsobjectAtIndexedSubscript:),method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzleMethod); }}Copy the code

There’s actually a problem here, because you need to add a singleton to make sure you only swap once.

+ (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method originalMethod = class_getInstanceMethod(self, @selector(objectAtIndexedSubscript:)); Method swizzleMethod = class_getInstanceMethod(self, @selector(lsobjectAtIndexedSubscript:)); bool didAddMethod = class_addMethod(self, @selector(objectAtIndexedSubscript:), method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod)); if (didAddMethod) { class_replaceMethod(self, @selector(lsobjectAtIndexedSubscript:),method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzleMethod); }}); }Copy the code

But it still crashes when it runs here. Why? The essence of a method is a message, which contains the receiver and the body of the message (SEl and argument). Here self is NSArray, but the collapse message is __NSArrayI, so we need to change the class in the originalMethod

Method originalMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndexedSubscript:));
Copy the code

So there’s no problem with the data crossing the line. Of course, other NSArray classes also need this treatment, such as mutable array __NSArrayM, immutable empty array __NSArray0, etc.

2. NSSetUncaughtExceptionHandler

There are many types of crash, so is there a way to capture all of them? Apple provides an API. NSSetUncaughtExceptionHandler.

Write an ExceptionHandler class, and add a class method installUncaughtExceptionHandler, and call NSSetUncaughtExceptionHandler in this method.

+ (void)installUncaughtExceptionHandler {
    NSSetUncaughtExceptionHandler(&lsExceptionHandlers);
}

Copy the code

NSSetUncaughtExceptionHandler parameter is a c function inside.


void lsExceptionHandlers(NSException *exception) {
    NSLog(@"%s",__func__);
    
    int32_t exceptionCount = atomic_fetch_add_explicit(&LGUncaughtExceptionCount,1,memory_order_relaxed);
    if (exceptionCount > LGUncaughtExceptionMaximum){
        return;
    }
    NSArray *callStack = [ExceptionHandler lsBackTrace];
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
    [userInfo setObject:exception.name forKey:LGUncaughtExceptionHandlerSignalExceptionName];
    [userInfo setObject:exception.reason forKey:LGUncaughtExceptionHandlerSignalExceptionReason];
    [userInfo setObject:callStack forKey:LGUncaughtExceptionHandlerAddressesKey];
    [userInfo setObject:exception.callStackSymbols forKey:LGUncaughtExceptionHandlerCallStackSymbolKey];
    [userInfo setObject:@"LSException" forKey:LGUncaughtExceptionHandlerFileKey];
     [[[ExceptionHandler alloc] init]   performSelectorOnMainThread:@selector(ls_handleException:)  withObject:[NSException exceptionWithName:exception.name reason:exception.reason userInfo:userInfo] waitUntilDone:YES];
}
Copy the code

Then in the AppDelegate didFinishLaunchingWithOptions call this class method, ensure the crash information collection early enough.

[ExceptionHandler  installUncaughtExceptionHandler];
Copy the code

So now if an application crashes, it’s going to go to lsExceptionHandlers, and they’re going to log the crash based on the exception that they get, and then they’re going to upload it to the server. Here, clicking Button will cause an array out of bounds crash. See the saved crash address and go to that address.

Open the log file and see that the crash information is saved, so you can upload the file to the server.

Next, look at the collapsing stack. You see there’s a function _objc_Terminate.

Search the source code for _objc_terminate and find the following implementation:

In the previous article, iOS low-level exploration — DYLD loading process (part 1) wrote that _objc_init also called exception_init method, so what is exception_init handling exception?

If an exception occurs, the _objc_TERMINATE callback will be called. So _objc_init does the exception callback.

void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
Copy the code

Uncaught_handler = uncaught_handler = uncaught_handler = uncaught_handler = uncaught_handler = uncaught_handler

static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler;
Copy the code

Further down there is the assignment of uncaught_handler, but the search does not have anywhere to call it, so it is the upper API.

objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}
Copy the code

See NSSetUncaughtExceptionHandler and objc_setUncaughtExceptionHandler names are very similar, Here know the objc_setUncaughtExceptionHandler NSSetUncaughtExceptionHandler is encapsulation. The previous call (*uncaught_handler)((id)e) called the callback that was passed in, so the lsExceptionHandlers method also has an NSException.