在目前的iOS组件化开发中,将组件进行二进制化已经成为业内比较主流的提升效率的方案,而随着二进制化后,如何对其断点调试,网上也出现众多方案。本文将结合目前网上已知或冷门的各种方案进行介绍和分析,并尽量提炼出一些关键点,希望读到本篇文章的同学对于如何友好调试二进制组件能够一个相对清晰的认识和解决现存问题提出更加简洁的方案。
说到程序断点调试,现代程序断点调试往往需要三个角色(缺一不可):可执行文件、调试器、调试信息文件。而其中的调试信息文件,是我们相对接触更少的角色,所以首先我们将简单介绍调试信息文件,这里我们就讲述目前较为主流的DWARF格式。
DWARF格式是可执行程序和源代码之间关系的一种简洁表示,调试器可以利用其相当有效地处理这种关系。
大多数现代编程语言都是块结构的,每个实体(例如类定义或函数定义)都包含在另一个实体中,故编译器很自然的在内部将程序表现为树。DWARF格式遵循这个模型,因为它也是块结构的,DWARF中每个描述性的实体DIE(除了描述源文件的最顶层DIE之外)都包含在父DIE中,并且可能包含子DIE,如果一个DIE包含多个子DIE,那么它们都是彼此关联的兄弟关系,所以DWARF也是类似于编译器的内部树结构。
DWARF通常与ELF对象文件相关联,但是其独立于ELF。它可以和其他任何目标文件格式一起使用(比如MachO)。所需要做的就是在对象文件或可执行文件中识别组成DWARF格式数据的不同section。
DWARF中的基本描述项为DIE(Debugging Information Entry),DIE具有一个标签,用于指定所描述的内容,以及一个属性列表用于填充详细信息而进一步描述该实体。属性可能包含各种值:常量(例如函数名称),变量(例如函数的起始地址)或对另一个DIE的引用(例如函数的返回值类型)。
上面说到DWARF可以与各种目标文件格式一起使用,这里要讲述的是其在MachO文件格式中的利用。和ELF类似,在MachO中,DWARF几乎使用一样的section名表达其意图,包括:
.debug_abbrev 用在.debug_info section的缩写
.debug_aranges 内存地址与编译单元之间的一个映射
.debug_frame 栈帧信息
.debug_info 包含DIE的核心DWARF数据
.debug_line 行信息
.debug_loc 位置描述
.debug_macinfo 宏的描述
.debug_pubnames 全局对象及函数的一个查找表
.debug_pubtypes 全局类型的一个查找表
.debug_ranges DIE所引用的地址范围信息
.debug_str 由.debug_info使用的字符串表
其中关系到可执行程序和源文件路径的对应关系的数据保存在 .debug_info
section中的 Compile Unit DIE中,其对应的标签为DW_TAG_compile_unit
,我们可以暂时记住它。
由于MachO格式文件存在多种可执行代码的载体,而每种载体对应的调试信息有不同的存储方式,我们接下来分别描述
可执行文件
最常见的当然是一个可执行文件了,其调试信息保存在一个独立的DWARF格式的文件中,后缀为DSYM。
静态库
静态库文件的调试信息直接保存在对象文件中的各个section中,而没有单独文件保存(最终通过链接被合并到宿主工程的DWARF文件中)。
动态库
动态库文件和可执行文件一样,其会生成一个单独的DSYM文件来保存DWARF格式数据。
这三种不同的对象文件的调试信息之所以如此保存,是由于每个调试信息文件对应的是一个运行时的镜像。