Most iOS apps encapsulate critical tasks into Background tasks once they are in the Background, otherwise the program is suspended after a few seconds. After starting Background Task, you get 3 minutes to continue executing the code.

Recently, I investigated the Background Crash problem in Messenger, and finally traced it to Background Task. I would like to share some key points with you.

Crash signal

Most apps have their own crash log collection tools, which generally have three problems. First, crash logs before the startup of the tool cannot be captured; second, if the App starts and blinks, logs cannot be uploaded; third, crash signals cannot be captured in some special scenarios.

Solve the first problem by making the execution time of the tool as early as possible, or by making the previous code as simple and reliable as possible.

To solve the second problem, we can use the background mode using NSURLSession that I shared earlier.

To solve the third problem, we need to rely on Apple’s own crash signal, which is also ignored by many development teams.

Apple also has its own crash log collection, but based on the consideration of user privacy, this crash log is not reliable and has the following defects:

  • Users need to agree to upload and share data. According to reports, the proportion of consent is less than 20%, so it is impossible to accurately determine the actual impact surface of a crash.

  • The crash log tool is simple, open Xcode -> Organizer, select App, you can download a version of the crash log from Apple background, cannot filter by certain conditions, such as you cannot filter out all SIGKILL logs.

  • The log is incomplete, and Apple presents crash samples according to its own rules. There are many crash samples online in an App, but Apple lists only dozens of crash samples, and the rules are unclear.

  • Crash logs are only saved for a week and refreshed once a week, so it is wise to write a script to synchronize them and upload them to your background.

Background Task Fancy crash

The Background Task API is extremely simple. The code between begin and end falls into the category of the Background Task. However, simple code can hide a lot of risks. Here are three relatively easy crashes. In addition, the crash collection tool of the client cannot capture the three crashes, and signals can only be obtained from Apple’s crash logs. The reason is very simple. When these crashes occur, APP is generally in the suspend state, and there is no chance to execute any code. After the system directly sends the SIGKILL signal, app is forcibly killed and a system log is generated, which can only be accessed by Apple. You have to agree to share it.

0xdead10cc

A crash log would look like this:

Exception Type:  EXC_CRASH (SIGKILL)

Exception Codes: 0x0000000000000000, 0x0000000000000000

Exception Note:  EXC_CORPSE_NOTIFY

Termination Reason: Namespace SPRINGBOARD, Code 0xdead10cc

Termination Description: SPRINGBOARD, com.xxx.xxx was task-suspended with locked system file

As I mentioned earlier, if your App has Extension and you need to share data with your Host App, the common practice is to put the DB file in the shared container directory. There’s a high probability that your App will crash.

App enters the Background and runs the Background Task. After the end, App is suspended by the system. If there is any operation to access DB after suspend, App will be immediately killed by the system, which is Apple’s consideration to protect the integrity of database files.

Therefore, the correct approach is to seal all DB operations that may occur after the App enters the Background into the Background Task to ensure security. This code is probably more appropriate at the DB Layer.

In addition, Apple recommends that when you want to start the Background Task, you do not need to consider whether the current App is foreground or Background. Even if the App starts the Background Task in foreground, It doesn’t take up 3 minutes after entering the Background, so feel free to put the key code into the Background Task.

0xbada5e47

When you follow the advice above and seal as much key code as possible into a Background Task, you may encounter the following crash:

Exception Type:  EXC_CRASH (SIGKILL)

Exception Codes: 0x0000000000000000, 0x0000000000000000

Exception Note:  EXC_CORPSE_NOTIFY

Termination Reason: Namespace ASSERTIOND, Code 0xbada5e47

Also related to Background tasks, because Apple thinks you have started too many Background tasks, so kill them. How much is too much? The current threshold is 1000. If the threshold exceeds 1000, it will be forcibly killed. If your Background Task encapsulates at the DB layer, it is still possible to hit the limit when a large amount of data comes in and needs to be stored or read.

