異常日誌符號化
一般情況下如果我們可以通過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 ...
所以,有以上兩個地址可以得到在加載時產生的隨機偏移量slide:
0x100e70000 - 0x0000000100000000 = 0x0000000000e70000
而實際的調用函數地址爲0x0000000100e77770,所以可以得到在符號表中的地址:
0x0000000100e77770-0x0000000000e70000 = 0x00000000100007770
上述slide的值也可以在加載時通過代碼獲取:
//該方法包含在mach-o/dyld.h聲明中,使用時需要引入#import <mach-o/dyld.h> intptr_t slide_address = _dyld_get_image_vmaddr_slide(0);
最後使用:
dwarfdump --arch {設備對應的指令集} --lookup {函數在符號表中的地址} {符號表(.dSYM文件)}
即
dwarfdump --arch arm64 --lookup 0x00000000100007770 SymblicateCrashDemo.app.dSYM
得到結果:
0x00075e5a: Compile Unit: length = 0x000005a2 version = 0x0004 abbr_offset = 0x0000 addr_size = 0x08 (next unit at 0x00076400) 0x00075e65: DW_TAG_compile_unit DW_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_subprogram DW_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+[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 'AppDelegate.m', line 38, column 39, start line 33