Deep Understanding of iOS Crash Log

Posted by tcollie on Sat, 11 May 2019 17:39:20 +0200

Crash Log

There are two main sources of Crash Log:

  1. What Apple provides can be copied directly from the user's device or downloaded from iTunes Connect (XCode)
  2. Three-party or self-research Framework statistics, three-party services including Fabric, Bugly and so on.

The Crash Log mentioned in this article is provided by Apple.

Obtain

Equipment acquisition

USB Connect Devices, then select Windows - > Devices and Simulators in the XCode menu bar, and then select View Device Logs

Then, wait for XCode to copy Crash Log. In the upper right corner, you can search by App's name. For example, here I search for Wechat. You can export Crash Log to local analysis by right-clicking:

When viewing Crash Log, XCode will automatically try Symboliate, and what is Symboliate will be explained later in this article.

XCode Download

Select Window - > Organizer in the XCode menu bar and switch to Crashes Tab. After selecting the version, you can automatically download the corresponding version of crash log:

Select Open In Project, then select the corresponding project, and then the familiar interface in our daily development:

Analysis

For Demo, Crash Log is a Wechat:

  • WeChat-2018-6-11-21-54.crash
  • Device information: iPhone 7, iOS 12 beta 1
  • Version information: Wechat 6.6.7.32 (6.6.7)

Header

Crash Log starts with a header, which contains metadata for the log:

//Unique identifier of crash log
Incident Identifier: 4F85AD99-CF91-4240-BBC7-AEAFA51ED7FC 
//The crash log of the same device is the same as the processed device identifier.
CrashReporter Key:   c84934ca1eae8ba4209ce4725a52492c77d05add
Hardware Model:      iPhone9,1
Process:             WeChat [31763]
Path:                /private/var/containers/Bundle/Application/11F1F5DE-2F68-4331-A107-FAADCED42A1F/WeChat.app/WeChat
Identifier:          com.tencent.xin
Version:             6.6.7.32 (6.6.7)
Code Type:           ARM-64 (Native)
Role:                Non UI
Parent Process:      launchd [1]
Coalition:           com.tencent.xin [12577]


Date/Time:           2018-06-11 21:54:07.2673 +0800
Launch Time:         2018-06-11 21:53:55.2690 +0800
OS Version:          iPhone OS 11.3 (15E216)
Baseband Version:    3.66.00
Report Version:      104

Reason

Then there is the Cause of Collapse Module:

Exception Type:  EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d
Termination Description: SPRINGBOARD, scene-create watchdog transgression: com.tencent.xin exhausted CPU time allowance of 2.38 seconds |  | ProcessVisibility: Background | ProcessState: Running | WatchdogEvent: scene-create | WatchdogVisibility: Background | WatchdogCPUStatistics: ( | "Elapsed total CPU time (seconds): 23.520 (user 23.520, system 0.000), 100% CPU", | "Elapsed application CPU time (seconds): 5.151, 22% CPU" | )
Triggered by Thread:  0

Exception Type denotes the type of exception:

Exception Type:  EXC_CRASH (SIGKILL)

In <mach/exception_types.h>, we can find the specific meaning of EXC_CRASH: abnormal process exit.

#define EXC_CRASH       10  /* Abnormal process exit */

So what does SIGKILL mean? In the header file <sys/signal.h>, you can find:

#define SIGKILL 9   /* kill (cannot be caught or ignored) */

This means that this is an exception that cannot be captured and ignored, so the system decides to kill the process.

The code in Exception Note can also be found in <mach/exception_types.h>.

#define EXC_CORPSE_NOTIFY   13  /* Abnormal process exited to corpse state */

The information provided by Termination Reason is more detailed.

Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d

0x8badf00d is a very common Code, which means that App starts too long or the main thread gets stuck too long, resulting in the system's WatchDog killing the current App.

Thread

Next is the call stack of each thread, and the crashed thread is marked as crashed. For example, the call stack of the main thread is as follows:

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libobjc.A.dylib                 0x0000000184475da8 0x184464000 + 73128
1   libobjc.A.dylib                 0x0000000184475aa8 0x184464000 + 
...
7   WeChat                          0x00000001031f64d4 0x100490000 + 47604948
8   WeChat                          0x0000000102e74a5c 0x100490000 + 43928156
9   WeChat                          0x0000000102e71a14 0x100490000 + 43915796
10  Foundation                      0x0000000185c52d1c 0x185be5000 + 449820
...
16  WeChat                          0x00000001029d0924 0x100490000 + 39061796
...
37  WeChat                          0x00000001005d7e18 0x100490000 + 1343000
38  libdyld.dylib                   0x0000000184c09fc0 0x184c09000 + 4032

