WWDC 2018 Session 414: Understanding Crashes and Crash Logs

For more WWDC 18 articles, head over to the xSwiftGG WWDC 18 Topics directory

About the author: @vong, currently working in the United States film, like to toss ~

To err is human. Everyone makes some mistakes when writing code, so how do you debug and find them? Let’s follow apple engineers to learn how crashes occur and how to fix them.

1. Basic knowledge

What is a crash? A crash is when an application is trying to do something, and it stops unexpectedly.

1.1 Why do crashes Occur

The main reasons are as follows

  • Code that the CPU cannot execute.
  • “Forcibly killed” by the operating system, the system will forcibly terminate those applications that are too slow or consume too much memory for the sake of user experience.
  • A crash triggered by a programming language to prevent errors, such asNSArrayorSwift.ArrayCrossing the line
  • Crashes triggered by developers to prevent errors, such as assertions for non-null judgments

1.2 What does a crash look like

In 1.2.1 debugger

When we were debugging with Xcode, we had a crash that looked something like this.

When connected to the debugger, we can retrieve some call stacks and corresponding methods from the crash scene. When not connected to the debugger, the system stores the crash log to disk.

1.2.2 Crash logs

In general, crash logs for release apps are unsymbolized and only address is recorded in the logs. We can use Xcode to symbolize the crash log, resolving the file name, method name, and line number of the crash.

1.3 Obtaining Crash Logs

There are many ways to get crash logs. Let’s look at Xcode Organizer to get crash logs for apps downloaded from TestFlight or the App Store.

1.3.1 Organizer Window

Take a look at the chart below:

The numbers 1 to 6 in the following figure respectively represent the numbers 1 to 6 in the figure

  • 1. You can see that all platforms are published inApp StoreorTestFlightThe application of.
  • 2. In the crash log list, you can see the number of affected devices, corresponding platforms and extensions, as marked in the blue box in the figure.
  • 3. Highlight the call stack where the crash occurred and the crash location.
  • 4. In the corresponding project, open the file where the crash is located and go to the specified location to track the fault.
  • 5. Recent data analysis, including two dimensions of system and model.
  • 6. Page turning is supported when the number of crashes is large.

PS: The above 6 are just a brief introduction of the theme, and the rest can be explored and used by ourselves. Such as searching, taking some notes on individual logs, and marking fixed crashes as resolved.

How do I get the crash log from the Organizer? It’s easy. Just do the following steps

    1. inXcodeLogin to the paid developer account.
    1. Uploading an application toApp StoreTestFlight, and upload the symbol file.
    1. Open theXcode OrganizerWindow, selectCrashesTAB (Shortcut keys:Cmd+Shift+6).

1.3.2 Devices Window

Connect the Device, open Xcode, use the shortcut key Cmd+Shift+2 to open Devices Window, select the corresponding Device, and then select View Device Logs to View all crash files on the disk of the current Device, and find the corresponding Logs of the application for analysis.

Sometimes, the crash logs obtained are not symbolized. You’ll need to do a few extra things yourself, and here’s a tip I shared earlier in The trivia section: iOS Fast parsing crash logs.

1.3.3 Other ways

  • XcodeAutomated tests for the (resulting in symbolized logs)
  • Macbuilt-inConsoleApply, getMacOr emulator crash logs
  • IOS devices can obtain it through this operation. Open [Settings] -> [Privacy] -> [Analysis] -> [Analysis Data] to get the corresponding unsymbolized crash log, and then transfer it to the corresponding device for analysis through the system’s own sharing.

1.4 Symbolic best practices

  • Upload the app’s symbolic file so that Apple can symbolize the crash log directly in the background, and finally in theXcode OrganizerCrashesTAB.
  • Keep the application archive for local symbolization, as long as the archive is there,XcodeIt’s automatically symbolized.
  • inXcode OrganizerArchiveTAB: enabledbitcodeApp DownloaddSYMFile.

2. Analyze the crash logs

