iOS Crash log符號化庖丁解牛

項目在做zombie內(nèi)存監(jiān)測的時候有把zombie調(diào)用棧和oc對象釋放棧報上來,由于我們的crash組件是用的第三方組件,zombie棧沒法和crash log一起符號化,要自己對棧進行符號化,研究了一下CrashLog的還原原理和方法。

Crash Log符號還原方法

使用xcode

這是最簡單的方法,要使用xcode進行符號還原需要下面三個文件:

Crash Reports(.crash文件)
符號文件 (.dsymb文件)
應(yīng)用程序文件 (appName.app文件)

把這3個文件放到同一個目錄下,打開Xcode的Window菜單下的organizer,然后點擊Devices tab,然后選中左邊的Device Logs。然后把.crash文件拖到Device Logs或者選擇下面的import導(dǎo)入.crash文件。

使用symbolicatecrash

symbolicatecrash是xcode自帶的工具
將“.app“, “.dSYM”和 “.crash”文件放到同一個目錄下,終端設(shè)置如下環(huán)境:

export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer

然后輸入下面命令

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash appName.crash appName.app > appName.log

使用atos命令行

前面兩種方法足夠簡單,但都不適合腳本自動化,并且我們也沒有原始.crash文件,
本文重點介紹這種方法和內(nèi)部的原理。
atos命令可以對指定的地址進行符號化

NAME
atos -- convert numeric addresses to symbols of binary images or processes
SYNOPSIS
atos [-o <binary-image-file>] [-p <pid> | <partial-executable-name>] [-arch architecture][-l <load-address>] [-s <slide>] [-printHeader] [-v] [-D] [-f <address-input-file>] [<address> ...]
DESCRIPTION
The atos command converts numeric addresses to their symbolic equivalents. If full debug symbol infor-mation informationmation is available, for example in a .app.dSYM sitting beside a .app, then the output of atos will
include file name and source line number information.

符號還原原理

首先來看下需要還原的棧長啥樣

0   AppName                     0x00000001009e3110 _ZNSt3__111char_traitsIcE2eqEcc + 7884972
1   AppName                     0x0000000100620f04 _ZNSt3__111char_traitsIcE2eqEcc + 3944096
2   CoreFoundation              0x0000000188e58f60 <redacted> + 132
3   CoreFoundation              0x0000000188d5280c _CF_forwarding_prep_0 + 92

看??梢灾烂總€frame包括image名、代碼地址、代碼地址對應(yīng)的符號信息,現(xiàn)在代碼地址對應(yīng)的符號是一串奇怪東東,我們要做的就是把這串奇怪的東東還原成可讀的信息,包括函數(shù)名、原文件名、代碼行。
要還原符號信息必須解決下面問題

  • 從哪里找函數(shù)名、原文件名、代碼行這些信息
  • 怎么找這些信息

從哪里找函數(shù)名、原文件名、代碼行這些信息

dSYM和DWARF

dSYM(debugging SYMbol)是從Mach-O文件中抽取調(diào)試信息而得到的文件目錄,發(fā)布的時候為了安全和減小安全,一般會把調(diào)試信息存儲在單獨的文件,dSYM實際是一個文件目錄,其目錄結(jié)構(gòu)如下:

 |--AppName.app.dSYM
    |--Contents
      |--info.plist
      |--Resources
        |--DWARF
          |--AppName

dSYM符號信息實際存儲在DWARF文件里面,DWARF (DebuggingWith Arbitrary Record Formats)是起源貝爾實驗室的一種調(diào)試信息文件格式,是ELF和Mach-O等文件格式中用來存儲和處理調(diào)試信息的標(biāo)準(zhǔn)格式。
DWARF文件包含所有調(diào)試信息,并且以section的形式進行存儲,DWARF使用DIE(Debugging Information Entry)來存儲具體信息,DIE通過樹結(jié)構(gòu)組織,DIE可以有兄弟節(jié)點和子節(jié)點。DWARF文件包括下面section:

.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

其中主要信息存儲在debug_info和debug_line里,debug_info存儲了函數(shù)信息、變量信息等,debug_line存儲了對應(yīng)源代碼行數(shù)信息??梢杂胐warfdump工具讀取dwarf文件里的section。使用dwarfdump讀取下面demo dSYM文件的section

@interface DSYMDemo : NSObject
@property (nonatomic, strong) NSString* var1;
- (NSString*)test;
@end
debug_info

使用dwarfdump讀取DWARF文件debug_info信息

dwarfdump -e --debug-info DSYMDemo.app.dSYM/Contents/Resources/DWARF/DSYMDemo > debug-info.txt
0x00034d29:     function [119] *
                low pc( 0x0000000100006adc )
                high pc( 0x0000000100006b14 )
                frame base( reg29 )
                object pointer( {0x00034d49} )
                name( "-[DSYMDemo setVar1:]" )
                decl file( "/Users/haishengding/Desktop/test/DSYMDemo/DSYMDemo/DSYMDemo.h" )
                decl line( 13 )
                prototyped( 0x01 )
                artificial( 0x01 )
                APPLE optimized( 0x01 )