You can see that the descriptive information here is addressed 0x0000102e74a5c 0x100490000 + 43928156. We can only find the problem by converting them into classes / methods in the code, which is what we will talk about next.

register

After a stack of thread calls, you can also see the register status of Crash:

Thread 0 crashed with ARM Thread State (64-bit):
    x0: 0x00000001b76acea0   x1: 0x000000018fbd3fbd   x2: 0x000000010cb17260   x3: 0x0000000000000001
    x4: 0x0000000000000000   x5: 0x0000000000000001   x6: 0x0000000000000020   x7: 0x0000000000000004
    x8: 0x0000000109a34380   x9: 0x0000000109a34310  x10: 0x0000000109a34311  x11: 0x0000000109a34318
   x12: 0x000000010c8e3cb0  x13: 0x0000000000000000  x14: 0x0000000000000000  x15: 0x000000018fbd49dd
   x16: 0x00000001b76acea0  x17: 0x0000000000000000  x18: 0x0000000000000000  x19: 0x000000018fbd3fbd
   x20: 0x0000000109a34318  x21: 0x0000000109a34388  x22: 0x00000001b766cfd0  x23: 0x0000000000000000
   x24: 0x00000001b76acea0  x25: 0x0000000000000000  x26: 0x00000001b766e000  x27: 0x00000000ffed8282
   x28: 0x0000000000000000   fp: 0x000000016f969e90   lr: 0x0000000184475aa8
    sp: 0x000000016f969e70   pc: 0x0000000184475da8 cpsr: 0x80000000

Executable file

Crash Log ends with an executable file, where you can see the dynamic libraries loaded at that time.

Binary Images:
0x100490000 - 0x103cabfff WeChat arm64  <6499420763bf3621abf3f6218adc6354> /var/containers/Bundle/Application/11F1F5DE-2F68-4331-A107-FAADCED42A1F/WeChat.app/WeChat
0x104ce8000 - 0x104e1ffff MMCommon arm64  <85b8839214673db29e3b6a4eeaaacba7> /var/containers/Bundle/Application/11F1F5DE-2F68-4331-A107-FAADCED42A1F/WeChat.app/Frameworks/MMCommon.framework/MMCommon
0x104e68000 - 0x104ea3fff dyld arm64  <06dc98224ae03573bf72c78810c81a78> /usr/lib/dyld
0x104efc000 - 0x1051bbfff TXLiteAVSDK_Smart_No_VOD arm64  <94b2ab6b3c863923b321327155770286> /var/containers/Bundle/Application/11F1F5DE-2F68-4331-A107-FAADCED42A1F/WeChat.app/Frameworks/TXLiteAVSDK_Smart_No_VOD.framework/TXLiteAVSDK_Smart_No_VOD
0x1055e4000 - 0x10572ffff WCDB arm64  <c1b1509046923a93b29755fe25526e00> /var/containers/Bundle/Application/11F1F5DE-2F68-4331-A107-FAADCED42A1F/WeChat.app/Frameworks/WCDB.framework/WCDB
0x10587c000 - 0x105c7bfff MultiMedia arm64  <b456f7d1d8ba3eadb83d84d9e9eed783> /var/containers/Bundle/Application/11F1F5DE-2F68-4331-A107-FAADCED42A1F/WeChat.app/Frameworks/MultiMedia.framework/MultiMedia
0x105f8c000 - 0x106147fff QMapKit arm64  <682efa309eed33ce894bd1383988e38a> /var/containers/Bundle/Application/11F1F5DE-2F68-4331-A107-FAADCED42A1F/WeChat.app/Frameworks/QMapKit.framework/QMapKit
...

Symbolication

The crash log function stack we just got:

...
7   WeChat                          0x00000001031f64d4 0x100490000 + 47604948
8   WeChat                          0x0000000102e74a5c 0x100490000 + 43928156
9   WeChat                          0x0000000102e71a14 0x100490000 + 43915796

