Article directory
- 1.Crash capture
-
- 1.2.NSException
- 1.2.C++ exceptions
- 1.3.Mach exception
- 1.4.Unix signals
- 2.Crash protection
-
- 2.1. Method not implemented
- 2.2.KVC causes crash
- 2.3.KVO causes crash
- 2.4. Collection class causes crash
- 2.5. Other scenarios that require attention:
1.Crash capture
According to the different sources of Crash
, it is divided into the following three categories:
1.2.NSException
Application layer exceptions, uncaught exceptions, cause the program to send the SIGABRT
signal to itself and crash, which is controllable by the application itself. Uncaught exceptions can be caught through the try-catch
or NSSetUncaughtExceptionHandler()
mechanism class.
Common Exceptions:
NSInvalidArgumentException
: Illegal parameter exception. Strengthen parameter checking to avoid passing in illegal parameters, especially parameters marked as nonull.NSRangeException
: out-of-bounds exceptionNSGenericException
: Modify the original collection while traversingNSInternalInconsistencyException
: Inconsistency exception. For example,NSDictionary
is used whenNSMutableNSDictionary
is used.NSFileHandleOperationException
: File processing exception. A common problem is insufficient storage spaceNSMallocException
: Memory exception. Such as insufficient memory.
All system-definedException
see NSExceptionName
Capturing NSExpection:
//Record the previous Crash callback function (if any) static NSUncaughtExceptionHandler *previousUncaughtExceptionHandler = NULL; + (void)registerUncaughtExceptionHandler {<!-- --> // Take out the Crash callback previously registered by others and back it up previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler(); // Then register your own NSSetUncaughtExceptionHandler( & amp;UncaughtExceptionHandler); } //Callback function on crash static void UncaughtExceptionHandler(NSException * exception) {<!-- --> //Exception stack information NSArray *stackInfo = [exception callStackSymbols]; //The reason for the exception NSString *reason = [exception reason]; //Exception name NSString *name = [exception name]; // Exception error reporting NSString *exceptionInfo = [NSString stringWithFormat:@"uncaughtException exception error report:\\ name:%@\\ reason:\\ %@\\ callStackSymbols:\\ %@", name, reason, [stackInfo componentsJoinedByString:@"\\ "]]; // Save Crash logs to the sandbox cache directory [SKTool cacheCrashLog:exceptionInfo name:@"CrashLog(UncaughtException)"]; // After your own handler is processed, remember to register other people's handlers back to form a standardized SOP. if (previousUncaughtExceptionHandler) {<!-- --> previousUncaughtExceptionHandler(exception); } // Kill the program to prevent the SIGABRT thrown at the same time from being caught by the Signal exception. kill(getpid(), SIGKILL); }
1.2.C++ exception
After the system catches the C++ exception, it will convert it into an OC exception and throw it. The call stack at this time is the leader when the exception occurs; but if the conversion fails, __cxa_throw
will be called to throw the exception. The call captain at this time is the stack that handles the exception, causing the original exception call stack to be lost.
Catching C++ exceptions:
- Set exception handling function:
g_originalTerminateHandler = std::set_terminate(CPPExceptionTerminate);
Call set_terminate(CPPExceptionTerminate)
to set the new global termination handler function and keep the old one.
- Override
__cxa_throw
:
void __cxa_throw(void* thrown_exception, std::type_info* tinfo, void (*dest)(void*)) {<!-- --> // Get the call stack and store it // Call the original __cxa_throw function again }
- Exception handling function
__cxa_throw
is executed later and enters the exception sorting function set byset_terminate
. If it is judged to be an OC exception, there will be nothing much and let the OC exception mechanism handle it; otherwise, the exception information will be obtained.
1.3.Mach exception
Kernel layer exceptions. User-mode developers can capture Mach exceptions by setting the exception ports of thread
, task
, and hot
through the Mach API.
tasks
: Resource ownership unit. Each task consists of a virtual address space, a port permission name control, and one or more threads. (similar to process)threads
: The unit of CPU execution in the taskports
: Secure simplex communication channel, accessible only through the send and receive functions.
APIs related to Mach
exceptions are:
task_get_exception_ports
: Get the exception port of the tasktask_set_exception_ports
: Set the exception port of the taskmach_port_allocate
: Creates a caller-specified port permission typemach_port_insert_right
: Insert the specified port into the target task
Note: Avoid listening during Xcode joint debugging, as it will cause deadlock.
1.4.Unix signals
Also known as BSD
signal, if the developer does not catch the Mach exception, the host layer method ux_exception()
will convert the exception into the corresponding Unix signal and pass the method threadsignal()
Delivers the signal to the erroring thread. Can be used with signal(x, SignalHandler)
to capture signal
.
Signal table:
SIGHUP
: hangSIGINT
: Program termination signal interrupt, issued when the user types the INTR character (usually Ctrl-C), is used to notify the foreground process group to terminate the process.SIGQUIT
: The program exits the signal quit, controlled by the QUIT character (usually Ctrl-). The program will generate a core file when it receives this signal and exits.SIGILL
: Execute illegal instructionSIGTRAP
: by breakpoint instruction or trap instructionSIGABRT
: Program interrupt signal abort.SIGBUS
: illegal addressSIGFPE
: Fatal arithmetic errorSIGKILL
: End the program immediately. Cannot be blocked, processed and ignored.SIGUSR1
: User signal 1SIGSEGV
: Invalid memory accessSIGUSR2
: User signal 2SIGPIPE
: Pipe burst. Inter-process communication, such as abnormal reading and writing of pipes.SIGALRM
: signal sent by alarmSIGTERM
: Termination signal, can be blocked and processed. Usually used to require the program to exit normally on its ownSIGSTKFLT
: stack overflowSIGCHLD
: Child process exitsSIGCONT
: Process continuesSIGSTOP
: process stoppedSIGTSTP
: Process stoppedSIGTTIN
: The process stops and the background process reads data from the terminal.SIGTTOU
: The process stops and the background process wants to write data to the terminal.SIGURG
: I/O urgent data reaches the current processSIGXCPU
: The CPU time of the process has expiredSIGXFSZ
: File size exceeds upper limitSIGVTALRM
: Virtual clock timeoutSIGPROF
: profile clock timeoutSIGWINVH
: window size changedSIGIO
: I/O relatedSIGPWR
: Shut downSIGSYS
: Illegal system call
Tips: Enter
kill -l
in the terminal to view all signal signals.
Capture the signal:
//General signals that need to be captured static const int g_fatalSignals[] = {<!-- --> SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGPIPE, SIGSEGV, SIGSYS, SIGTRAP, }; void installSignalHandler() {<!-- --> stack_t ss; struct sigaction sa; struct timespec req, rem; long ret; //Apply a memory space for use as an optional signal processing function stack ss.ss_flags = 0; ss.ss_size = SIGSTKSZ; ss.ss_sp = malloc(ss.ss_size); // Use the sigaltstack function to notify the system of the existence and location of the optional signal processing stack frame sigaltstack(&ss, NULL); //Specify the SA_ONSTACK flag to notify the system that this signal processing function should execute the registered signal processing function on the optional stack frame memset( & amp;sa, 0, sizeof(sa)); sa.sa_handler = handleSignalException; sa.sa_flags = SA_ONSTACK; sigaction(SIGABRT, &sa, NULL); } void XXXHandleSignalException(int signal) {<!-- --> // print stack NSMutableString *crashInfo = [[NSMutableString alloc] init]; [crashInfo appendString:[NSString stringWithFormat:@"signal:%d\\ ",signal]]; [crashInfo appendString:@"Stack:\\ "]; void* callstack[128]; int i, frames = backtrace(callstack, 128); char** strs = backtrace_symbols(callstack, frames); for (i = 0; i <frames; + + i) {<!-- --> [crashInfo appendFormat:@"%s\\ ", strs[I]]; } NSLog(@"%@", crashInfo); //Remove other Crash listeners to prevent deadlock NSSetUncaughtExceptionHandler(NULL); signal(SIGHUP, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGABRT, SIG_DFL); signal(SIGILL, SIG_DFL); signal(SIGSEGV, SIG_DFL); signal(SIGFPE, SIG_DFL); signal(SIGBUS, SIG_DFL); signal(SIGPIPE, SIG_DFL); }
2.Crash protection
2.1. Method not implemented
The implementation of the method cannot be found: unrecognized selector sent to instance
. For details of the search process, please see: iOS_Objective-C message sending (message search and message forwarding) process
solution:
Add a new category to NSObject
and implement several methods of message forwarding
to avoid Crash
:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {<!-- --> if ([self respondsToSelector:aSelector]) {<!-- --> // No processing has been implemented return [self methodSignatureForSelector:aSelector]; } return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } - (void)forwardInvocation:(NSInvocation *)anInvocation {<!-- --> NSLog(@"%@ can't responds %@", NSStringFromClass([self class]), NSStringFromSelector(anInvocation.selector)); } + (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {<!-- --> if ([self respondsToSelector:aSelector]) {<!-- --> // No processing has been implemented return [self methodSignatureForSelector:aSelector]; } return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } + (void)forwardInvocation:(NSInvocation *)anInvocation {<!-- --> NSLog(@"%@ can't responds %@", NSStringFromClass([self class]), NSStringFromSelector(anInvocation.selector)); }
2.2.KVC causes crash
Details of KVC’s search mode can be found at: iOS_KVC: Key-Value Coding-2 (visitor search mode). When the corresponding key is not found in the end, it will cause a crash.
Common scenarios:
- Scenario 1: key does not exist
XXXClass * obj = [[XXXClass alloc] init]; [obj setValue:nil forKey:@"xxx"]; // reason: '[<XXXClass 0x2810bfa80> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key xxx.' id value = [obj valueForKey:@"xxx"]; // Thread 1: "[<MOPerson 0x600000c76c10> valueForUndefinedKey:]: this class is not key value coding-compliant for the key xxx."
- Scenario 2: key is nil
XXXClass* obj = [[XXXClass alloc] init]; [obj setValue:@"value" forKey:nil]; // reason: '*** -[XXXClass setValue:forKey:]: attempt to set a value for a nil key' // In addition: value will not crash if it is nil [obj setValue:nil forKey:@"name"];
Solution: Override the implementation where the system throws an exception:
- (id)valueForUndefinedKey:(NSString *)key {<!-- --> NSLog(@"Error: valueForUndefinedKey: %@", key); return nil; } - (void)setValue:(id)value forUndefinedKey:(NSString *)key {<!-- --> NSLog(@"Error: setValue:%@ forUndefinedKey: %@", value, key); }
2.3.KVO causes crash
Scenes:
- Observer/observed are local variables
- Not implemented
observeValueForKeyPath:ofObject:changecontext:
- Remove unregistered observers (e.g. repeated removal)
Tips: Adding observers repeatedly will not crash, but will call back multiple times.
solution:
addObserver
andremoveObserver
must appear in pairs- Implemented using Facebook’s KVOController
2.4. Collection class causes crash
Common scenarios:
- Cross the line
NSArray *arr = [NSArray array]; id value = [arr objectAtIndex:1]; // Thread 1: "*** -[__NSArray0 objectAtIndex:]: index 1 beyond bounds for empty array"
- insert nil
NSMutableArray *arr = [NSMutableArray array]; [arr addObject:nil]; // Thread 1: "*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil" NSMutableDictionary *dict = [NSMutableDictionary dictionary]; [dict setObject:nil forKey:@"xxx"]; // Thread 1: "*** -[__NSDictionaryM setObject:forKey:]: object cannot be nil (key: xxx)"
solution:
- Use runtime to add null processing before calling these modification methods. For details, see: Demo
2.5. Other scenarios that require attention:
performSelector:
must first determinerespondsToSelector:
- Before calling the delegate method, you must first determine
respondsToSelector:
- The id type cannot be forcibly converted, you must first determine
isKindOfClass:
- When accessing UIKit, be sure to dispatch to the main queue
- An example. When thread access security cannot be guaranteed, remember to add a read-write lock.
dispatch_group_leave
must appear in pairs withdispatch_group_enter
- Check how attributes are modified (
assign
/strong
/weak
/copy
) - Block must be empty before calling
- Do not modify combined type objects while traversing them
- Time-consuming operations must be dispatched to sub-threads to avoid triggering watchDog
Debug
mode turns on zombie mode to facilitate immediate problem discovery.- Use
Xcode
‘sAddress Sanitizer
to detect address access out of bounds
refer to:
iOS Crash/Crash/Exception capture
Linux signal list
A brief discussion on Crash capture and protection in iOS
Summary of common Crash in iOS