可以看到DIE里面包括了函數(shù)開始地址、結(jié)束地址、函數(shù)名、原文件名、開始地址在原文件的行數(shù)。對于給定的地址,找到函數(shù)開始地址和結(jié)束地址之間包含改地址的DIE,則可以還原函數(shù)名和原文件名。

debug_line

通過debug_info還原了函數(shù)名、原文件名,剩下原文行數(shù)則通過debug_line進行還原

dwarfdump -e --debug-line DSYMDemo.app.dSYM/Contents/Resources/DWARF/DSYMDemo > debug-line.txt
Address                Line  File
------------------ ------ ------------------------------
0x0000000100006ac0     13 ~/Desktop/test/DSYMDemo/DSYMDemo/DSYMDemo.m
0x0000000100006ac0     14
0x0000000100006acc     13 ~/Desktop/test/DSYMDemo/DSYMDemo/DSYMDemo.h
0x0000000100006acc     13
0x0000000100006adc     13
0x0000000100006aec      0
0x0000000100006b14     11 ~/Desktop/test/DSYMDemo/DSYMDemo/DSYMDemo.m
0x0000000100006b14     11
0x0000000100006b28     11

可以看到debug_line里面包含了每個代碼地址對應(yīng)的行數(shù)。

怎么找這些信息

已經(jīng)知道對于指定的地址,通過dSYM文件可以還原符號信息,但怎么拿到這個地址呢?開始的函數(shù)棧frame有一個地址,就是這個地址嗎?當(dāng)然沒這么簡單,我們知道image加載的時候都會相對基地址進行重定位,并且每次加載的基地址都不一樣,函數(shù)棧frame的地址是重定位后的絕對地址,我們要的是重定位前的相對地址。

Binary Images

Binary Images:
0x100050000 - 0x101c07fff +AppName arm64 <ab90a1c5646f35dca8f8cf1ce74c767c> /var/containers/Bundle/Application/175ED3FA-7329-49DE-B54A-88EEC120412C/AppName.app/AppName
0x187ee6000 - 0x187eeffff  libsystem_pthread.dylib arm64 <d8480fc3a35d3475b0d12553c761d8cb> /usr/lib/system/libsystem_pthread.dylib
0x187e04000 - 0x187e28fff  libsystem_kernel.dylib arm64

可以看到Crash Log的Binary Images塊包含每個image加載起止地址、image名、arm架構(gòu)、uuid、image路徑。

frame
0   AppName   0x00000001009e3110 _ZNSt3__111char_traitsIcE2eqEcc + 7884972
Binary Image
0x100050000 - 0x101c07fff +AppName arm64 <ab90a1c5646f35dca8f8cf1ce74c767c> /var/containers/Bundle/Application/175ED3FA-7329-49DE-B54A-88EEC120412C/AppName.app/AppName

frame0的地址0x00000001009e3110-0x100050000 就是函數(shù)的相對地址,使用改地址通過dSYM文件就可以還原符號。

UUID

符號還原的時候必須通過匹配的dSYM,dSYM和image是通過UUID進行關(guān)聯(lián)的,兩者的UUID必須一樣才能正確還原,image的UUID在Binary Images可以拿到,dSYM 的UUID可以通過dwarfdump讀取

dwarfdump -u -arch arm64 AppName.app.dSYM/Contents/Resources/DWARF/AppName
UUID: AB90A1C5-646F-35DC-A8F8-CF1CE74C767C (arm64) AppName.app.dSYM/Contents/Resources/DWARF/AppName

可以看到讀取的跟image里的是一樣的。

使用atos命令行

上面講了符號還原的原理,實際上atos工具幫我們做了這些事情,只要通過簡單的命令就看還原了

atos -o AppName.app.dSYM/Contents/Resources/DWARF/AppName -arch arm64 -l 0x100050000 0x00000001009e3110
currentCallStack (in AppName) (xxx.m:17)

其中0x100050000是image加載地址,* 0x00000001009e3110*是需要符號還原的絕對地址,atos自己會轉(zhuǎn)成相對地址

系統(tǒng)符號

dSYM文件只有我們自己代碼的符號,系統(tǒng)函數(shù)則必須通過系統(tǒng)符號文件進行還原,系統(tǒng)符號一般存儲在~/Library/Developer/Xcode/iOS DeviceSupport目錄

0x187ee6000 - 0x187eeffff  libsystem_pthread.dylib arm64 <d8480fc3a35d3475b0d12553c761d8cb> /usr/lib/system/libsystem_pthread.dylib
OS Version: iPhone OS 10.2 (14C92)

通過Crash Log文件Bianry Images和OS Version信息可找到對應(yīng)的符號文件。

腳本化

知道怎么還原具體frame棧楨的符號了,通個腳本解析每個棧楨就可以自動化還原整個棧了,像buggly平臺應(yīng)該也是用類似的方案。

以上內(nèi)容為本人工作學(xué)習(xí)中所得,如有錯誤之處,還請指出!

參考文件

Symbolicating Your iOS Crash Reports
Understanding and Analyzing Application Crash Reports
Introduction to the DWARF Debugging Format

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容