1. Capture iOS Crash 1. Set an exception breakpoint and run Set exception breakpoint.png Note: After setting an Xcode exception breakpoint and running the program, when a crash occurs, the breakpoint will locate the error code line, but it is only applicable to the development stage. The online app crash also needs to be captured by the crash collection mechanism and recorded in the log. 2. Mach exceptions and Unix signals - When an iOS Crash occurs, a Mach exception (the lowest kernel-level exception) is generated first, and then the Mach exception is converted into a corresponding Unix signal by ux_exception at the host layer, and the signal is delivered to the faulty thread through threadsignal.
- When capturing crash events, Mach exceptions are preferred because Mach exception handling occurs before Unix signal handling. If the Mach exception handler causes the program to exit, the Unix signal will never reach the process. The purpose of converting Unix signals is to be compatible with the more popular POSIX standard (SUS specification), so that you don't have to understand the Mach kernel and you can use Unix signals for compatible development.
- When implementing the solution, the Crash event is captured by capturing the combination of Mach exceptions + Unix signals. When choosing a specific solution, you can choose an excellent open source project such as PLCrashReporter, or you can choose a complete Crash reporting and statistics product such as Umeng and Bugly (depending on the project requirements).
3. Capture Crash Not all crashes can be caught by NSException. If not, you can use the signal mechanism to capture the error content when the crash occurs. 1) NSException that can be captured, by registering NSUncaughtExceptionHandler to capture the exception information - //Register exception handling function
-
- NSSetUncaughtExceptionHandler(&uncaught_exception_handler);
-
- //Exception handling function
-
- static void uncaught_exception_handler (NSException *exception) {
-
- //You can get NSException information
-
- //...
-
- abort();
-
- }
Note: You cannot get signals when using Objective-C exception handling. 2) For NSException that cannot be caught, use the Unix standard signal mechanism to register processing functions when signals such as SIGABRT, SIGBUS, and SIGSEGV occur. - //Register to handle SIGSEGV signal
-
- signal(SIGSEGV,handleSignal);
-
- // Register to handle other signals ....
-
-
-
- //Signal processing function
-
- static void handleSignal( int sig ) {
-
- }
2. Crash log composition The previous section introduced Crash capture. This section will look at the composition of the Crash log. 1. Log content demo The log is mainly divided into six parts: process information, basic information, exception information, thread backtrace, thread status and binary image. The following is the main information extracted from a specific crash log of an APP, as shown below: - //1. Process information
-
- Hardware Model: iPhone9,2
-
- Process: AppName [3580]
-
- Path: /var/containers/Bundle/Application/C7B90C8A-E269-4413-A011-552971D1ED39/AppName.app
-
- Identifier: xxxx.xxx.xxxx.xxx
-
- Version: xx.xx
-
- Code Type: ARM-64 (Native)
-
- Parent Process: [1]
-
-
-
- //2. Basic information
-
- Date / Time : 2017-05-22 03:05:06.743 +0800
-
- OS Version: iPhone OS 10.2.1 (14D27)
-
-
-
- //3. Abnormal information
-
- Exception Type: NSInvalidArgumentException(SIGABRT)
-
- Exception Codes: -[NSNull integerValue]: unrecognized selector sent to instance 0x1a9d88ef8 at 0x00000001835c7014
-
- Crashed Thread: 0
-
-
-
- //4. Thread backtrace (shows the backtrace information of the crash thread, others are omitted)
-
- Thread 0 Crashed:
-
- 0 libsystem_kernel.dylib 0x00000001835c7014 __pthread_kill + 4
-
- 1 libsystem_c.dylib 0x000000018353b400 abort + 140
-
- 2 AppName 0x0000000100a26704 0x0000000100028000 + 10479360
-
- 3 CoreFoundation 0x00000001845f9538 ___handleUncaughtException + 644
-
- 2 CoreFoundation 0x0000000184600268 ___methodDescriptionForSelector
-
- 3 CoreFoundation 0x00000001845fd270 ____forwarding___ + 916
-
- 4 CoreFoundation 0x00000001844f680c _CF_forwarding_prep_0 + 80
-
- 5 AppName 0x0000000100205280 0x0000000100028000 + 1954432
-
- 6 AppName 0x00000001002ae59c 0x0000000100028000 + 2647440
-
- 7 AppName 0x0000000100482944 0x0000000100028000 + 4565312
-
- 16 CoreFoundation 0x00000001845a6810 ___CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
-
- + 12
-
- 17 CoreFoundation 0x00000001845a43fc ___CFRunLoopRun + 1660
-
- 18 CoreFoundation 0x00000001844d22b8 CFRunLoopRunSpecific + 436
-
-
-
- //5. Process status (display part)
-
- Thread 0 crashed with ARM 64 Thread State:
-
- x0: 000000000000000000 x1: 000000000000000000 x2: 000000000000000000 x3: 0xffffffffffffffff
-
- x4: 0x0000000000000010 x5: 0x0000000000000020 x6: 000000000000000000
-
- x8: 0x0000000008000000 x9: 0x0000000004000000 x10: 000000000000000000 x11: 0x00000001ac336c83
-
- x12: 0x00000001ac336c83 x13: 0x0000000000000018 x14: 0x0000000000000001
-
- x16: 0x0000000000000148 x17: 000000000000000000 x18: 000000000000000000
-
-
-
- //6. Binary image (display part)
-
- Binary Images:
-
- 0x100028000 - 0x1011dbfff +AppName arm64 /var/containers/Bundle/Application/C7B90C8A-E269-4413-A011-552971D1ED39/AppName.app/AppName
-
- 0x18368a000 - 0x183693fff libsystem_pthread.dylib arm64 /usr/lib/system/libsystem_pthread.dylib
-
- 0x1835a8000 - 0x1835ccfff libsystem_kernel.dylib arm64 /usr/lib/system/libsystem_kernel.dylib
-
- 0x1834b1000 - 0x1834b5fff libdyld.dylib arm64 /usr/lib/system/libdyld.dylib
-
- 0x1834d8000 - 0x183556fff libsystem_c.dylib arm64 /usr/lib/system/libsystem_c.dylib
-
- 0x183481000 - 0x1834b0fff libdispatch.dylib arm64 /usr/lib/system/libdispatch.dylib
-
- 0x183028000 - 0x183401fff libobjc.A.dylib arm64 /usr/lib/libobjc.A.dylib
2. Analysis of log content composition The parts of the entire log that are directly related to the crash information and can best help developers locate the problem are: exception information and thread backtrace. 1) Process information: related information of the crash process - Hardware Model: Identifies the device type. If many crash logs come from the same device type, it means that the app has problems only on a certain type of device. In the log above, the device that generated the crash log is an iPhone 7 Plus (iPhone 7 Plus also has 2 versions, iPhone9,2 and iPhone9,4. The hardware code is D11AP and D111AP. The models are: A1661, A1784, A1785 and A1786.)
- Process is the name of the application. The number in the brackets is the process ID of the application when it crashes.
2) Basic Information: Provides some basic information, including the date and time when the crash occurred and the iOS version of the device. 3) Exception information: The type of exception thrown when the crash occurs. You can also see the exception code and the thread that threw the exception. - //Take the exception information in the above content as an example:
-
- Exception Type: NSInvalidArgumentException(SIGABRT)
-
- Exception Codes: -[NSNull integerValue]: unrecognized selector sent to instance 0x1a9d88ef8 at 0x00000001835c7014
-
- Crashed Thread: 0
- Exception Type: usually includes Signal signals in 1.7 and EXC_BAD_ACCESS, NSRangeException, etc.
- Exception Codes:
- Crashed Thread: The thread id where the crash occurred
4) Thread Backtrace: The backtrace is a list of all active frames when the crash occurred. It contains a list of functions called when the crash occurred. 5) Thread state: the value of the register at the time of the crash. This information is usually not needed, because the information in the backtrace is enough for you to find the problem. 6) Binary image: The binary file that was loaded when the crash occurred. 3. Interpretation of abnormal information 1. Exception Type - Exception Type: usually includes Signal signal and EXC_BAD_ACCESS, NSRangeException, etc.
Exception Type | Possible causes | Debugging methods |
---|
EXC_CRASH | unrecognized selector | All Exception Point | EXC_BAD_ACCESS | Memory access error | NSZombie | SIGSEGV | Referenced a released object/referenced an uninitialized object/array out of bounds/attempted to write data to a memory address for which write permission is not available | NSZombie | SIGABRT | Crash caused by logical errors, such as trying to release the same unsaved | Logic Check | SIGPIPE | TCP suddenly disconnects and then sends data | Add signal (SIGPIPE, XX) |
For specific signal descriptions, see iOS exception capture (http://www.iosxxx.com/blog/2015-08-29-iosyi-chang-bu-huo.html) 2. Exception Code - Exception Code: Starts with some text, followed by one or more hexadecimal values. These values describe the nature of the crash.
- From the Exception Code, you can distinguish whether the crash is caused by a program error, illegal memory access, or other reasons. Common exception codes are as follows:
Exception Code | describe |
---|
0x8badf00d | ate bad food means that the app was terminated by iOS because of a watchdog timeout. This is usually because the app took too long to start, terminate, or respond to system events. | 0xdeadfa11 | dead fall, the user is forced to exit. | 0xbaaaaaad | The user holds down the Home button and the volume button to obtain the current memory status, which does not mean a crash. | 0xbad22222 | The VoIP application was terminated due to too frequent restarts | 0xc00010ff | cool off, because it was too hot to be dried | 0xdead10cc | Dead lock indicates that the application is occupying system resources (such as the address book database) because it is running in the background | 0xbbadbeef | bad beef, fatal error |
Note 1: For detailed meaning of abnormal codes, please refer to: Hexspeak Note 2: Closing a suspended app in the background task list will not generate a crash log. Once an app is suspended, it is reasonable to terminate it at any time. Therefore, no crash log will be generated. 4. Crash log symbolization 1. Overview The thread backtrace section is as follows: - 5 AppName 0x0000000100205280 0x0000000100028000 + 1954432
-
- 6 AppName 0x00000001002ae59c 0x0000000100028000 + 2647440
These two records include four columns: (take the first record as an example) - Frame number - 5 (the smaller the number, the later the occurrence time and the later the occurrence sequence, the better it is to identify the scope of the problem)
- The name of the binary library - in this case, AppName.
- The address of the calling method - in this case 0x0000000100205280.
- The fourth column is divided into two sub-columns, a base address and an offset. Here it is x0000000100028000 + 1954432, the first number points to the file, the second number points to the line of code in the file.
Note 1: The thread backtrace does not use method names and line numbers as we are used to, but hexadecimal addresses. Therefore, we need to convert these hexadecimal addresses into method names and line numbers before analyzing the crash. This process is called symbolization. Note 2: To symbolize the Crash log, you need to obtain the corresponding application binary file and the .dSYM file (symbol table) generated when the binary file is generated. They must match exactly. Otherwise, the log cannot be fully symbolized. Note 3: After Xcode compiles the project, it will get a dSYM file (symbol table) with the same name. The dSYM file (symbol table) is a transfer file that saves the hexadecimal function address mapping information. The symbols we debug will be included in this file, and a new dSYM file will be generated each time the project is compiled. It is located in the /Users//Library/Developer/Xcode/Archives directory. For each release version, it is necessary to save the corresponding Archives file. Note 4: Symbolication can be done using two Xcode commands: symbolicatecrash command + atos command 2. symbolicatecrash command 1) First find the location of the symbolicatecrash command - find /Applications - name symbolicatecrash -type f
-
- //Location of my native command: /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash
2) Find the xcarchive file corresponding to the online version. Find the .dSYM and .app files in it The path where xcarchive is located is generally in the /Users//Library/Developer/Xcode/Archives directory 3) Get the crash log file - The crash logs of online apps are obtained through the crash log collection service (the main source).
- You can also get the Crash log file from the real device. Click Window -> Devices, select your own machine, and then click View Device Logs. Right click to export the Crash file.
- All the obtained log files need to be symbolized.
4) Copy symbolicatecrash, .dSYM, .app, crash.crash to the same folder on the desktop 5) Check whether the UUIDs of the xx.app and xx.app.dSYM files and the crash file are consistent. To view the UUID of the xx.app file, enter the command in the terminal: - dwarfdump
View the UUID of the xx.app.dSYM file and enter the command in the terminal: - dwarfdump
View the Incident Identifier in the crash log (UUID of the crash file) 6) Use the command to generate a "crash file that can locate the problem" - //symbolreportXXX.crash is the symbolized file
-
- ./symbolicatecrash crashXXX.crash appName.app.dSYM > symbolreportXXX.crash
7) The symbolized thread backtrace information can help locate the problematic code line. Note: If you get an error like Error: "DEVELOPER_DIR" is not defined at ./symbolicatecrash… when executing the symbolicatecrash command, you can enter export DEVELOPER_DIR="/Applications/XCode.app/Contents/Developer" before executing the command. 3. atos command When symbolizing, you can also use the atos command. It is found that the crash on the armv7 processor cannot be symbolized using symbolicatecrash. 1)Put .dSYM, .app, and crash.crash in the same folder. 2) Know the UUID of the crash file: execute grep "AppName arm" *crash to get the result - crash1.crash:0x100040000 - 0x100e23fff +AppName arm64 /var/containers/Bundle/Application/55A4D641-847F-4D24-86E1-129B28461858/AppName.app/AppName
-
- crash2.crash:0x100060000 - 0x100e43fff +AppName arm64 /var/containers/Bundle/Application/3229ED68-8D19-406D-A3F5-EC0310C9DB7C/QAppName.app/AppName
-
- crash3.crash: 0x5000 - 0xce8fff +AppName armv7 /var/containers/Bundle/Application/C6BE271D-2EAC-42C0-8E72-4523F88C76B2/AppName.app/AppName
Among them, 0x100040000, 0x100060000, and 0x5000 are loading addresses (loadingAddress), and arm64 and armv7 are architecture values (architectureValue). These two values will be used later. 3) Then execute the atos command, the input is successful and enters the waiting state - xcrun atos -o appName.app.dSYM/Contents/Resources/DWARF/appName -l loadingAddress -arch architectureValue
4) Now enter the Crash address corresponding to the App to get the crash information. Example 1: - grep "AppName arm" *crash
-
- xcrun atos -o AppName.app.dSYM/Contents/Resources/DWARF/AppName -l 0x100040000 -arch arm64
Example 2: - grep "AppName arm" *crash
-
- xcrun atos -o AppName.app.dSYM/Contents/Resources/DWARF/AppName -l 0x5000 -arch armv7
5. Common Crashes There are some common crashes. Here are 5 common crashes. 1. Array Operations - Scenario 1: The index of data is out of bounds. This usually happens when using UITableView, because the cellForRowAtIndexPath proxy method is executed asynchronously. Once the dataSource of the UITableView object changes during the data loading process, an array out-of-bounds exception is likely to occur. In a multi-threaded scenario, the data in the list interface may change frequently, and this exception is likely to occur; when the data in the list interface does not change much, this exception is almost imperceptible. Solution: Before getting data from the array, check whether the index is correct.
- @implementation NSMutableArray (Safe)
-
-
-
- - (id)safeObjectAtIndex:(NSUInteger) index {
-
-
-
- if ( index < self. count ){
-
- return [self objectAtIndex: index ];
-
- } else {
-
- NSLog(@ "Warning: Array out of bounds!!!" );
-
- }
-
- return nil;
-
- }
-
-
-
- @ end
- Scenario 2: nil when adding data objects to an array Solution: Before adding an object to the array, determine whether it is nil
Note: Deletion and other operations on arrays are handled similarly, and data verification is required before array operations. 2. Crash in multithreading Generally, a multithreaded crash will receive a SIGSEGV signal, indicating an attempt to access memory not allocated to it, or an attempt to write data to a memory address for which it has no write permission. - Scenario 1: Update UI in child thread
Solution: Put UI update operations in the main thread, you can use performSelectorOnMainThread or GCD - //In the child thread, use the macro to dispatch the task of updating the UI to the main queue
-
- #define dispatch_main_sync_safe(block) \
-
- if ([NSThread isMainThread]) { \
-
- block(); \
-
- } else { \
-
- dispatch_sync(dispatch_get_main_queue(), block); \
-
- }
-
-
-
- #define dispatch_async_main(block) dispatch_async(dispatch_get_main_queue(), block)
- Scenario 2: Creating a singleton in multiple threads Solution: Use dispatch_once to ensure that the code is executed only once and to ensure thread safety.
- //Take QSAccountManager singleton as an example
-
- static QSAccountManager *_shareManager = nil;
-
- + (instancetype)shareManager{
-
-
-
- static dispatch_once_t once;
-
- dispatch_once(&once, ^{
-
- _shareManager = [[self alloc] init];
-
- });
-
- return _shareManager;
-
- }
-
-
-
- + (instancetype)allocWithZone:(struct _NSZone *)zone{
-
-
-
- static dispatch_once_t onceToken;
-
- dispatch_once(&onceToken, ^{
-
- _shareManager = [super allocWithZone:zone];
-
- });
-
- return _shareManager;
-
- }
-
-
-
- - (nonnull id)copyWithZone:(nullable NSZone *)zone{
-
- return _shareManager;
-
- }
- Scenario 3: Use of non-thread-safe classes in multithreading, such as NSMutableArray and NSMutableDictionary. Solution: Use dispatch queues or locks to ensure data read and write security. For detailed implementation, see the first part of iOS Record 12: Issues ignored in using NSMutableArray.
- Scenario 4: Data is cached to disk and read. Solution: Use dispatch queues or locks to ensure data read and write security. For example, asynchronously put data reading and writing into a serial synchronization queue to ensure data synchronization and thread safety.
3. Crash caused by WatchDog timeout - The general exception code is 0x8badf00d, which means that the application was terminated by iOS due to a watchdog timeout. Usually, the application takes too long to start, terminate, or respond to system events.
- Scenario 1: Time-consuming operations are performed in the main thread, causing the main thread to be stuck for a certain period of time. Solution: The main thread is only responsible for UI updates and responses, and time-consuming operations are asynchronously placed in the background thread for execution. Time-consuming operations include: network requests, database reads and writes, etc.
4. Crash under performSelector:withObject:afterDelay - Scenario 1: Object release is earlier than performSelector:afterDelay Solution: Execute cancelPreviousPerformRequestsWithTarget in dealloc of the corresponding class to cancel the execution.
5. Program exit caused by SIGPIPE - When the server closes a connection, if the client continues to send data, according to the TCP protocol, it will receive a RST response. When the client sends data to the server again, the system will send a SIGPIPE signal to the process, telling the process that the connection has been disconnected and not to write anymore. According to the default signal processing rules, the default execution action of the SIGPIPE signal is terminate, so the client will exit.
- Scenario: A long-connected socket or redirected pipe enters the background and is not closed. Solution 1: When switching to the background, close the long connection and pipe, return to the foreground and rebuild; Solution 2: Use signal (SIGPIPE, SIG_IGN) to hand SIGPIPE over to the system for processing. Doing so sets SIGPIPE to SIG_IGN, so that the client does not perform the default action, that is, does not exit.
|