引言
当游戏运行崩溃(也叫闪退)时,iOS系统生成对应的*.crash崩溃日志并保存在设备上。这份崩溃日志通常包含游戏闪退时当时运行的每个线程的堆栈调用信息,我们通过分析这些信息,定位游戏闪退发生的代码位置和异常类型。默认情况下,崩溃日志的堆栈调用信息是一堆16进制的内存地址和偏移量数字组成,如图

并不具有可读性。需要将其导入到Xcode,通过对应的dSYM文件和游戏包体,才能将这些数字转化成游戏代码里的调用方法名和所在文件的行数,如图,

从而生成我们可以分析的具体堆栈调用信息。
一、如何获取崩溃日志。
-
如果你的游戏已经发布到TestFight或者App Store,那么TestFight和AppStore会自动收集你提交的每个游戏版本对应的崩溃日志并下载显示到Xcode - Window - Organizer - Crashes面板里。如图

如果你提交包的时候勾选了包含了dSYM文件选项,崩溃日志还会被自动符号化。要注意的是,只有玩家在设备上选择同意分享数据和统计给开发者,App Store的包对应的崩溃日志才能被收集到。而TestFight的包的崩溃日志总是会被收集到。 -
如果能拿到产生崩溃日志的设备,那么你可以将他连接到mac电脑,通过Xcode - Window - Devices And Simulator,选中左侧面板中的设备,在右侧“Devices”信息栏下选择“View Devices Logs”,如图

然后在Log列表里选择需要的crash日志。如果Xcode的归档文件里有相应dSYM文件和游戏构建,那么crash也会被符号化。
二、如何分析符号化后的崩溃日志
一份完整的崩溃日志包含如下:
// 进程信息
Incident Identifier: B6FD1E8E-B39F-430B-ADDE-FC3A45ED368C
CrashReporter Key: f04e68ec62d3c66057628c9ba9839e30d55937dc
Hardware Model: iPad6,8
Process: TheElements [303]
Path: /private/var/containers/Bundle/Application/888C1FA2-3666-4AE2-9E8E-62E2F787DEC1/TheElements.app/TheElements
Identifier: com.example.apple-samplecode.TheElements
Version: 1.12
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd [1]
Coalition: com.example.apple-samplecode.TheElements [402]
// 基本信息
Date/Time: 2019-09-25 10:43:07.5806 -0700
Launch Time: 2019-09-25 10:43:01.0293 -0700
OS Version: iPhone OS 10.0 (14A5345a)
Report Version: 104
// 异常
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [0]
Triggered by Thread: 0
// 线程回溯
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 TheElements 0x000000010006bc20 -[AtomicElementViewController myTransitionDidStop:finished:context:] (AtomicElementViewController.m:203)
1 UIKit 0x0000000194cef0f0 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 312
2 UIKit 0x0000000194ceef30 -[UIViewAnimationState animationDidStop:finished:] + 160
3 QuartzCore 0x0000000192178404 CA::Layer::run_animation_callbacks(void*) + 260
4 libdispatch.dylib 0x000000018dd6d1c0 _dispatch_client_callout + 16
5 libdispatch.dylib 0x000000018dd71d6c _dispatch_main_queue_callback_4CF + 1000
6 CoreFoundation 0x000000018ee91f2c __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
7 CoreFoundation 0x000000018ee8fb18 __CFRunLoopRun + 1660
8 CoreFoundation 0x000000018edbe048 CFRunLoopRunSpecific + 444
9 GraphicsServices 0x000000019083f198 GSEventRunModal + 180
10 UIKit 0x0000000194d21bd0 -[UIApplication _run] + 684
11 UIKit 0x0000000194d1c908 UIApplicationMain + 208
12 TheElements 0x00000001000653c0 main (main.m:55)
13 libdyld.dylib 0x000000018dda05b8 start + 4
Thread 1:
0 libsystem_kernel.dylib 0x000000018deb2a88 __workq_kernreturn + 8
1 libsystem_pthread.dylib 0x000000018df75188 _pthread_wqthread + 968
2 libsystem_pthread.dylib 0x000000018df74db4 start_wqthread + 4
...
// 线程状态
Thread 0 crashed with ARM Thread State (64-bit):
x0: 0x0000000000000000 x1: 0x000000019ff776c8 x2: 0x0000000000000000 x3: 0x000000019ff776c8
x4: 0x0000000000000000 x5: 0x0000000000000001 x6: 0x0000000000000000 x7: 0x00000000000000d0
x8: 0x0000000100023920 x9: 0x0000000000000000 x10: 0x000000019ff7dff0 x11: 0x0000000c0000000f
x12: 0x000000013e63b4d0 x13: 0x000001a19ff75009 x14: 0x0000000000000000 x15: 0x0000000000000000
x16: 0x0000000187b3f1b9 x17: 0x0000000181ed488c x18: 0x0000000000000000 x19: 0x000000013e544780
x20: 0x000000013fa49560 x21: 0x0000000000000001 x22: 0x000000013fc05f90 x23: 0x000000010001e069
x24: 0x0000000000000000 x25: 0x000000019ff776c8 x26: 0xee009ec07c8c24c7 x27: 0x0000000000000020
x28: 0x0000000000000000 fp: 0x000000016fdf29e0 lr: 0x0000000100017cf8
sp: 0x000000016fdf2980 pc: 0x0000000100017d14 cpsr: 0x60000000
// 二进制映像
Binary Images:
0x100060000 - 0x100073fff TheElements arm64 <2defdbea0c873a52afa458cf14cd169e> /var/containers/Bundle/Application/888C1FA2-3666-4AE2-9E8E-62E2F787DEC1/TheElements.app/TheElements
...
名词解释如下:
| 名词 | 说明 |
|---|---|
| Incident Identifier | 崩溃日志的唯一标识符 |
| CrashReporter Key | 非全局的设备标识符。来自同一个设备的崩溃日志具有相同的值。 |
| Hardware Model | 标识设备机型 |
| Process | 应用名称,也就是Info.plist里键CFBundleExecutable对应的值。中括号里面的数字是闪退时应用的进程ID。 |
| Path | 应用在设备里的安装路径 |
| Identifier | 包名 |
| Version | 应用版本号,由键CFBundleVersion和CFBundleVersionString对应的值拼接而成。 |
| Code Type | 应用的目标架构 |
| Role | 闪退时应用的任务模式(task_role) |
| Parent Process | 父进程 |
| Date/Time | 闪退发生时间 |
| Launch Time | 游戏启动时间 |
| OS Version | 设备的iOS版本,括号里的是iOS的构建版本号 |
| Report Version | 日志版本号 |
| Exception Type | 异常类型的可读性名称,括号里是异常类型编码 |
| Exception Subtype | 异常编码Exception Codes的可读名称,所以一般不会再显示Exception Codes。 |
| Termination Reason | 应用闪退时的退出信息。 |
| Triggered by Thread | 应用闪退时,起始抛出异常的线程ID。 |
| 线程回溯 | 提供闪退时应用中所有线程的调用函数清单。 |
| 线程状态 | 闪退时寄存器中的值。 |
| 二进制映像 | 列出闪退时应用已经加载的二进制文件。 |
这里还有两点还要进一步说明:
- 关于线程回溯,如图