Another possible reason for 0xbada5e47 is that Background Task calls expiry Handler after expiry, and whatever the number of Background tasks you have, all expiry handler cannot be executed for more than a few seconds, If they do, they’ll be shot. So, in expiry Handler, don’t do anything time-consuming like disk IO.

0x8badf00d

0x8BadF00D = 0x8BADF00D = 0x8BADF00D = 0x8BADF00D = 0x8BADF00D = 0x8BADF00D

A Background Task can also be 0x8BADF00D, for example:

Exception Type:  EXC_CRASH (SIGKILL)

Exception Codes: 0x0000000000000000, 0x0000000000000000

Exception Note:  EXC_CORPSE_NOTIFY

Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d

The leaked Background Task is set for the system to crash. The leaked Background Task is set for the system to crash. Look at the code:

- (void)startBgTask{ self.bgTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ NSLog(@"Expired: %lu", (unsigned long)self.bgTaskID); [[UIApplication sharedApplication] endBackgroundTask:self.bgTaskID]; }]; }- (void)endBgTask{ [[UIApplication sharedApplication] endBackgroundTask:self.bgTaskID]; }Copy the code

Self. bgTaskID is given a leaked Background TaskID for the second time, and the TaskID is lost.

0x8BadF00D specifies whether the Background Task is leaked or the main stream is dead. It’s easy. Look at the stack of the main thread, if it looks like this:

Thread 0 Crashed:0  

libsystem_kernel.dylib         0x000000018472be08 0x18472b000 + 35921  

libsystem_kernel.dylib         0x000000018472bc80 0x18472b000 + 32002  

CoreFoundation                 0x0000000184c6ee40 0x184b81000 + 9744003  

CoreFoundation                 0x0000000184c6c908 0x184b81000 + 9648724  

CoreFoundation                 0x0000000184b8cda8 0x184b81000 + 485525  

GraphicsServices               0x0000000186b6f020 0x186b64000 + 450886  

UIKit                         0x000000018eb6d78c 0x18e850000 + 32664447  

Messenger                     0x0000000103015ee4 0x102ff8000 + 1225968   libdyld.dylib                 0x000000018461dfc0 0x18461d000 + 4032

The stack is a classic example of a stack that doesn’t need a symbolicate to know what it’s doing. This is the stack where the UI thread runloop is idle, waiting for a message from the kernel. The UI thread is in the idle state, which is likely to be killed by Background Task.

Make good use of local crash logs

When a user’s mobile phone encounters a crash and you can neither reproduce it nor find the crash log in the background, your best hope is the local crash log of the mobile phone.

The local log is located in Settings -> Privacy -> Analytics -> Analytics Data. Take a look. Maybe there are some crash logs of the App you developed. There are many logs on wechat and Alipay on my mobile phone.

The logs are sorted first by App name and then by the date the logs occurred.

If you are investigating crashes that use too much memory, look at the logs starting with JetsamEvent-xxx.

If you want to know the abnormal logs of the system before the App crash, you need to install a loggingios. mobileconfig file on the device first, which basically allows the user to authorize you to record the system behavior. When the user meets the crash, After pressing both volume and power buttons and releasing the vibration, the system records critical logs of a past period of time, which are very helpful for analyzing difficult cases. Such logs usually start with sysDiagnose_XXX.

Once the loggingios. mobileconfig file is installed, Apple will record more and more detailed crash logs, because the user is authorized, so Apple can do it. If stacks + Appname-date.ips is displayed, the log file name is stacks + Appname-date.ips.

If the user’s device can reproduce the problem you are investigating, another simple and efficient solution is to connect the USB phone to the MAC, and then start the Console App on the MAC. You can see all key system logs visually. For example, you can view the nSURlsessiond network exception log. Locate the exception log view locationd, Background Task exception log view Assertiond, or filter it by the process name of your app to view the life cycle and the cause of being forcibly killed.

conclusion

The above are some knowledge points about the recent investigation of Background Task crash, hoping to help you.