1 文件介绍
1.1 概念
Xcode 编译项目后,我们会看到一个同名的 dsym 文件。
这是一个什么文件呢?
dsym 是一个保存16进制函数地址映射信息的中转文件,我们需要调试的符号都会包含在这个文件中,并且每次编译项目的时候都会生成一个新的 dsym 文件。
这个文件在哪?
打包时:打包后,会生成对应的 xcarchive 文件。
- Xcode > Window > Origanizer,选择右键”查看包内容”,便可找到。
- 通过 /Users/yangchuxuan/Library/Developer/Xcode/Archives 。
编译时:build后,查看 Pruducts > xxx.app,右键 show in finder,可见生成了 xxx.app 和 xxx.app.dsYM。
附:如果发现没有 dsym 文件,查看项目,Build Settings > Build Options > Debug Information Format ,选择为 DWARF with dsYM File.(全称是Debugging With Attributed Record Formats,设置调试信息,会附带生成一个包含对象文件的调试信息的dsYM文件)
1.2 作用
当我们软件以 release 模式打包或上线后,无法像 Xcode 中那么直观看到崩溃信息。这时就需要分析 crash 文件,设备中会有日志文件保存我们每个应用出错的函数内存地址,通过 Xcode 可以将设备中的 crash 文件导出,再对应出错的函数地址去查询dsYM 文件中程序对应的函数名和文件名。(所以建议保存每个发布版本的 Archives 文件)
2 符号化崩溃报告
2.1 准备文件
xxx.app.dsYM 文件
看第一点。
xxx.app 文件
生成 ipa 文件后,归档工具后,可得。
crash 文件
连接设备,Xcode > Window > Devices > 选择设备 > View Devices Log,根据信息,找到对应的崩溃日志,右键导出。
2.1 验证关系
每一个 xx.app 和 xx.app.dSYM 文件都有对应的 UUID,crash 文件也有自己的 UUID,只要这三个文件的 UUID 一致,我们就可以通过他们解析出正确的错误函数信息了。
xxx.app.dsYM 文件(注意更换名称)
dwarfdump --uuid xxx.app.dSYM
xxx.app 文件(注意更换名称)
dwarfdump --uuid xxx.app/xxx
crash 文件
找到 Binary Images,第一行。(有些报错查看 Crash 文件便可找出)
2.3 使用工具
找到崩溃日志分析工具 symbolicatecrash (注意修改 Xcode 名)
find /Applications/Xcode.app -name symbolicatecrash -type f
生成更易分析的 crash (注意更换名称)
./symbolicatecrash xxx.crash xxx.app.dSYM > result.crash
发现报错,输入命令,进行配置(注意修改 Xcode 名)
export DEVELOPER_DIR=”/Applications/Xcode.app/Contents/Developer”
3 分析符号化过程
3.1 崩溃流程
3.2 流程分析
当编译器将源代码翻译成机器代码时,它也会生成调试符号,它将编译后的二进制中的每一个机器指令映射到源代码的行源代码中。
根据调试信息格式(debug information format)编译设置,这些调试符号存储在二进制或在同伴的调试符号文件(dsym)。默认情况下,调试版本的应用程序的调试符号存储在编译后的二进制中,而发布版本的应用程序的调试符号存储在相应的dsym文中件以减少二进制大小。
图中,前者为运行时对应崩溃堆栈地址,后者函数的加载地址。+号前后两者相加,即是运行时地址。通过这样的方式,机器内部进行转换。
调试符号文件和应用程序二进制文件与每一个build生成的UUID捆绑在一起。一个新的UUID生成是由build一个应用产生的,它应用程序每次build(编译打包)的唯一标识。即使一个功能相同的可执行文件是从相同的源代码重构,具有相同的编译器设置,也会有不同的生成的UUID。调试符号文件的后续版本,甚至来自同一个源文件,不会与其他版本的二进制文件相混淆。
当你的应用程序崩溃时,一个unsymbolicated崩溃报告会被创建并存储在设备上。
应用是通过AdHoc or Enterprise distribution发布的,你就只能从设备上导出崩溃报告。方法:Xcode > Devices > Devices Log > 选中 Export 。
应用是上传到商店,用户选择了与苹果共享诊断数据,或者如果用户通过TestFlight安装了你的应用程序,崩溃报告会被上传到应用商店。方法:手机 > 设置 > 隐私 > 诊断与用量 > 诊断与用量数据,即可获得Crash文件,用户通过选择自动发送将之上传。这时你会发现,Crash文件并没有符号化,这时便需要进行符号化。
3.3 崩溃报告
上图中,从上到下,分别是 未符号化的 Crash 文件 、部分符号化的 Crash 文件 、 符号化的 Crash 文件。
4 监测应用的第三方库
可借助检测应用的第三方库来帮助我们进行异常或崩溃的跟踪。例如腾讯 Bugly、友盟。
举例,腾讯 Bugly:
- 注册应用,获取对应的 App_ID、App_key;
- 以 pod / frameWork 的方式集入 Bugly 的 SDK;
- 配置。(具体参考 SDK 文档)
5 崩溃报告的结构分析
5.1 第一部分:进程信息
字段 | 表达信息 |
---|---|
Incident Identifier | 崩溃报告的唯一标识符 |
CrashReporter Key | 与设置标识相对应的唯一键值,并非真正的设备标识符 |
Hardware Model | 标识设备类型 |
Process | 应用名称,中括号里面的数字是闪退时应用的进程ID |
Path | 应用的安装路径 |
Identifier | 应用的 Bundle Identifier |
Version | 应用版本号 |
Code Type | 当前 App 的 CPU 架构 |
Role | 崩溃时应用处于后台还是当前 |
Parent Process | 当前进程的父进程,由于 iOS 中 App 通常是单进程,则父进程一般为 Launchd |
Coalition | 不清楚 |
5.2 第二部分:基本信息
字段 | 表达信息 |
---|---|
Date/Time | 应用崩溃时间 |
Launch Time | 应用启动时间 |
OS Version | 设备系统版本,()内的数字代表 build 号 |
Report Version | Crash 日志的格式,当前基本都是104 |
5.3 第三部分:异常信息
字段 | 表达信息 |
---|---|
Exception Type | 异常类型 |
Exception Codes | 异常代码。关于异常的处理器特定信息被编码成一个或多个64位十六制数字,通常,这个字段是不存在的,因为崩溃报告程序会解析异常代码,并将其作为其他字段中人们可阅读的描述来展现 |
Exception Subttype | 异常子类型,人们可阅读性的异常代码 |
Exception Note | 对异常类型不具体的附加信息 |
Triggered by Thread | 发生异常的线程,Thread 0 为主线程,其他为子线程 |
5.3.1 两种崩溃报告的例子
给两个截图,看两种崩溃报告的异常部分
1、未捕获的 OC 异常导致崩溃的崩溃报告
2、进程因取消 NULL 指针被终止的崩溃报告
5.3.2 异常类型
Bad Memory Access [ EXC_BAD_ACCESS // SIGSEGV // SIGBUS ]
该过程尝试访问无效内存,或者尝试以不允许的存储器保护级别的方式访问存储器(例如写入只读存储器)。异常子类型字段(Exception Subtype)会包含一个 kern_return_t 描述错误和未正确访问的内存地址。
Abnormal Exit [ EXC_CRASH // SIGABRT ]
收到Abort信号退出,通常 Foundation 库中的容器为了保护状态正常会做一些检测,例如插入 nil 到数组中等会遇到此类的错误。
字段 | 表达信息 |
---|---|
SIGSEGV | 由于重复释放对象导致,这种异常类型在切换了 ARC 以后就很少见 |
SIGABRT | 收到Abort信号退出,通常 Foundation 库中的容器为了保护状态正常会做一些检测,例如插入 nil 到数组中等会遇到此类的错误 |
SEGV | 代表无效内存地址,比如空指针,未初始化指针,栈溢出等 |
SIGBUS | 总线错误,SIGSEGV 访问的是无效地址,而 SIGBUS 访问的是有效地址,但总线访问异常(如地址对齐问题) |
SIGILL | 尝试执行非法的指令,可能不被识别或者没有权限 |
5.4 第四部分:线程回溯
提供应用中所有线程的回溯日志,回溯是闪退发生时所有活动帧清单,它包含闪退发生时调用函数的清单。
包括四列:
- 帧编号:此处为2
- 二进制库的地址:此处为XYZLib
- 调用方法的地址:此处为0x34648e88
- 第四列为2个子列(基本地址和偏移量):前者是指向文件,后者指向文件中的代码行,此处为0x83000 + 8740.
5.5 第五部分:线程状态
这是崩溃时寄存器线程状态。阅读 Crash 文件不用关注这部分信息,但也可以利用这个信息更好了解崩溃信息。
5.6 第六部分:二进制映像
这是崩溃时已经记载的二进制映像。
6 低内存闪退
崩溃日志其实是有两种,低内存崩溃日志 与 普通崩溃日志。上述为普通崩溃日志,下面介绍低内存闪退相关内容。
6.1 触发低内存闪退
iOS 设备检测到低内存时,虚拟内存系统发出通知请求应用释放内存。这些通知发到到正在运行的应用和进程,尝试回收一些内存。
如果内存使用依然居高不下,系统将会终止后台线程以缓解内存压力。如果可用内存足够,应用将能够继续运行而不会产生崩溃报告。否则,应用将被iOS终止,并产生低内存崩溃报告。
6.2 低内存崩溃日志
低内存崩溃日志上没有应用线程的堆栈回溯。相反,上面显示的是以内存页数为单位的各进程内存使用量。
被iOS因释放内存页终止的进程名称后面你会看到jettisoned 字样。如果看到它出现在你的应用名称后面,说明你的应用因使用太多内存而被终止了。
可以通过 Instruments 工具中使用 Allocations 和 Leaks 来发现内存分配问题和内存泄漏问题。
6.3 低内存崩溃日志特有字段
字段 | 表达信息 |
---|---|
Free pages | 可用内存页数 |
Purgeable pages | 可被清除或重用的内存 |
Largest process | 闪退时,使用大部分内存的应用名称 |
Processes | 显示了闪退时各进程列表,以及内存使用量 |