2.1 Composition of crash logs

  • Crash summary, mainly records some basic information, such as model, system version, crash time, etc
  • Collapse reason
  • Crash information (this part of the real machine is private for reasons that are generally not visible in the simulator andMacOSThe visible)
  • Crash the call stack of the thread
  • The call stack of other threads when a crash occurs
  • Register state
  • The loaded executable binary file

2.2 Analysis

Start with the crash type in the crash cause

The crash type EXC_BAD_INSTRUCTION shown above is “killed” when the CPU tries to execute a nonexistent or invalid piece of code.

We can then find the first few lines of the call stack of the crashed thread and analyze further with crash information (if any). Find the first line in the crash stack where the binary name of the application name is located, drill down to the number of lines of code corresponding to the file (such as the line highlighted in red above), and then analyze further. The crash in the figure above is clearly caused by forced unpacking of nil.

2.3 Crashes caused by assertions and prerequisites

The significance of assertions and prerequisites is to force the termination of the current process when an error occurs.

The crash of forced unpacking nil mentioned above is one of those assertions and prerequisites. They also include the following:

  • Data access out of bounds
  • Arithmetic overflow
  • An exception that is not caught
  • Custom assertions in code

2.4 Crash caused by “Killing” applications by the operating system

In some cases, the system kills some abnormal applications for protection purposes. The following scenarios may trigger the system to apply kill:

  • Watchdog event. Main thread is not responding for a long time
  • Excessive hot of equipment
  • Out of memory
  • The application signature is invalid. Procedure

Crash logs for crashes caused by the above scenarios can be viewed in the above mentioned Device Window. The Organizer Window does not necessarily collect these logs. Refer to Apple’s Understanding and Analyzing Application Crash Reports for more details.

Let’s start with an example of a watchdog.

The above crash type is EXC_CRASH (SIGKILL). SIGKILL generally indicates that the system terminates the process. This signal cannot be captured by the application and therefore cannot be processed. The reason for termination is Namespace SPRINGBOARD, Code 0x8BadF00D. If you have watched the crash log lecture mentioned above, you should know what Code 0x8BADF00D stands for. According to the termination description, it was due to a startup time exceeding 19.97 seconds.

This time we finally know why the guard dog corresponding code is 0x8badF00D, from the apple engineer’s pronunciation, this code is the same as ate bad food.

2.4.1 How to Avoid Startup Timeout

One of the most common reasons for application rejection is startup timeouts. So how can this be avoided? Apple engineers gave us these tips:

  • Test on a real machine, as the watchdog is disabled in the simulator and debugging phase
  • When tested on a low-performance device, high-performance devices respond quickly and fail to show the real effect

2.4.2 How to Avoid Memory Problems

Common memory errors include: overfree, wild Pointers (accessing freed objects), and out-of-bounds memory access (such as C arrays). Let’s use a log to analyze the specific problem.

As indicated by 1 in the figure above, we know that the crash type is EXC_BAD_ACCESS(SIGSEGV). This type of crash is mainly caused by two situations:

  • Write operations to read-only memory addresses
  • Access memory addresses that do not exist

By crashing objC_release, Object_Dispose, etc in the stack, we are more sure that this crash is due to memory problems. We know from these clues that the LoginViewController instance experienced a memory problem when it called the deinit method to destroy the related properties, causing a crash.

Back to the Exception Codes in the first section of the log, the Apple engineers say they can draw the conclusion based on experience and the relevant information in the log. The corresponding BAD_ADDRESS is 0x7FDD5e70700. The reason is that 0x7FDD5E70700 is in the address range MALLOC_TINY 00007FDD5E400000-00007FDD5E800000.

Some basics about memory and freeing

The memory layout for Objective-C objects, as well as some Swift objects, starts with ISA when an object is valid (not freed) and isa points to the class to which it belongs. Objc_release basically reads the isa pointer to the object and then dereferences the ISA pointer from Class.

Normally, everything works as usual. What happens if the object has already been freed? When the free function is called, the object is deleted and inserted into a list of other freed objects, with the previous ISA area pointing to the next freed object in the list.