As you can see, these addresses don't actually provide us with any useful information. We need to convert them into classes / functions to find problems. This process is called symbolization.

Symbolization requires one thing: Debug Symbol files, which we often call dsym files.

Machine instructions usually correspond to a line of code in your source file. When compiled, the compiler generates information about this mapping relationship. According to the DEBUG_INFORMATION_FORMAT settings in build setting s, this information may exist in binary files or dsym files.

Note that the binary file in crash log will have a unique UUID and the dsym file will have a unique uuid. The UUID of the two files can be symbolized only when they are corresponding.

If you choose to upload dsym files when uploading to App Store, the crash logs you see in XCode are automatically symbolized.

BitCode

When a project opens BitCode, the compiler does not generate machine code, but an intermediate code called bitcode. When uploaded to the App Store, does this bitCode compile into a machine?

So, the question arises. The final compilation process is uncontrollable to you. How do you get dsym files?

The answer is that Apple will generate this dsym file, which you can download from XCode or iTunes Connect.

Download from XCode: Window - > Orginizer - > Archives - > Select Build Version - > Download dSYMs

Download from iTunes Connect

Manual Symbolization

uuid

In crash log, you can see the uuid corresponding to image (executable file).

You can also use grep to quickly find uuid

$ grep --after-context=1000 "Binary Images:" <Path to Crash Report> | grep <Binary Name>

Next, let's look at dsym's uuid:

xcrun dwarfdump --uuid <Path to dSYM file>

Only when two UUIDs correspond to each other can the symbolization be successful.

XCode

XCode automatically attempts to symbolize Crash Log (requiring the file to end with. crash)

  1. USB Connection Device
  2. Open XCode, menu bar device - > Window
  3. Choose a device
  4. Point View Device Logs
  5. Then drag your crash log to the left
  6. XCode automatically symbolizes

To automatically symbolize XCode, you need to be able to find the following files:

  • Crashed executable files and dsym files
  • dsym files for all framework s used
  • OS version related symbols (XCode automatically copies these symbols to the device when connected to USB)

atos

atos is a command-line tool that can be used to symbolize a single address in the following format:

atos -arch <Binary Architecture> -o <Path to dSYM file>/Contents/Resources/DWARF/<binary image name> -l <load address> <address to symbolicate>

Give an example:

$ atos -arch arm64 -o TheElements.app.dSYM/Contents/Resources/DWARF/TheElements -l 0x1000e4000 0x00000001000effdc
-[AtomicElementViewController myTransitionDidStop:finished:context:]

symbolicatecrash

Symbolic tecrash is XCode's built-in tool for symbolizing the entire Crash Log

cd /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources
./symbolicatecrash ~/Desktop/1.crash ~/Desktop/1.dSYM > ~/Desktop/result.crash

If you make a mistake

Error: "DEVELOPER_DIR" is not defined at ./symbolicatecrash line 60

Environmental variables can be introduced to solve this problem.

export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer

lldb

Suppose you have a crashlog stack like this

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
...
0   libobjc.A.dylib 0x00007fff6011713c objc_release + 28
1   RideSharingApp 0x00000001000022ea @objc LoginViewController.__ivar_destroyer + 42

By calling the stack, we know that crash is caused when the ivar of LoginViewController is released, and LoginViewController has many attributes. Which one is released that causes crash?

We can look at the assembly code through lldb to find some clues:

First, open the terminal and import the crashlog tool

LeodeMacbook:Desktop Leo$ lldb
(lldb) command script import lldb.macosx.crashlog
"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help
"malloc_info", "ptr_refs", "cstr_refs", "find_variable", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.

Next, we can use a series of commands provided by this script.

Load Crash log

crashlog /Users/…/RideSharingApp-2018-05-24-1.crash
...
Thread[0] EXC_BAD_ACCESS (SIGSEGV) (0x000007fdd5e70700)
[ 0] 0x00007fff6011713c libobjc.A.dylib objc_release + 28
[ 1] 0x00000001000022ea RideSharingApp @objc LoginViewController.__ivar_destroyer + 42
[ 2] 0x00007fff6011ed66 libobjc.A.dylib object_cxxDestructFromClass + 127
[ 3] 0x00007fff60117276 libobjc.A.dylib objc_destructInstance + 76
[ 4] 0x00007fff60117218 libobjc.A.dylib object_dispose + 22
[ 5] 0x0000000100002493 RideSharingApp Initialize (main.swift:33)
[ 6] 0x0000000100001e75 RideSharingApp main (main.swift:37)
[ 7] 0x00007fff610a2ee1 libdyld.dylib start + 1

