一、dSYM 1.dSYM 调试符号表,是苹果为调试和定位问题而使用的一种调试文件。调试符号信息在构建应用时就保存在Mach-O
文件中了,而.dSYM
就是从Mach-O
文件中抽取调试信息而得到的一个单独的文件目录。它使用的是 DWARF 结构,在 .xcarchive 目录中其层次结构如下:
1 2 3 4 5 6 .xcarchive --dSYMs |--Your .app.dSYM |--Contents |--Resources |--DWARF
要生成 dSYM 文件,你可以在 Xcode 的Build Settings
中这样设置:
1 2 Generate Debug Symbols = YES Debug Information Format = "DWARF with dSYM File"
反之,如果你不想生成 dSYM 文件,则可以这样配置:
1 2 3 Generate Debug Symbols = NO 或者Debug Information Format = "DWARF"
2.DWARF DWARF(DebuggingWith Arbitrary Record Formats),是 ELF 和 Mach-O 等文件格式中用来存储和处理调试信息的标准格式,.dSYM
中真正保存符号表数据的是DWARF
文件。DWARF 中不同的数据都保存在相应的section
(节)中,ELF文件里所有的 section 名称都以.debug_
开头,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | Section Name | Contents | | -------------------- | ------------------------------------------------ | | .debug_abbrev | Abbreviations used in the .debug_info section | | .debug_aranges | A mapping between memory address and compilation | | .debug_frame | Call Frame Information | | .debug_info | The core DWARF data containing DIEs | | .debug_line | Line Number Program | | .debug_loc | Macro descriptions | | .debug_macinfo | A lookup table for global objects and functions | | .debug_pubnames | A lookup table for global objects and functions | | .debug_pubtypes | A lookup table for global types | | .debug_ranges | Address ranges referenced by DIEs | | .debug_str | String table used by .debug_info
Mach-O中关于section
的命名和ELF稍有区别,把名称前的.
换成了_
,例如.debug_info
变成了_debug_info
。
保存在 DAWARF 中的信息是高度压缩的,可以通过dwarfdump
命令从中提取出可读信息。前文所述的那些 section 中,定位闪退日志只需要用到.debug_info
和.debug_line
。由于解析出来的数据量较大,为了方便查看,就将其保存在文本中。两个 section 的数据提取方式如下:
1 $ dwarfdump -e --debug-info YourApp.dSYM/Contents/Resources/DWARF > info -e.txt
1 $ dwarfdump -e --debug -line YourApp.dSYM/Contents/Resources/DWARF > line -e.txt
3.闪退解析过程
下面的第二章节中会详细讲解闪退日志中的stack address
、load address
以及怎样计算闪退地址在符号表中的 symbol address
,这里以 0x52846 为例。
.debug_info
中最基本的描述单元为DIE(Debug Information Entry),首先我们要根据符号表闪退地址0x52846
从.debug_info
中取出包含这个地址的DIE单元。为了简单起见,直接贴出了从 info-e.txt 中取出的对应DIE,其部分内容如下:
1 2 3 4 5 6 7 8 9 10 0 x00062112 : function [99 ] * low pc ( 0 x000502e0 ) high pc ( 0 x00053730 ) frame base ( r7 ) object pointer ( {0 x0006212a } ) name ( "-[OBDFirstConnectViewController showOilPricePickerView]" ) decl file ( "/YourSourcePath/OBDFirstConnectViewController.m" ) decl line ( 870 ) prototyped ( 0 x01 ) APPLE instruction set architecture ( 0 x01 )
可以看出,该DIE包含的是方法-[OBDFirstConnectViewController showOilPricePickerView]的内容,其地址范围是 0x000502e0–0x00053730,我们的目标地址 0x52846 正是在这个范围内,所以可以判定闪退发生在该方法的某一行中。
需要指出的是,上面这段DIE是为了介绍方便直接贴出来的,实际应用的时候需要通过搜索算法找出包含目标符号表闪退地址(这里是0x52846)的DIE。
从上述DIE中我们可以获取到这些信息:
闪退所在源码文件:/YourSourcePath/OBDFirstConnectViewController.m
发生闪退的方法:-[OBDFirstConnectViewController showOilPricePickerView]
发生闪退的方法在源文件中的行号:870
截止目前,我们可以获取到发生了闪退的方法的相关信息,但要想确定闪退发生的具体行号,还需要.debug_line 的帮助。
.debug_line 以一个方法为基本块,记了该方法中每一行对应的符号表地址。通过.debug_info 得知闪退发生的方法地址范围是 0x000502e0–0x00053730,通过起始地址 0x000502e0 再解析. debug_line 得到的 line-e.txt 中直接搜索即可得到闪退所在方法的. debug_line 数据,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 0 x00000000000502e0 870 /YourSourcePath/OBDFirstConnectViewController.m0 x00000000000502e0 0 0 x00000000000502f0 872 0 x000000000005033c 873 0 x0000000000050374 874 0 x000000000005039e 875 0 x00000000000503c8 876 ...0 x0000000000052812 880 0 x000000000005283e 881 0 x0000000000052846 882 0 x00000000000528c8 883 ...
. debug_line 段的第一行内容标识了该方法的起始符号表地址,行号及方法所在文件路径,通过之前得到的闪退地址0x52846
即可得知闪退发生在882
行。
至此我们已经根据闪退地址解析出了闪退发生位置的详细信息:
闪退所在源码文件:/YourSourcePath/OBDFirstConnectViewController.m;
发生闪退的方法:-[OBDFirstConnectViewController showOilPricePickerView];
发生闪退的方法在源文件中的行号:870;
闪退发生在源文件中得行号:882。
二、.crash文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 Incident Identifier: 28593639 -9021 -4BF9-A730-0A4E644EE52E CrashReporter Key: cf719616d48fff95262c17eaf002e4de3d3f9842 Hardware Model: iPhone8,1 Process: WeChat [25431 ]Path: /var/../WeChat.app/WeChat Identifier: com.tencent.xin Version: 6.5 .7 .32 (6.5.7) Code Type: ARM-64 (Native) Parent Process: launchd [1 ]Date/Time: 2017-04-29 14:12:13.13 +0800 Launch Time: 2017-04-26 03:26:06.06 +0800 OS Version: iOS 9.3 .5 (13G36) Report Version: 105 Exception Type: 00000020 Exception Codes: 0x000000008badf00d Exception Note: SIMULATED (this is NOT a crash) Highlighted by Thread: 0 Thread 0 name: Dispatch queue: com.apple.main-thread Thread 0: 0 libsystem_kernel.dylib 0x0000000180f99014 0x180f98000 + 4116 1 libdispatch.dylib 0x0000000180e763e8 0x180e64000 + 74728 2 FrontBoardServices 0x0000000182db63d4 0x182d94000 + 140244 3 FrontBoardServices 0x0000000182d9e910 0x182d94000 + 43280 15 GraphicsServices 0x0000000182be0088 0x182bd4000 + 49288 16 UIKit 0x00000001865e6088 0x186568000 + 516232 17 WeChat 0x00000001000fbea4 0x100058000 + 671396 Binary Images: 0x100058000 - 0x1033c3fff WeChat arm64 <8b7a21136f4434dfb2771b8a4218a0f1> /var/containers/Bundle/Application/26524DA8-D1E8-430D-8ECA-9D5ABE3CE3CA/WeChat.app/WeChat ..
上面是一个crash文件的大致内容,为了简洁删除了一部分。
1.参数
1 Incident Identifier: 28593639 -9021 -4 BF9-A730-0 A4E644EE52E
这是闪退文件中的 uuid ,根据这个 uuid 可确定与 dSYM 文件是否匹配,方法如下:
1 dwarfdump --uuid appName.app .dSYM/
这个命令行会打印出该 dSYM 文件内所有架构的 uuid。
上面 “WeChat” 表示的是闪退所在进程的名字,括号中的数字即为此进程的ID。
1 Code Type : ARM-64 (Native)
程序运行时的映射信息:(Binary Images)
1 2 3 Binary Images:0 x100058000 - 0 x1033c3fff WeChat arm64 <8 b7a21136f4434dfb2771b8a4218a0f1> /var/ containers/Bundle/ Application/26524DA8-D1E8-430D-8ECA-9D5ABE3CE3CA/ WeChat.app/WeChat
第一列,应用二进制文件中代码段的起始地址~终止地址;(0x100058000 - 0x1033c3fff)
第二列,映射文件名;(WeChat)
第三列,uuid;(8b7a21136f4434dfb2771b8a4218a0f1)
第四列,映射文件路径;
闪退的堆栈信息(Thread Backtrace):
1 2 3 4 5 6 7 8 0 libsystem_kernel. dylib 0x0000000180f99014 0x180f98000 + 4116 1 libdispatch. dylib 0x0000000180e763e8 0x180e64000 + 74728 2 FrontBoardServices 0x0000000182db63d4 0x182d94000 + 140244 3 FrontBoardServices 0x0000000182d9e910 0x182d94000 + 43280 ..15 GraphicsServices 0x0000000182be0088 0x182bd4000 + 49288 16 UIKit 0x00000001865e6088 0x186568000 + 516232 17 WeChat 0x00000001000fbea4 0x100058000 + 671396
第一列:调用顺序;(0、1、2、3)
第二列:二进制库名;(WeChat、UIKit)
第三列:进程在运行时发生闪退处的地址;(stack address)
第四列:进程运行时的起始地址;(load address)
第五列:闪退处距离进程起始地址的偏移量;(slide)
注意!!这里的 “stack address” 与 “load address” 都是16进制的,而 “slide” 则是10进制的。
2.stack address 计算一下你就会发现,上面堆栈信息中,第四列+第五列 的内容与第三列在实质上是一样的。以下面的闪退堆栈信息的第 9 行为例:
1 2 3 4 5 6 7 8 9 10 Thread 0 :0 libobjc. A. dylib 0x33f10f60 0x33efe000 + 77664 1 Foundation 0x273526ac 0x2734a000 + 34476 9 Your 0x000f0846 0xa2000 + 321606 28 Your 0x0024643a 0xa2000 + 1721402 29 libdyld. dylib 0x34484aac 0x34483000 + 6828 Binary Images:0xa2000 - 0x541fff Your armv7 /var/mobile/Containers/Bundle/Application/645D3184-4C20-4161 -924B-BDE170FA64CC/Your. app/Your
1 2 3 stack address = 0 x000f0846 load address = 0 xa2000slide = 321606
将 slide 转换为 16 进制后(即 0x4E846)与 load address 相加:
1 0x000f0846 = 0 xa2000 + 0 x4E846
所以,我们可以得出这么一个公式:
1 stack address = load address + slide;
注意!!这里 “stack address” 和 “load address” 均为程序在运行时的地址。要想利用符号表解析出闪退对应位置,需要计算出符号表中对应的闪退堆栈地址(symbol address)。
3.symbol address 我们打开一个应用时,内核会为该应用创建一个新的进程,并把应用的二进制文件加载到一片虚拟地址空间中。iOS4.3 后为了阻止内存溢出攻击苹果在iOS中使用了 ASLR 技术。这样进程每次启动时,地址空间都会被简单地随机化,所以每次加载时地址都不一样。这里的随机只是偏移不是搅乱,实现方式是通过内核将 Mach-O 的段“平移”某个随机数。
根据虚拟内存偏移量不变原理,只要提供了符号表 __TEXT 段的起始地址(vmaddr),再加上偏移量(这里为0x4E846)就能得到符号表中的堆栈地址(symbol address),计算方法为:
1 symbol address = vmaddr + slide;
如何获取符号表中的 __TEXT 段起始地址呢?
1 $otool -l xx.app.dSYM/Contents/ Resources/DWARF/ xx
注意把 xx 替换为你自己的应用名。运行结果中的片段如下:
1 2 3 4 5 6 7 8 9 10 11 12 Load command 3 cmd LC_SEGMENT cmdsize 736 segname __TEXT vmaddr 0 x00004000 vmsize 0 x00700000 fileoff 0 filesize 0 maxprot 0 x00000005 initprot 0 x00000005 nsects 10 flags 0 x0
其中的 vmaddr 0x00004000 字段即为 __TEXT 段的起始地址。
由公式:
1 symbol address = vmaddr + slide;
可得出:
1 0x52846 = 0 x4000 + 0 x4E846
即符号表中的闪退地址 = 0x52846,接下来就可以根据这个地址解析出闪退位置了。
三、解析方案 1.dwarfdump 命令如下:
1 $dwarfdump --arch armv7 Your.app .dSYM --lookup 0x52846 | grep 'Line table '
需要注意的是:这里的 armv7 是运行设备的 CPU 指令集,而不是二进制文件的指令集。比如 armv7 指令集的二进制文件运行在 arm64 指令集的设备上,这个地方应该写 arm64。
–lookup 后面跟的一定是经过准确计算的符号表中的闪退地址
使用 dwarfdump 解析的结果较杂乱,因此使用 grep 命令抓取其中关键点展示出来
运行结果如下:
1 2 Line table dir : '/data/.../Src/OBDConnectSetting/Controller' Line table file: 'OBDFirstConnectViewController.m' line 882 , column 5 with start address 0x000000000052768
其中第一行是编译时文件目录,第二行包含了闪退发生的文件名称以及文件中具体行号等信息,有了这些信息就能准确定位闪退原因啦。
2.atos atos 命令可以解析出指定某一行的堆栈,使用方式如下:
1 $atos -o /Users/ xxx/crash/ AppName.app/AppName -arch armv7 0 x52846
其执行结果如下:
1 -[OBDFirstConnectViewController showOilPricePickerView] (in Your) (OBDFirstConnectViewController.m:882 )
3.无需计算闪退地址 atos 还提供了另外一种无需计算闪退地址对应的符号表地址的方式,命令格式如下:
1 $atos -o Your.app.dSYM/Contents/ Resources/DWARF/ Your -arch armv7 -l [load address] [stack address]
其中 -l 选项指定了二进制文件在运行时的 load address (0xa2000),后面跟的是闪退发生时的 stack address (0x000f0846)。解析结果:
1 -[OBDFirstConnectViewController showOilPricePickerView] (in Your) (OBDFirstConnectViewController.m:882 )
4.shell脚本 桌面新建一个crash文件夹,把dSYM文件、友盟日志txt 及 shell 脚本放进来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 Application received signal SIGSEGV (null) ((0 CoreFoundation 0x0000000190f311b8 0x0000000190e01000 + 1245624 1 libobjc. A. dylib 0x000000018f96855c objc_exception_throw + 56 2 CoreFoundation 0x0000000190f3108c 0x0000000190e01000 + 1245324 3 Foundation 0x00000001919e902c 0x000000019193b000 + 712748 4 UIKit 0x00000001976c5704 0x0000000196dd7000 + 9365252 5 UIKit 0x00000001976c5afc 0x0000000196dd7000 + 9366268 6 UIKit 0x00000001976c6f0c 0x0000000196dd7000 + 9371404 7 UIKit 0x00000001975f1c10 0x0000000196dd7000 + 8498192 8 UIKit 0x00000001975f1e28 0x0000000196dd7000 + 8498728 9 MyApp 0x0000000100223668 0x00000001000ac000 + 1537640 10 MyApp 0x00000001001e1588 0x00000001000ac000 + 1267080 11 UIKit 0x00000001973aef80 0x0000000196dd7000 + 6127488 12 UIKit 0x00000001973b2748 0x0000000196dd7000 + 6141768 13 UIKit 0x0000000196f7973c 0x0000000196dd7000 + 1713980 14 UIKit 0x0000000196e180f0 0x0000000196dd7000 + 266480 15 UIKit 0x00000001973a2680 0x0000000196dd7000 + 6076032 16 UIKit 0x00000001973a21e0 0x0000000196dd7000 + 6074848 17 UIKit 0x00000001973a149c 0x0000000196dd7000 + 6071452 18 UIKit 0x0000000196e1630c 0x0000000196dd7000 + 258828 19 UIKit 0x0000000196de6da0 0x0000000196dd7000 + 64928 20 MyApp 0x00000001005b01b4 __cxa_throw + 2250128 21 UIKit 0x00000001975d075c 0x0000000196dd7000 + 8361820 22 UIKit 0x00000001975ca130 0x0000000196dd7000 + 8335664 23 CoreFoundation 0x0000000190edeb5c 0x0000000190e01000 + 908124 24 CoreFoundation 0x0000000190ede4a4 0x0000000190e01000 + 906404 25 CoreFoundation 0x0000000190edc0a4 0x0000000190e01000 + 897188 26 CoreFoundation 0x0000000190e0a2b8 CFRunLoopRunSpecific + 444 27 GraphicsServices 0x00000001928be198 GSEventRunModal + 180 28 UIKit 0x0000000196e517fc 0x0000000196dd7000 + 501756 29 UIKit 0x0000000196e4c534 UIApplicationMain + 208 30 MyApp 0x00000001001b5804 0x00000001000ac000 + 1087492 31 libdyld. dylib 0x000000018fded5b8 0x000000018fde9000 + 17848 ) dSYM UUID: 1852C4B7-8391 -3615 -ADA5-2EE58D11DDEDCPU Type: armv7 Slide Address: 0x00004000 Binary Image: MyApp Base Address: 0x000ca000
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 DSYM=$1 LOGFILE=$2 if [ ! -n "$DSYM " ]; then echo ">>>> DSYM is missing!!!" exit fi if [ ! -n "$LOGFILE " ]; then echo ">>>> log file is missing!!!" exit fi echo "" echo "/////////////////////" echo " Info get... " echo "/////////////////////" echo "" DSYM_UUID_KEY="dSYM UUID: " DSYM_CPUTYPE_KEY="CPU Type: " DSYM_BINARY_KEY="Binary Image: " while read singleLinedo str="$singleLine " if [[ $str == $DSYM_UUID_KEY * ]]; then UUID=${str#*"$DSYM_UUID_KEY"} ; echo "Log UUID: " $UUID ; elif [[ $str == $DSYM_CPUTYPE_KEY * ]]; then CPU=${str#*"$DSYM_CPUTYPE_KEY"} ; echo "Log CPU: " $CPU ; elif [[ $str == $DSYM_BINARY_KEY * ]]; then BINARY=${str#*"$DSYM_BINARY_KEY"} ; echo "Log BINARY: " $BINARY ; fi done < $LOGFILE echo "" echo "/////////////////////" echo " Info check... " echo "/////////////////////" echo "" checkUUID=`dwarfdump --uuid $DSYM -arch $CPU ` checkUUID=${checkUUID#*"UUID: "} checkUUID=${checkUUID%" ($CPU)"*} echo "DSYM UUID: $checkUUID " if [[ "$UUID " == "$checkUUID " ]]; then echo "UUID check passed." else echo "Warning!!!!!! UUID is not the same.. even though the code could be." fi echo "" echo "/////////////////////" echo " Log trace... " echo "/////////////////////" echo "" while read singleLinedo str="$singleLine " if [[ $str == $DSYM_BINARY_KEY * ]]; then echo "" else str=${str#*" "} if [[ $str == *$BINARY * ]]; then str=${str#*" "} ; str=${str%" + "*} s1=$(echo $str | awk -F " " '{print $1}' ) s2=$(echo $str | awk -F " " '{print $2}' ) echo `atos -o $DSYM /Contents/Resources/DWARF/$BINARY -arch $CPU -l $s2 $s1 ` fi fi done < $LOGFILE
执行脚本:
1 ./trace_dsyms.sh Release.dSYM/ crash
解析后的闪退堆栈如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /// // // // // // // // // // Info get... /// // // // // // // // // // Log UUID : 1852C4B7-8391 -3615 -ADA5 -2EE58D11DDED Log CPU : armv7 Log BINARY : MyApp /// // // // // // // // // // Info check... /// // // // // // // // // // DSYM UUID : 1852C4B7-8391 -3615 -ADA5 -2EE58D11DDEDUUID check passed. /// // // // // // // // // // Log trace... /// // // // // // // // // // -[TLTaoBaoBindViewController setUps] (in MyApp) (TLTaoBaoBindViewController.m: 234 ) -[MBProgressHUD labelText] (in MyApp) (MBProgressHUD.h: 313 ) [invalid usage]: slide is not a recognized number -[TLLoginManager httpTelLoginWithDistrict: mobile: captcha: fromVC: ] (in MyApp) (TLLoginManager.m: 277 )
5.symbolicatecrash symbolicatecrash 是Xcode自带的一个分析工具,可以通过机器上的闪退日志和应用的.dSYM文件定位发生闪退的位置,把crash日志中的地址替换成代码相应位置。
1、文件准备:.app 与 .dSYM 文件
拷贝 .crash 文件到桌面新建的crash文件夹内;
Xcode->Window->Organizer->APP->Show in Finder;
.xcarchive 文件->显示包内容,找到 .app.dSYM 与 .app文件,并复制到 crash 目录中;
2、找到symbolicatecrash
1 find /Applications/ Xcode.app -name symbolicatecrash -type f
1 /Applications/ Xcode.app/Contents/ SharedFrameworks/DVTFoundation.framework/ Versions/A/ Resources/symbolicatecrash
拷贝symbolicatecrash到crash目录中,与上面俩文件放到一起。
1 2 cp /Applications/ Xcode.app/Contents/ SharedFrameworks/DVTFoundation.framework/ Versions/A/ Resources/symbolicatecrash / Users/xxx/ Desktop/crash
3、执行symbolicatecrash
1 cd /Users/ xxx/Desktop/ crash
1 ./symbolicatecrash / Users/xxx/ Desktop/crash/ xxx.crash /Users/ xxx/Desktop/ crash/xxx.app.dSYM > new_symbol.crash
1 Error: "DEVELOPER_DIR" is not defined at ./symbolicatecrash line 69.
1 export DEVELOPER_DIR ="/Applications/XCode.app/Contents/Developer"
相关参考:
#©王中周-手动解析CrashLog之—-方法篇
#©王中周-手动解析CrashLog之—-原理篇
#©ctinusdev-Mach-O文件介绍之ASLR