在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