异常日志符号化
一般情况下如果我们可以通过Xcode来查看异常日志的话,获取到的异常日志都是符号化之后,可以直接查看并定位异常.但是如果在测试阶段需要从手机上导出异常日志,或者集成了第三方异常收集但是未能上传符号表到对应的后台等情况下就只能获取到未符号化的异常日志,这时候就需要对异常日志进行符号化.
生成符号表文件的前提
想要进行异常日志的符号化,前提就是保存了打包时生成的.dSYM文件,如果没有保存的话,符号化是不可能了,能靠经验去找了.
- 在Generate Debug Symbols --> Apple Clang --> Code Generation中,设置Generate Debug Symbols为YES,模式是打开的;
- 在Build Settings --> Build Options--> Debug Information Format中,将需要产生符号表的模式对应的值设置为DWARF with dsym File,将不需要产生符号表的模式对应值设置为DWARF.默认Debug模式下不产生符号表,Release模式不产生符号表.
符号表的存储位置
在默认情况下,符号表会存储在跟.xcarchive --> dSYMs文件夹下.
异常日志的符号化
准备
- 新建一个文件夹CrashAnalysis,用来存储需要的文件;
- 查找symbolicatecrash,并将该工具保存到新创建的文件夹CrashAnalysis中;
Xcode内置了符号化异常日志的工具symbolicatecrash.打开terminal输入下边的指令,通过find命令查找Mac上的symbolicatecrash位置:
find /Applications/Xcode.app -name symbolicatecrash -type f -print | grep iPhoneSimulator
然后将symbolicatecrash复制到CrashAnalysis中.
- 将异常日志(.crash),符号表(.dSYM),复制到文件夹CrashAnalysis中;
日志符号化
对于异常日志的符号化,分为两种情况进行讨论:
- 可以获取到完整的.crash系统异常日志,将全部异常日志一次性进行符号化;
- 不能获取到完整的.crash系统日志(例如自定义异常获取时,只保留了调用堆栈等部分异常信息),只将需要的部分进行符号化.
(1) 将完整的系统异常日志全部符号化
- 使用dwarfdump命令查看.dSYM文件的UUID;
dwarfdump --uuid xxx.dSYM
- 查看.crash中的UUID是否与.dSYM中的UUID一致[在Xcode 11.0上发现每次打包会产生三个.dSYM文件,两个文件名以UUID开头,另外一个文件名以工程名开头];
- 然后使用以下命令符号化日志
./symbolicatecrash xxx.crash xxx.app.dSYM > result.txt
可能会遇到
Error: "DEVELOPER_DIR" is not defined at ./symbolicatecrash line 69.
错误提示,如果遇到了,使用
//如果Xcode.app不是默认的名称,修改为自己Xcode名称即可
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
然后再次运行开始的命令,即可得到一个result.txt的符号化文件,在该文件中即可看到符号化之后的异常日志.
(2) 将局部异常日志符号化:
有时候我们并不能获取到完整的crash日志,或者只是想对一部分异常日志进行符号化.这时候也可以使用atos命令来进行.
例如自定义异常捕获方法时,我们只获取到了异常调用堆栈的信息:
0 libsystem_kernel.dylib 0x0000000183d35348 0x183d14000 + 136008
1 libsystem_pthread.dylib 0x0000000183e49354 0x183e46000 + 13140
2 libsystem_c.dylib 0x0000000183ca4fd8 0x183c42000 + 405464
3 libc++abi.dylib 0x0000000183708068 0x183706000 + 8296
4 libc++abi.dylib 0x0000000183708210 0x183706000 + 8720
5 libobjc.A.dylib 0x0000000183730810 0x183728000 + 34832
6 libc++abi.dylib 0x000000018372054c 0x183706000 + 107852
7 libc++abi.dylib 0x00000001837205b8 0x183706000 + 107960
8 libdispatch.dylib 0x0000000183ba105c 0x183ba0000 + 4188
9 libdispatch.dylib 0x0000000183ba86c8 0x183ba0000 + 34504
10 FrontBoardServices 0x00000001868f1a04 0x1868b1000 + 264708
11 FrontBoardServices 0x00000001868f16a8 0x1868b1000 + 263848
12 FrontBoardServices 0x00000001868f1c44 0x1868b1000 + 265284
13 CoreFoundation 0x00000001841c4358 0x1840da000 + 959320
14 CoreFoundation 0x00000001841c42d8 0x1840da000 + 959192
15 CoreFoundation 0x00000001841c3b60 0x1840da000 + 957280
16 CoreFoundation 0x00000001841c1738 0x1840da000 + 948024
17 CoreFoundation 0x00000001840e22d8 0x1840da000 + 33496
18 GraphicsServices 0x0000000185f73f84 0x185f69000 + 44932
19 UIKit 0x000000018d68e880 0x18d61b000 + 473216
20 SymblicateCrashDemo 0x0000000100e77770 0x100e70000 + 30576
21 libdyld.dylib 0x0000000183c0656c 0x183c05000 + 5484
根据异常堆栈的调用信息可以看到,异常日志调用堆栈包含了四列信息:
- 第一列是调用顺序序号:只不过时机的执行调用顺序是按照序号的逆序相同(栈);
- 第二列是对应函数所属的Binary Images:包含了系统的库和自定义的库;
- 第三列是实际调用的函数栈地址:调用的函数在内存中的栈地址(stack address);
- 第四列是函数栈地址的偏移量表示法:使用函数所在的的Binary Image起始地址+偏移量来表示函数的栈地址.
目之所及,只有调用序号为20的这一行是自己编写代码中的调用,其余的都是系统Binary Image的调用,没有操作权限,所以不是我们关注的信息.
查看出错信息时,使用
atos -o {.dSYM中可执行文件的路径} -l {函数所在Binary Image起始地址} -arch {对应设备使用的指令集} {实际函数栈地址}
对于用例
20 SymblicateCrashDemo 0x0000000100e77770 0x100e70000 + 30576
来说(当前路径是/CrashAnalysis),
- {.dSYM中可执行文件的路径}:SymblicateCrashDemo.app.dSYM/Contents/Resources/DWARF/SymblicateCrashDemo;
- {函数所在Binary Image起始地址}:0x100e70000;
- 所使用测试设备为iPhone XS,所以{对应设备使用的指令集}:arm64(在实际操作中可以获取到当前的设备类型映射到对应的指令集);
- {实际函数栈地址}:0x0000000100e77770
执行:
atos -o SymblicateCrashDemo.app.dSYM/Contents/Resources/DWARF/SymblicateCrashDemo -l 0x100e70000 -arch arm64 0x0000000100e77770
输出:
__44+[ExceptionHandler registerExceptionHandler]_block_invoke (in SymblicateCrashDemo) (AppDelegate.m:38)
可以知道,出错的代码在Appdelegate实现文件第38行[ExceptionHandler registerExceptionHandler]中.
其他
其实对于局部异常日志符号化,还有一种方法,那就使用 dwarfdump命令,只不过这个命令使用起来比较麻烦.
ASLR (Address space layout randomization):
在iOS中为了防止缓冲区溢出攻击而采用了ASLR技术将可执行文件加载到设备内存.简单来说就是设备每次加载应用可执行文件到内存时,系统都会随机生成一个地址偏移量slide,然后在原来虚拟内存的基础上偏移slide得到得到一个加载可执行文件的起始地址,所以设备每次加载应用的可执行文件到内存的起始地址都是随机的.
在链接时,符号表地址产生了一个实际存储符号的虚拟地址,这个地址存储在应用的可执行文件中,可以使用(mach O文件中load commands中的text段虚拟地址)
otool -arch {设备指令集} -l {应用对应的二进制可执行文件} | grep "segname __TEXT" --after-context=1 | grep "vmaddr"
来进行查看.这个可执行文件在打包时会被存储在符号表的可执行文件中,保存在xxx.app.dSYM/Contents/Resources/DWARF/路径下.
针对用例中:
otool -arch arm64 -l SymblicateCrashDemo.app.dSYM/Contents/Resources/DWARF/SymblicateCrashDemo | grep "segname __TEXT" --after-context=1 | grep "vmaddr"
可以得到:
vmaddr 0x0000000100000000
这个地址就是编译时存储符号表内容的加载地址.
而在设备加载应用可执行文件到内存时会有一个加载起始地址,可以在完整的系统.crash中可以查看(设备加载的第一个Binary Image起始地址):
...
Binary Images:
0x100e70000 - 0x100f53fff SymblicateCrashDemo arm64 <02162b5d77623cd9b937e2633b497b2e> /var/containers/Bundle/Application/0DF5E4BF-6B77-4EC1-982F-1FCC2C2E0642/SymblicateCrashDemo.app/SymblicateCrashDemo
...
所以&#xff0c;有以上两个地址可以得到在加载时产生的随机偏移量slide&#xff1a;
0x100e70000 - 0x0000000100000000 &#61; 0x0000000000e70000
而实际的调用函数地址为0x0000000100e77770&#xff0c;所以可以得到在符号表中的地址&#xff1a;
0x0000000100e77770-0x0000000000e70000 &#61; 0x00000000100007770
上述slide的值也可以在加载时通过代码获取&#xff1a;
//该方法包含在mach-o/dyld.h声明中,使用时需要引入#import
intptr_t slide_address &#61; _dyld_get_image_vmaddr_slide(0);
最后使用&#xff1a;
dwarfdump --arch {设备对应的指令集} --lookup {函数在符号表中的地址} {符号表(.dSYM文件)}
即
dwarfdump --arch arm64 --lookup 0x00000000100007770 SymblicateCrashDemo.app.dSYM
得到结果&#xff1a;
0x00075e5a: Compile Unit: length &#61; 0x000005a2 version &#61; 0x0004 abbr_offset &#61; 0x0000 addr_size &#61; 0x08 (next unit at 0x00076400)0x00075e65: DW_TAG_compile_unitDW_AT_producer ("Apple clang version 11.0.0 (clang-1100.0.33.8)")DW_AT_language (DW_LANG_ObjC)DW_AT_name ("/Users/ericydong/Desktop/Exercises/CrashDemo/CrashDemo/AppDelegate.m")DW_AT_stmt_list (0x00011e29)DW_AT_comp_dir ("/Users/ericydong/Desktop/Exercises/CrashDemo")DW_AT_GNU_pubnames (true)DW_AT_APPLE_optimized (true)DW_AT_APPLE_major_runtime_vers (0x02)DW_AT_low_pc (0x00000001000076a8)DW_AT_high_pc (0x0000000100007998)0x00075fac: DW_TAG_subprogramDW_AT_low_pc (0x0000000100007708)DW_AT_high_pc (0x000000010000781c)DW_AT_frame_base (DW_OP_reg29 W29)DW_AT_call_all_calls (true)DW_AT_name ("__44&#43;[ExceptionHandler registerExceptionHandler]_block_invoke")DW_AT_decl_file ("/Users/ericydong/Desktop/Exercises/CrashDemo/CrashDemo/AppDelegate.m")DW_AT_decl_line (33)DW_AT_prototyped (true)DW_AT_APPLE_optimized (true)
Line info: file &#39;AppDelegate.m&#39;, line 38, column 39, start line 33