@qidiandasheng
2022-08-23T09:34:16.000000Z
字数 4120
阅读 5450
iOS调试&工具
一个典型的ELF可重定位目标文件包含下面几个节:
... ...
.symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量信息。一些程序员错误地认为必须通过-g选项来编译程序才能得到符号表信息。实际上,每个可重定位目标文件在.symtab中都有一张符号表。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的条目。
... ...
.debug:一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件。只有以-g选项调用编译驱动程序时才会得到这张表。
... ...为了构造可执行文件,链接器必须完成两个主要任务:
符号解析(symbol resolution)。目标文件定义和引用符号。符号解析的目的是将每个符号引用刚好和一个符号定义联系起来。
重定位(relocation)。编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义与一个存储器位置联系起来,然后修改所有对这些符号的引用,使得它们指向这个存储器位置,从而重定位这些节。
这里主要的就是一个路径的问题,比如我们链接某个动态库或静态库到我们工程的时候,如果是使用的绝对路径,那给别人用的时候或者团队合作的时候就需要修改这个路径,会很麻烦,所有在Search Paths
里常常会有这样一个设置:$(SRCROOT)
或${PODS_ROOT}
,当你写入进去的时候,就会发现他会自动变成当前工程或Pods所在的目录。
这里有两个很类似的选项,User Header Search Paths
和Header Search Path
,一般带Users的是表示用户自定义的头文件的搜索路径。
Header Search Paths 顾名思义就是用来存放 Project 中头文件的搜索根源,没有被add到项目里的头文件,可以通过配置Header Search Paths 来引入头文件,这样的好处可以不让project 包含的文件太多,便于管理。
浅显一点的区别是,编码时候通过 #include 引入头文件的方式有两种 <> 和 ""。<> 是只从
Header Search Paths
中搜索, 而 "" 则能从Header Search Paths
和User Header Search Paths
中搜索。换言之 ,假如你把 路径加到User Header Search Paths
中,那么 你用#include <file.h>
的方式去引入对应的头文件,就会报错。 如果加到Header Search Paths
, 就没有问题了。具体一点的区别是,<> 是从系统目录空间 (对应 Header Search Paths)中搜索文件, "" 是从用户目录空间(对应 User Header Search Paths)中搜索文件。如果你把路径加到 User Header Search Paths 中,而 <> 无法从系统目录空间中找到新加的路径,从而报错。
所以如cocoapods这样安装第三方库的话,cocoapods会在Header Search Path
写入对应库的路径,比如:"${PODS_ROOT}/Headers/Public/AFNetworking"
,而User Header Search Paths
会是空的。
在(User) Header Search Path
这个里面的每一个路径后面都会有一个选项recursive
或non-recursive
。这个表示是否递归搜索头文件。
有时候我们在#import的时候编译器会没有代码补全头文件,只能我们手动输入头文件的全名,这样是很不方便的。
比如我们用cocoapods引入某个.framework库的时候,默认路径后面都为non-recursive
,也就是不递归搜索头文件,也就不会有代码补全。而且引入也要#import "frameworkName/public.h"这样引入,告诉编译器是哪个framework下的头文件。
但是如果我们在User Header Search Paths
下输入${PODS_ROOT}
并设置为recursive
,那编译器就会帮你找到所有Pods目录下的头文件,包括framework里的所有头文件。这样你在#import的时候就会有自动补全提示了,而且引入framework的头文件也只需要这样引入#import "public.h"
。但是这样带来的问题就是会浪费编译器的时间。
优先搜索User Header Search Paths
路径下的文件,这种情况下如果有同名的头文件,那么User Header Search Paths
就会覆盖Header Search Paths
里的文件。
Project 的 Building Settings 中得设置默认并不被 Targets 继承
只有 Targets 的设置加入了 $(inherited) 时才被继承,添加目录的时候写上 $(inherited) 就表示从 Frameworks 里面读取。
一个程序从简单易读的代码到可执行文件往往要经历以下步骤:
源代码 > 预处理器 > 编译器 > 汇编器 > 机器码 > 链接器 > 可执行文件
源文件经过一系列处理以后,会生成对应的.obj文件,然后一个项目必然会有许多.obj文件,并且这些文件之间会有各种各样的联系,例如函数调用。链接器做的事就是把这些目标文件和所用的一些库链接在一起形成一个完整的可执行文件。
有时候我们在安装第三方Pod的时候,在运行时会出现selector not recognized
这样的崩溃。那可能就是因为这个第三方Pod里面有分类,Objective-C
的链接器并不会为每个方法建立符号表,而是仅仅为类建立了符号表。这样的话,如果静态库中定义了已存在的一个类的分类,链接器就会以为这个类已经存在,不会把分类里的方法加入到这个类的method list
里面。这样的话,在最后的可执行文件中,就会缺少分类里的代码,这样函数调用就失败了。
所以我们需要在Other Linker Flags
里加入需要链接的静态库和对应的参数,这里的参数主要有以下三个:
-ObjC
加了这个参数后,链接器就会把静态库中所有的Objective-C类和分类都加载到最后的可执行文件中,虽然这样可能会因为加载了很多不必要的文件而导致可执行文件变大,但是这个参数很好地解决了我们所遇到的问题。
-all_load
当静态库中只有分类而没有类的时候,-ObjC参数就会失效了(可以在静态库中加一个空的OC的类)。这时候,就需要使用-all_load
或者-force_load
了。
-all_load
会让链接器把所有找到的目标文件都加载到可执行文件中,但是千万不要随便使用这个参数!假如你使用了不止一个静态库文件,然后又使用了这个参数,那么你很有可能会遇到ld: duplicate symbol
错误,因为不同的库文件里面可能会有相同的目标文件,所以建议在遇到-ObjC
失效的情况下使用-force_load
参数。
-force_load
-force_load
所做的事情跟-all_load
其实是一样的,但是-force_load
需要指定要进行全部加载的库文件的路径,这样的话,你就只是完全加载了一个库文件,不影响其余库文件的按需加载。
一般我们在使用cocoapods的安装第三方库时,cocoapods默认会生成静态库,所以cocoapods会把这些静态库加入到Other Linker Flags
里面,cocoapods安装完后我们能看到Other Linker Flags
里有$(inherited)
、-ObjC
、-|libName
、-framework
换行 "framworkName"
这些参数。每一个"framworkName"
上面都会有一个-framework
。
其实我们把除$(inherited)
外的都删了也不会有错,删了之后我们把鼠标悬浮在Other Linker Flags
的参数上面,还是能看到跟之前一样的配置,只是双击点进去就没了。应该就是$(inherited)
继承了上一层的配置,但是这个上一层在哪呢?
设置Write Link Map File
为YES,就能在Products
里的文件所在的目录找到了。
/Users/dasheng/Library/Developer/Xcode/DerivedData/dfc_v2-guuwzafnatopzhgxrftmngjzjcnj/Build/Intermediates/dfc_v2.build/Release-iphoneos/dfc_v2.build/dfc_v2-LinkMap-normal-armv7s.txt
可以通过一些配置来Hook一些编译的阶段。
xcode在编译每一个类时使用的是clang,可以在User-Defined
里通过配置CC
和CXX
来自定义clang。
xcode在最后的链接阶段生成目标文件会通过clang,可以在User-Defined
里通过配置LD
和LDPLUSPLUS
来自定义clang。
xcode在生成每一个pod库对应的静态库时会使用libtool工具,可以在User-Defined
里通过配置LIBTOOL
来自定义libtool。