iOS dSYM文件结构剖析
CSDN移动将持续为您优选移动开发的精华内容,共同探讨移动开发的技术热点话题,涵盖移动应用、开发工具、移动游戏及引擎、智能硬件、物联网等方方面面。如果您想投稿、参与内容翻译工作,或寻求近匠报道,请发送邮件至tangxy#csdn.net(请把#改成@)。
在iOS App开发过程中,我们会利用Xcode打包,生成.xcarchive的包文件,通过Xcode的Organizer工具可以管理、导出发布文件,相信iOS开发对于这些过程都相当地熟悉,这里就不再赘述。主要想说的是,打包之后的dSYM文件。
通过以下方式获取dSYM文件,首先打开Archives管理窗口,如下图:
每Archive一次,都会生成一条记录,找到当前记录所在的目录,如下图:
打开.xcarchive包文件会看到其目录结构,dSYMs中的dSYM包文件就是我们接下来要剖析的文件了。dSYM同样个包文件,打开之后,我们会找到一个二进制文件,如下图,例子中是一个叫Demo的二进制文件。
从目录名中,可以看出iOS使用的是DWARF文件结构,DWARF(可能的解释是:Debugging With Attributed RecordFormats)是一种调试文件结构标准,结构相当的复杂。关于DWARF的前世今生,从何而来,为何而来,如何发展,请参考DWARF官网或网上搜索。
dSYM文件的一个重要作用就在于,当我们的程序发生崩溃,通过crash log或其他方式,会看到调用栈信息。通过log信息,我们并不知道具体是在那个文件的哪个位置出了问题,这个时候这个二进制文件就非常的有用了,通过它我们可以通过工具去符号化,比如Xcode自带的atos,这样可以直接定位到某个文件的具体位置。
目前有很多的工具可以解析DWARF文件,比如,Mac OS中就有dwarfdump、otool等,但现有工具并不能满足我们所有的需求,现在我们了解下其内部结构,在日后开发中有需要,可以用来参考。
下面我们打开这个二进制文件。注意:以下“段”单位为4个字节。
打开文件后,先来看下文件头,结构定义参考<fat.h>。
FAT二进制数据
第一段为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版本的差异,每个数据块之间都是紧密联系的,一个字节的读取偏差就会造成后续数据的读取错误,正所谓差之毫厘,失之千里。作者简介:李民湘,Testin崩溃分析项目高级iOS开发工程师