iOS崩溃日志分析

环境

macOS Mojave 10.14.3
Xcode10.1
iPhone6S 10.0.1

获得崩溃日志

方式一:Xcode

菜单 > Window > Devices and Simulators

选择设备并查看设备日志(View Device Logs)

方式二:手机

设置 > 隐私 > 诊断与用量 > 诊断与用量数据

崩溃日志的结构

头部(Header)

  • Incident Identifier: 崩溃日志的唯一标识
  • CrashReporter Key: 匿名的设备标识
  • Process:进程名
  • Path:二进制文件路径
  • Identifier:包名
  • Version:应用版本号
  • Code Type: 目标架构
  • Role:进程终止时进程被赋予的task_role枚举值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Incident Identifier: F21EC10C-018D-494F-997D-E9C9D82B2F7F
CrashReporter Key: ab7fcdf6938542fd82446362e0481f7fddba141f
Hardware Model: iPhone6,2
Process: JACrash [27195]
Path: /private/var/containers/Bundle/Application/EDDDCB8E-75CE-4638-84CA-5B7971F6EFE0/JACrash.app/JACrash
Identifier: com.ishepherdme.JACrash
Version: 1 (1.0)
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd [1]
Coalition: com.ishepherdme.JACrash [4182]


Date/Time: 2019-02-08 13:03:58.7771 +0800
Launch Time: 2019-02-08 13:03:58.3811 +0800
OS Version: iPhone OS 10.0.1 (14A403)
Report Version: 104

异常信息(Exception Information)

  • Triggered by Thread: 产生崩溃的线程
  • Exception Type: 崩溃类型
1
2
3
4
Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Triggered by Thread: 0

其他诊断信息

  • Application Specific Information:在进程终止时框架捕获的错误信息
1
2
3
4
5
Application Specific Information:
abort() called

Filtered syslog:
None found

线程回溯

罗列崩溃时每个线程的调用栈情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 libsystem_kernel.dylib 0x000000018af0e014 __pthread_kill + 8
1 libsystem_pthread.dylib 0x000000018afd5460 pthread_kill + 112
2 libsystem_c.dylib 0x000000018ae823f4 abort + 140
3 libc++abi.dylib 0x000000018a94d2d4 __cxa_bad_cast + 0
4 libc++abi.dylib 0x000000018a96acc0 default_unexpected_handler+ 126144 () + 0
5 libobjc.A.dylib 0x000000018a978844 _objc_terminate+ 34884 () + 124
6 libc++abi.dylib 0x000000018a96766c std::__terminate(void (*)+ 112236 ()) + 16
7 libc++abi.dylib 0x000000018a967234 __cxa_rethrow + 144
8 libobjc.A.dylib 0x000000018a97871c objc_exception_rethrow + 44
9 CoreFoundation 0x000000018be1a0bc CFRunLoopRunSpecific + 560
10 UIKit 0x0000000191df37cc -[UIApplication _run] + 608
11 UIKit 0x0000000191dee550 UIApplicationMain + 208
12 JACrash 0x000000010007de90 0x100078000 + 24208
13 libdyld.dylib 0x000000018adfc5b8 start + 4

线程状态

列出崩溃线程的线程状态。这是一个寄存器列表及其执行停止时的值。在阅读崩溃报告时,无需了解线程状态,但可以使用此信息更好地了解崩溃的情况。

1
2
3
4
5
6
7
8
9
10
Thread 0 crashed with ARM Thread State (64-bit):
x0: 0x0000000000000000 x1: 0x0000000000000000 x2: 0x0000000000000000 x3: 0x00000001740e9737
x4: 0x000000018a96bbc3 x5: 0x000000016fd87520 x6: 0x000000000000006e x7: 0xffffffffffffffec
x8: 0x0000000008000000 x9: 0x0000000004000000 x10: 0x000000000000000b x11: 0x0000000000000010
x12: 0x000000018ae99792 x13: 0x0000000000000000 x14: 0x0000030000000300 x15: 0x0000000000000000
x16: 0x0000000000000148 x17: 0x0000000000000000 x18: 0x0000000000000000 x19: 0x0000000000000006
x20: 0x00000001b0bf8c40 x21: 0x000000016fd87520 x22: 0x00000001b0bffbb8 x23: 0x0000000000000001
x24: 0x0000000170011110 x25: 0x0000000000000000 x26: 0x0000000000000001 x27: 0x0000000000000000
x28: 0x000000016fd87b80 fp: 0x000000016fd87480 lr: 0x000000018afd5460
sp: 0x000000016fd87460 pc: 0x000000018af0e014 cpsr: 0x00000000

二进制镜像

进程二进制文件加载的地址区间,动态库加载的地址区间

1
2
3
4
5
6
7
Binary Images:
0x100078000 - 0x10007ffff JACrash arm64 <2fd461c16e7239489a037c6c1141c134> /var/containers/Bundle/Application/A55333A1-CD62-41A2-AE96-F3411ECD3447/JACrash.app/JACrash
0x10009c000 - 0x10009ffff MobileSubstrate.dylib arm64 <3134cfb2f722310ea2c742ae4dc131ab> /Library/MobileSubstrate/MobileSubstrate.dylib
0x1000a8000 - 0x1000affff libswiftCoreFoundation.dylib arm64 <47deeaa1967b3e339dfcb48535a994e1> /var/containers/Bundle/Application/A55333A1-CD62-41A2-AE96-F3411ECD3447/JACrash.app/Frameworks/libswiftCoreFoundation.dylib
0x1000bc000 - 0x1000cbfff libswiftCoreGraphics.dylib arm64 <25fe91aa901b3cf6a443b498012ab5af> /var/containers/Bundle/Application/A55333A1-CD62-41A2-AE96-F3411ECD3447/JACrash.app/Frameworks/libswiftCoreGraphics.dylib
0x1000f0000 - 0x1000f7fff libswiftCoreImage.dylib arm64
....