10 : 帧编号
UIKit : 二进制库名称
0x0000000194d21bd0 : 调用方法地址后的-[UIApplication _run]是符号化后的具体方法名
+684 : 指向文件中的代码行。 - 常用异常类型
| 异常名字或编码 | 异常说明 |
|---|---|
| EXC_BAD_ACCESS // SIGSEGV // SIGBUS | 非法内存地址。这个异常通常发生在应用尝试读取非法内存,或者尝试修改只读内存时触发。 |
| EXC_CRASH // SIGABRT | 应用意外退出。通常发生在应用抛出的Objective-C/C++异常未捕获从而调用了abort()。 |
| EXC_BAD_INSTRUCTION // SIGILL | 非法指令。应用尝试执行非法或者未定义的指令。 |
| SIGQUIT | 应用退出。应用被其他进程终止了。 |
| SIGKILL | 应用中断。应用被系统中断了。 |
| EXC_RESOURCE | 应用占用的资源达到了系统的资源上限。 |
| 0x8badf00d | 应用在启动,关闭,响应某个操作的时间超时,导致其被系统中断。 |
| 0xdead10cc | 应用在被挂起的时候还持有文件句柄或者数据库链接,导致其被系统中断。 |
虽然日志里罗列了一大堆信息,但主要还是看线程回溯的第一行以及闪退的异常类型,从而定位闪退发生的代码位置,再依据异常类型,推理出触发异常的操作,然后自己复现一下看看推理是否正确。当你有大量的调试经验,你很容易凭借直觉推理出异常的原因。最重要的是:要相信自己可以找出问题的原因!
iOS崩溃日志分析与处理指南
本文详细介绍了如何获取和分析iOS应用的崩溃日志,包括从TestFlight和App Store收集日志,以及通过Xcode进行设备日志查看。重点讨论了符号化崩溃日志以获取可读的堆栈调用信息,并解析了崩溃日志中的关键元素,如线程回溯和异常类型,帮助开发者定位和解决闪退问题。

被折叠的 条评论
为什么被折叠?



