[iOS开发]iOS 符号表dSYM
一、符号表
1、概念:debugger Symbols 的简称。符号表就是指在Xcode项目编译后,在编译生成的.app的同级目录下生成的同名的.dSYM文件。
符号表是内存地址与函数名,文件名,行号的映射表。 符号表元素如下所示:
<起始地址> <结束地址> <函数> [<文件名:行号>]
.dSYM文件其实是一个目录,在子目录中包含了一个16进制的保存函数地址映射信息的中转文件,所有Debug的symbols都在这个文件中(包括文件名、函数名、行号等),所以也称之为调试符号信息文件
。
2、作用:符号表就是用来符号化 crash log(崩溃日志)。crash log中有一些方法16进制的内存地址等,通过符号表就能找到对应的能够直观看到的方法名之类。
3、获取途径:在Archive的时候会生成.xcarchive文件,然后显示包内容就能够在里面找到.dsYM文件和.app文件。
- 一般Xcode项目每次编译后, 都会产生一个新的.dSYM文件和.app文件, 这两者有一个共同的UUID.
- 注:项目编译完dSYM文件和app文件在同一个目录,Xcode Debug 编译默认不会生成.dSYM文件, Release 编译才会生成
- 注:为了方便找回Crash对应的dSYM文件和还原堆栈,建议每次构建或者发布APP版本的时候,备份好dSYM文件。
二、符号化文件
1、 Xcode分析
1>、要使用Xcode符号化 crash log,你需要下面所列的3个文件:
①crash报告(.crash文件)
②符号文件 (.dsymb文件)
③应用程序文件 (.app文件)
2>、把这3个文件放到同一个目录下,打开Xcode的Window菜单下的organizer,然后点击Devices tab,然后选中左边的Device Logs。
然后把.crash文件拖到Device Logs或者选择下面的import导入.crash文件。
这样你就可以看到crash的详细log了。
2、使用命令行工具symbolicatecrash
1>、将“.app“, “.dSYM”和 ".crash"文件放到同一个目录./Crash 下。
2>、在Xcode中找到 symbolicatecrash 工具
使用命令
find /Applications/Xcode.app -name symbolicatecrash -type f
可以轻松找到。找到后你会发现有多个其中
/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash
对应的是真机, 找到后将 symbolicatecrash 拷贝到 ./Crash
目录下
3>、切换到 ./Crash目录下。 打开终端(Terminal)然后输入如下的命令:
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
然后输入命令:
./symbolicatecrash appName.crash appName.app > appName.log
现在,符号化的crash log就保存在appName.log中了。
如果报No such file or directory : at ./symbolicatecrash line 909.错误,尝试执行
./symbolicatecrash ./*.crash ./*.app.dSYM>Symbol_Crash.crash
3、atos
- atos 是一个可以把地址转换为函数名(包括行号)的工具, 它和dwarfdump 为mac os自带工具.
我们使用atos命令来完成符号化,具体命令如下:
$ atos -arch <Binary Architecture> -o <Path to dSYM file>/Contents/Resources/DWARF/<binary image name> -l <load address> <address to symbolicate>
其中:
- Binary Architecture: arm64、armv6、armv7 armv7s 根据自己的情况来写。
- Path to dSYM file: dSYM文件的路径。
- binary image name: 你工程的名字。
- load address: 是运行时起始地址(基地址),如果我们的崩溃日志中没有这个信息(比如上面的Crash信息中就没有包含),就需要我们手动去计算这个load * address:laod address = address to symbolicate - offset,比如:0x0000000102838119转化为十进制为4337139993,再减去偏移量265,为4337139728,在转化为十六进制0x0000000102838010
- address to symbolicate:运行时堆栈地址,当前方法的内存地址。
具体示例:
atos -arch arm64 -o CrashDemo.app.dSYM/Contents/Resources/DWARF/CrashDemo -l 0x0000000102838010 0x0000000102838119
这样crash文件就被符号化完成了,打开符号化如下图:
三、Crash分析
以上图为例,大部分字段都是不言而喻的,下面列举一些有用处的。(在官方文档都有解释,这里做归纳与翻译)
imageIncident Identifier: 报告的唯一标识符,两份报告决不会共享同一个事件标识符。
CrashReporter Key:每个设备的匿名标识符,来自同一设备的两个报告将包含相同的值。
Process:很明显是我们的进程名称。
Date/Time 与 Launch Time:报告生成时间与程序开始运行时间
Exception Type:异常类型
Exception Note:不属于异常类型的附加信息,如果这个字段包含SIMULATED(不是崩溃),那这个进程不是崩溃的,而是在系统的请求下被杀死,通常是看门狗机制起了作用(APP内一段时间内无法响应用户的操作,会被系统kill)。
Termination Reason:闪退的原因,比如常见的数组越界啊,什么的。
Triggered by Thread:出现问题在哪个线程,这个比较重要,首先确定在哪个线程中出了问题,然后再去定位。
接着是最重要的堆栈信息,由下到上为最后调用的顺序:
可以很明显的看到,一个名为ViewController的对象在viewDidLoad
方法中调用了第35行的testMethodTwo
方法,并执行testMethodTwo
方法中的61行时代码,在Backtrace第2行可以发现调用了未识别的方法导致崩溃,我们来看下代码。
最终我们找到了崩溃的原因:一个NSArray调用了addObject方法,so easy!
一些较常见的异常类型(如果翻译错误请指正):
内存访问不良[EXC_BAD_ACCESS // SIGSEGV // SIGBUS]
通常用于访问了不该访问的内存导致或者尝试以不允许的方式访问内存(例如只读属性,比如在上一篇我们提到的通过KVO更改只读属性),并且" Exception SubType"字段会包含kern_return_t来描述错误和未正确访问的内存地址。
下面是官方给出的建议:
- 如果
objc_msgSend
或objc_release
接近崩溃线程的Backtraces(堆栈信息回溯)的顶部,则该进程可能试图向释放的对象发送消息,可以使用Zombie Instrument来分析应用程序,以更好地了解此次崩溃的情况。- 如果
gpus_ReturnNotPermittedKillClient
接近崩溃线程的Backtraces的顶部,则该进程被终止,因为它尝试在后台使用OpenGL ES或Metal进行渲染。
异常退出[EXC_CRASH // SIGABRT]
该进程异常退出,此异常类型崩溃的最常见原因是向对象发送了无法识别的消息,比如上文中向NSArray发送了addObject消息。
另外如果App Extensions需要太多时间来初始化(看门狗机制),那么App Extensions将终止于此异常类型,如果扩展因启动时挂起而死亡,则生成的崩溃报告的Exception Subtype将会是LAUNCH_HANG
,由于扩展没有main
函数,任何花在初始化上的时间都会在+load
扩展库和相关库中的静态构造函数和方法中,你应该尽可能多地推迟这项工作。
资源限制[EXC_RESOURCE]
该过程超出了资源消耗限制,这是来自操作系统的通知,该进程正在使用太多的资源,确切的资源列在Exception SubType字段中。如果Exception Note字段包含
NON-FATAL CONDITION
,则即使生成崩溃报告,该进程也不会被终止。
- 异常子类型
MEMORY
表示该进程已超过系统施加的内存限制。- 异常子类型
WAKEUPS
表示进程中的线程每秒被唤醒的次数过多,这迫使CPU醒来的频率很高,并且消耗电池寿命。
四、符号化dSYM常见问题
如何查看dsYM文件的UUID?
方法一: 通过命令行查看dSYM文件的UUID
xcrun dwarfdump --uuid <dSYM文件>
方法二:通过符号表文件查看UUID
符号表文件的UUID与dSYM文件的UUID是一致的,因此可以通过符号表工具生成的符号表文件来查看dSYM文件的UUID:
生成符号表文件(.zip) ---> 解压符号表文件(.symbol) ---> 使用文本编辑器打开符号表文件
其中符号表文件的“UUID”信息即Debug SO文件的UUID,亦是符号表文件的UUID,如果文件较大,建议使用“Sublime Text”等文本编辑器来打开符号表文件。
如何定位dSYM文件?
一般情况下,项目编译完dSYM文件跟app文件在同一个目录下,下面以XCode作为IDE详细说明定位dSYM文件。
-> 进入XCode;
-> 打开工程(已编译过);
-> 在左栏找到“Product”项;
-> 鼠标右键点击编译生成的“xxx.app”;
-> 点击“Show in Finder”;
如下图所示:
如果有多个dSYM文件,可以在使用工具时指定输入为dSYM文件所在的目录或者工程目录。
Xcode编译后没有生成dSYM文件?
XCode Release编译默认会生成dSYM文件,而Debug编译默认不会生成,对应的Xcode配置如下:
XCode -> Build Settings -> Code Generation -> Generate Debug Symbols -> Yes
XCode -> Build Settings -> Build Option -> Debug Information Format -> DWARF with dSYM File
开启Bitcode之后需要注意哪些问题?
-
在点“Upload to App Store”上传到App Store服务器的时候需要声明符号文件(dSYM文件)的生成:
-
在配置符号表文件之前,需要从App Store中把该版本对应的dSYM文件下载回本地(参考“如何找回已发布到App Store的App对应的dSYM文件?”),然后用符号表工具生成和上传符号表文件。
-
不需要配置自动生成符号表的脚本了,也不要用本地生成的dSYM文件来生成符号表文件,因为本地编译生成的dSYM文件的符号表信息都被隐藏了。如果用本地编译生成的dSYM文件生成符号表文件并配置到Bugly平台之后,还原出来的结果将是类似于“__hiden#XXX”这样的符号。
如何判断dSYM文件是否与Crash的UUID匹配?
Bugly还原Crash堆栈时,需要根据UUID来匹配符号表文件,因此只有上传的符号表文件的UUID与Crash对应APP的UUID一致时,才能准确地对堆栈进行还原。
-
查看符号表文件的UUID(“如何查看dSYM文件的UUID?”)
-
查看Crash对应的APP的UUID
如何找回已发布到App Store的App对应的dSYM文件?
1> 通过Xcode找回
- 打开 Xcode 顶部菜单栏 -> Window -> Organizer 窗口:
- 打开 Xcode 顶部菜单栏,选择 Archive 标签:
-
找到发布的归档包,右键点击对应归档包,选择Show in Finder操作:
-
右键选择定位到的归档文件,选择显示包内容操作:
- 选择dSYMs目录,目录内即为下载到的 dSYM 文件:
2>通过mdfind工具找回
在Bugly的issue页面查询到crash对应的UUID:
然后在Mac的Shell中,用mdfind命令定位dSYM文件:
mdfind "com_apple_xcode_dsym_uuids == <UUID>"
注意,使用mdfind时,UUID需要格式转换(增加“-”): 12345678-1234-1234-1234-xxxxxxxxxxxx
例如,要定位的dSYM的UUID为:E30FC309DF7B3C9F8AC57F0F6047D65F 则定位dSYM文件的命令如下:
mdfind "com_apple_xcode_dsym_uuids == E30FC309-DF7B-3C9F-8AC5-7F0F6047D65F"
|12345678-1234-1234-1234-xxxxxxxxxxxx|
建议每次构建或者发布APP版本的时候,备份App对应的dSYM文件!
参考:
iOS开发符号表(dSYM)知识总结
bugly文档
iOS Crash收集与分析详解(基础篇)
iOS Crash监测及处理上传