在iOS App開發過程當中,咱們會利用Xcode打包,生成.xcarchive的包文件,經過Xcode的Organizer工具能夠管理、導出發佈文件,相信iOS開發對於這些過程都至關的熟悉,這裏就再也不贅述。主要想說的是,打包以後的dSYM文件。數據結構
經過如下方式獲取dSYM文件,首先打開Archives管理窗口,以下圖:架構
每Archive一次,都會生成一條記錄,找到當前記錄所在的目錄,以下圖:app
打開.xcarchive包文件會看到其目錄結構,dSYMs中的dSYM包文件就是咱們接下來要剖析的文件了。dSYM一樣個包文件,打開以後,咱們會找到一個二進制文件,以下圖,例子中是一個叫Demo的二進制文件。工具
從目錄名中,能夠看出iOS使用的是DWARF文件結構,DWARF(可能的解釋是:Debugging With Attributed RecordFormats)是一種調試文件結構標準,結構至關的複雜。關於DWARF的前世此生,從何而來,爲什麼而來,如何發展,請參考DWARF官網或網上搜索。學習
dSYM文件的一個重要的做用在於當咱們的程序發生崩潰,經過crash log或其餘方式,會看到調用棧信息,經過log信息,咱們並不知道具體是在那個文件的哪一個位置出了問題,這個時候這個二進制文件就很是的有用了,經過它咱們能夠經過工具去符號化,好比Xcode自帶的atos,這樣能夠直接定位到某個文件的具體位置。spa
目前有不少的工具能夠解析DWARF文件,好比,Mac OS中就有dwarfdump,otool等,但現有工具並不能知足咱們全部的需求,如今咱們瞭解下其內部結構,在往後開發中有須要,能夠用來參考。debug
下面咱們打開這個二進制文件。注:如下「段」單位爲4個字節。調試
打開文件後,先來看下文件頭,結構定義參考<fat.h>。code
FAT二進制數據orm
第一段爲magic,這裏須要注意字節序,讀出來以後須要看下是0xCAFEBABE仍是0xBEBAFECA,須要根據這個來轉後續讀取的字節的字節序。
第二段爲arch count,也就是該app或dSYM中包含哪些cpu架構,好比armv7,arm64等,這個例子中爲2,表示包含了兩種cpu架構。
後續段中包含cputype(0x0000000C)、cpusubtype(0x00000009)、offset(0x00000040)、size(0x000F6825)等數據,根據fat中的結構定義,依次讀取,這裏須要說明的是,若是隻包含一種cpu架構的話,是沒有這段fat頭定義的,能夠跳過這部分,直接讀取Arch數據。
根據fat頭中讀取的offset數據,咱們能夠跳到文件對應的arch數據的位置,固然若是隻有一中架構的話就不須要計算偏移量了。例子中32-bit arch的offset爲0x00000040,64-bit arch的offset爲0x000F6880。如下數據結構參考<mach.h>。
Mach二進制數據
(32-bit)
(64-bit)
經過magic咱們能夠區分出是32-bit 仍是64-bit,64-bit多了4個字節的保留字段,這裏一樣須要注意字節序的問題,也就是判斷magic,來肯定是否須要轉換字節序。
如下部分解析,以32-bit爲例。
UUID二進制數據
UUID是16個字節(128bit)的一段數據,是文件的惟一標識,前面提到的符號化時,這個UUID必需要和app二進制文件中的UUID一致,才能被正確的符號化。dwarfdump查看的UUID就是這段數據。讀取這部分數據時經過Command結構讀取的,也就是第一段(0x0000001B)表示接下來的數據類型,第二段(0x00000018)數據的大小(包含Command數據)。
SymTab二進制數據
符號表數據塊結構,前二段依然是Command數據。後邊4段分別爲符號在文件中的偏移量(0x00001000)、符號個數(0x00000015)、字符串在文件中的偏移量(0x000010FC)、字符串大小(0x00000297)。
接下來就是讀取Segment和Section數據塊了,和上面讀取數據塊結構同樣是根據Command結構讀取,下圖展現的Segment數據和Section數據是分開的,實際在二進制文件中它們是連續的,也就是每一條Segment數據後面會緊跟着多條對應的Section數據,Section的數據總數是經過Segment結構中的nsects決定的。
Segment數據
從Segment數據中咱們能夠看到, __TEXT的vmaddr是0x00004000,也就是程序的加載地址,固然這個是指32-bit的程序,64-bit是不一樣的。__DWARF中代表了DWARF數據塊的信息,表示dSYM是DWARF格式的數據結構。
Section數據
以上爲Section數據的一部分,從Section數據中,咱們能夠找到__debug_info, __debug_pubnames, __debug_line等調試信息,經過這些調試信息咱們能夠找到程序中符號的起始地址、變量類型等信息。若是咱們要符號化的話,就能夠經過解析這些數據獲得咱們想要的信息。
關於Segment和Section中類型的定義,請參考DWARF官網。
關於如何解析解析數據獲得符號在文件中的位置,下篇再作分享。
到這裏咱們已經讀取了符號文件頭中的大部分數據,在文件頭裏還有一部分數據也是很重要的,就是符號塊數據,他是咱們程序裏全部的方法信息。
Symbol二進制數據
經過SymTab中的數據能夠獲得Symbol在文件中的位置和個數,Symbol塊數據中包含了符號的
起始地址,字符串的便宜量等數據,這部分數據結構能夠參考<nlist.h> 和 <stabl.h>。這部分數據所有讀取後,就能夠讀取全部的符號數據了,也就是接下來的數據。
Symbol String二進制數據
經過SymTab和Symbo中的數據能夠獲得每一個符號字符串在文件中的偏移量和大小,每一個符號數據是以0結尾的字符串。
咱們經過以上兩部分數據的組合就能夠獲得每一個symbo在程序中的加載地址了。這些數據對於之後作符號工做都很是的有幫助。64-bit的數據解析與以上方法相同,不過要注意64-bit中的有些數據是有點差異的,解析時須要注意。
到此,關於dSYM文件中頭部數據讀取就完成了。頭部數據都有相應的數據結構定義,讀取時相對會比較容易些,解析數據時要注意字節序的問題,32-bit和64-bit數據結構的差別、字節長度的差別,DWARF版本的差別,每一個數據塊之間都是緊密聯繫的,一個字節的讀取誤差就會形成後續數據的讀取錯誤,正所謂差之毫釐,失之千里。
本文由TestinAPM李明湘原創
欲知更多幹貨,請訪問apm.testin.cn
或加入學習小組qq羣:232336330