关于异常类型(Exception Type)

野指针(Bad Memory Access [EXC_BAD_ACCESS // SIGSEGV // SIGBUS])

尝试访问了非法的内存地址,或访问了受保护的地址

1
2
3
4
5
6
Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x000000064229beb8
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [0]
Triggered by Thread: 0

比如在MRC环境下尝试发送消息给已释放的对象,调用栈的特点是objc_msgSend/objc_release在调用栈顶

异常退出(Abnormal Exit [EXC_CRASH // SIGABRT])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
[self callCrashMethod];
}

- (void)callCrashMethod {
[self arrayOutOfBounds];
}

- (void)arrayOutOfBounds {
NSArray *arr = @[@"0",@"1"];
NSLog(@"%@",arr[2]);
}

@end
1
2
3
4
Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Triggered by Thread: 0

崩溃会直接到main,因为调用栈中包含objc_exception_rethrow栈帧,根据参考1,进行如下的配置,设置异常的断点

The Exception Breakpoint
So how do you find the line in the code that made the app crash? Well, whenever you get a stacktrace like this, an exception was thrown by the app. (You can tell because one of the functions in the call stack is named objc_exception_rethrow.)

追踪陷阱(Trace Trap [EXC_BREAKPOINT // SIGTRAP])

这个异常是为调试器提供在进程执行的特定点中断进程的机会。

1
2
3
4
5
6
Exception Type:  EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x000000010006e588
Termination Signal: Trace/BPT trap: 5
Termination Reason: Namespace SIGNAL, Code 0x5
Terminating Process: exc handler [0]
Triggered by Thread: 0

非法指令(Illegal Instruction)

进程试图执行非法或未定义指令。这个进程可能试图通过一个配置错误的函数指针,跳到一个无效的地址。

1
2
3
4
5
6
7
Exception Type:  EXC_CRASH (SIGILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Termination Signal: Illegal instruction: 4
Termination Reason: Namespace SIGNAL, Code 0x4
Terminating Process: neteasemusic [6562]
Triggered by Thread: 0

被杀死(Killed[SIGKILL])

1
2
3
4
5
Exception Type:  EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d
Triggered by Thread: 0

资源限制(Resource Limit [EXC_RESOURCE])

这个进程超出了资源消耗的限制。

符号化

来自参考3

方式一:dSYM文件

DWARFDebugging with Attributed Record Formats,Xcode使用它来生成符号表,生成的符号表放在.dSYM文件中,在Debug模式下,默认不会生成.dSYM文件,可以在Build Settings中进行设置

编译后,在工程中Product目录下,选择应用程序,Show in Finder,会发现生成了应用的.dSYM文件

获取UUID

1
2
Binary Images:
0x1000f8000 - 0x1000fffff JACrash arm64 <2fd461c16e7239489a037c6c1141c134> /var/containers/Bundle/Application/7B72F18F-930E-42CB-B69C-FED75161A03F/JACrash.app/JACrash

UUID:2fd461c16e7239489a037c6c1141c134

1
2
3
% dwarfdump --uuid path/to/Your.app.dSYM/Contents/Resources/DWARF/JACrash
...
UUID: 2FD461C1-6E72-3948-9A03-7C6C1141C134 (arm64) /Users/Jason/Library/Developer/Xcode/DerivedData/JACrash-bntjxfwsvrraeuevozrbvraorxqv/Build/Products/Debug-iphoneos/JACrash.app.dSYM/Contents/Resources/DWARF/JACrash

两者UDID一致即可

定位崩溃地址

1
2
3
% atos -o path/to/Your.app.dSYM/Contents/Resources/DWARF/Your -arch arm64 -l 0x1000f8000 0x1000fdda8

-[ViewController arrayOutOfBounds] (in JACrash) (ViewController.m:33)

0x1000f8000: 运行时应用基地址
0x1000fdda8: 运行时崩溃地址(待符号化的地址)

支持bitcode

支持bitcode的情况下,因为最终的二进制文件由Apple Store后台生成,本地是没有对应的.dSYM文件,可以从XcodeiTunes Connect上下载

方式二:崩溃日志 + Hopper

如果没有.dSYM文件,但有崩溃日志的情况时

崩溃地址 = 基地址 + ASLR + 方法偏移地址

方法相对基地址的偏移地址是不变的,因此只要把ASLR从崩溃地址中移除,再加上方法的偏移地址即可

hopper找出基地址,再加上方法偏移地址

Snip20190208_26

异常捕获监听

1
2
3
4
5
/// 函数指针,用于自定义捕获异常后要进行的操作
typedef void NSUncaughtExceptionHandler(NSException *exception);

FOUNDATION_EXPORT NSUncaughtExceptionHandler * _Nullable NSGetUncaughtExceptionHandler(void);
FOUNDATION_EXPORT void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler * _Nullable);

在程序启动时加上一个异常捕获监听,用来处理程序崩溃时的回调动作

1
2
3
4
5
6
7
8
9
10
11
12
void exceptionHandler(NSException *exception) {
NSLog(@"%@", [exception reason]);
NSLog(@"%@", [exception userInfo]);
NSLog(@"%@", [exception callStackSymbols]);
}

int main(int argc, char * argv[]) {
NSSetUncaughtExceptionHandler(&exceptionHandler);
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

参考

  1. My App Crashed, Now What? – Part 1
  2. 手动解析CrashLog之—-方法篇
  3. Understanding and Analyzing Application Crash Reports
  4. Address space layout randomization
  5. NSSetUncaughtExceptionHandler not catch all errors on iPhone