Then, look at the assembly code:

(lldb) disassemble -a 0x00000001000022ea
RideSharingApp`@objc LoginViewController.__ivar_destroyer:
0x1000022c0 <+0>: pushq %rbp
0x1000022c1 <+1>: movq %rsp, %rbp
0x1000022c4 <+4>: pushq %rbp
0x1000022c4 <+4>: pushq %rbx
0x1000022c5 <+5>: pushq %rax
0x1000022c6 <+6>: movq %rdi, %rbx 
0x1000022c9 <+9>: movq 0x551e40(%rip), %rax      ; direct field offset for LoginViewController.userName
0x1000022d0 <+16>: movq 0x10(%rbx,%rax), %rdi
0x1000022d5 <+21>: callq 0x1004adc90             ; swift_unknownRelease
0x1000022da <+26>: movq 0x551e37(%rip), %rax     ; direct field offset for LoginViewController.database
0x1000022e1 <+33>: movq (%rbx,%rax), %rdi
0x1000022e5 <+37>: callq 0x1004bf9e6             ; symbol stub for: objc_release
0x1000022ea <+42>: movq 0x551e2f(%rip), %rax     ; direct field offset for LoginViewController.views
0x1000022f1 <+49>: movq (%rbx,%rax), %rdi
0x1000022f5 <+53>: addq $0x8, %rsp
0x1000022f9 <+57>: popq %rbx
0x1000022fa <+58>: popq %rbp
0x1000022fb <+59>: jmp 0x1004adec0               ; swift_bridgeObjectRelease

We see that the address of this line is our crash's symbolic address:

0x1000022ea <+42>: movq 0x551e2f(%rip), %rax     ; direct field offset for LoginViewController.views

But the PC register always saves the next instruction to execute, so the actual crash should be the last instruction.

0x1000022da <+26>: movq 0x551e37(%rip), %rax     ; direct field offset for LoginViewController.database
0x1000022e1 <+33>: movq (%rbx,%rax), %rdi
0x1000022e5 <+37>: callq 0x1004bf9e6             ; symbol stub for: objc_release

It is not difficult to see from the comments behind the assembly code that the problem lies in the attribute database.

Common Code and Debug Techniques

EXC_BAD_ACCESS/SIGSEGV/SIGBUS

All three are memory access errors, such as array crossing, accessing an OC object that has been released, trying to write to the readonly address, and so on. This error usually finds some details of the error address in Exception's Subtype.

When debugging, you need to observe the context of the call stack:

  1. If you see objc_msgSend and objc_release in the context, it is often an attempt to send a message to a released Objective C object, which can be debugged with Zombies.
  2. Multithreading can also be the cause of memory problems. Open Address Sanitizer and let it help you find multithreaded DataRace.

EXC_CRASH/SIGABRT

These two Code s represent the exit of process exceptions, most commonly some of which are not handled by Objective C/C++ exceptions.

App Extensions can also occur if it takes too much time to initialize and is killed by watchdog.

EXC_BREAKPOINT/SIGTRAP

Similar to process exception exit, this exception is trying to tell the debugger that this exception has occurred, and if there is no debugger attachment at present, the process will be killed.

This exception can be manually set out in the code by _builtin_trap().

This Crash is common in iOS underlying frameworks, most commonly GCD, such as dispatch_group

Crashed: com.apple.main-thread
0  libdispatch.dylib              0x18316fae4 dispatch_group_leave$VARIANT$mp + 76
2  libdispatch.dylib              0x18316cb24 _dispatch_call_block_and_release + 24

Swfit code also has this exception in the following cases:

  • Assign nil to a non-optional value type
  • Failed Mandatory Type Conversion

Killed [SIGKILL]

Processes are forced to be killed by the system. Usually, Termination Reason can find the reason for the killing:

  • 0x8badf00d indicates a watch dog timeout, usually when the main thread is stuck or the startup time exceeds 20s.

data

Topics: xcode Database iOS Swift