When the former ISA memory area was written into a pointer to the Free list, it meant that a visit to the address returned an invalid memory address, leading to a crash. So when the Objc_release was removed from the ISA reference, it had access to the Free list, so the crash happened.

Therefore, it can be analyzed that a property must have been released when it was released. Can we know which attribute is causing it? The answer is yes.

So far __ivar_DESTROYER is a function that the compiler automatically generated for us based on the line that crashed, so we don’t know which line caused the problem. We only know that this class has the following three properties:

But you can get something from @objc LoginViewController.__ivar_DESTROYER +42, which represents the offset of this function in the assembly. We can disassemble the __ivar_DESTROYER function and see which attribute gets an offset of 42. LLDB debugging is possible in Xcode.

Command script import lldb.macosx.crashlog, crashlog /Users/… / ridesharingapp-2018-05-24-1. Crash, the following path needs to be replaced with your crash log path. Xcode automatically retrieves the binaries and corresponding dSYM files, which are then symbolized in the LLDB console. Then we find the crash address and execute the following command to get the corresponding disassembly code:

We do not need to understand the meaning of each line assembly. The comments at the end of each line can help us to understand. According to the comments, we can know that code 1, 2 and 3 represent the release of userName, database and views respectively. Going back to +42 mentioned above, we find the first line at 3. One thing to note is that most of the time the assembly’s offset address is the return address, so calling objc_Release is on the previous line. So you can determine that there was a problem with releasing the database. Although we do not know the specific problem at present, we can narrow the scope of finding the problem by using this information. We can find the place where the database is used to find the real problem.

2.4.2 Log Analysis Summary

  • Understand the cause of crash logs
  • Check crash stack information
  • Using disassembly helps us find more clues to analyzebad addressThe problem

2.4.3 Common Memory Errors

  • Objc_msgSend or retain/release crashes

  • Unrecognized method exception

  • abort() inside malloc/free

2.5 Log Analysis Suggestions

  • Instead of focusing on the one line of code where the crash occurred, take a look at the code related to the crash, such as the one above that didn’t actually cause itbugCause of occurrence
  • Look at all the call stacks, not just the one on the thread where the crash occurred, but the one on the non-crash thread can help us see the state of the application at the time of the crash
  • Check crash logs. Sometimes many crash logs crash in the same place, but some crash logs contain more information than others
  • useXcodeProvides tools to reproduce memory problems, such asAddress SanitizerorZombies

3. Multithreading

3.1 Some “symptoms” of multithreading problems in crash logs

  • A class of bugs that are most difficult to reproduce and diagnose
  • Multithreading problems often cause memory contention
  • Multiple threads execute similar code
  • The same bug may have different crash logs

3.2 Using Thread Sanitizer to detect multithreading problems

Even if we get the log with a high probability, we cannot analyze the problem. Even if we debug with Xcode, we may not be able to reproduce the problem stably. Even if we are lucky enough to reproduce the problem, we may not be able to analyze the specific problem. So to help us analyze this, we can use a tool provided by Xcode called Thread Sanitizer. Use the shortcut Cmd+ Shift +, and select Diagnostics TAB. Select Thread Sanitizer. As shown in the figure below

  • Can stably reproduce multithreading bug
  • It can also be done under the simulator
  • Only look for problems with code that is currently executing

3.3 Practical Suggestions

When creating GCD Queue, (NS)OperationQueue, and (NS)Thread, use custom names for subsequent debugging and viewing in crash logs.


let queue = DispatchQueue(label: "com.example.myapp.networking")

let operationQueue = OperationQueue()
operationQueue.name = "Networking OperationQueue"
 
let thread = Thread(...). thread.name ="Networking Thread"
Copy the code

3.4 Additional Suggestions

  • Use a real machine test
  • Try to reproduce, get the crash log from the user and try to reproduce the problem according to the call stack
  • Use tools to find bugs that are hard to reproduce. See the following two tools for more informationWWDC 2016 Session 412 Thread Sanitizer and Static Analysis
    • useAddress SanitizerTo check for memory problems
    • useThread SanitizerTo look at multithreading problems