1.Macho概述
Macho是iOS、iPadOS、MacOS存储程序和库的文件格式。是通过编译器编译生成的.o文件链接合成。文件
特征可读可写。
Macho内容组成:
Macho文件配置信息查看指令:
objdump --macho --private-headers macho文件名
符号表查看所有符号指令:
nm --pa macho文件名或.o文件名
代码段查看指令:
objdump --macho -d macho文件名或.o文件名
2.连接概述
将多个由.m文件编译生成的目标文件(.o格式)合并成一个Mach-O可执行文件的过程,本质上是将多个目标文件
的符号表合并到一起。
3.符号表大致概述
.o文件链接合成Macho文件后会生成一张最终的符号表Symbol Table;
Symbol Table:所有符号及符号地址都存在这张表中。
String Table:用来保存符号的名称。
Indirect Symbol Table:间接符号表,保存使用的外部符号。更准确一点就是使 用的外部动态库的符号。是Symbol Table的子集。
4.符号种类与作用
重定位符号:
在链接生成Macho文件之前,通过编译生成.o文件时会将符号进行归类,.o文件使用到的外部符号(即.m文件用
到api)就会暂存到重定位符号表,在链接生成Macho文件后会统一存到Symbol Table中。
当前.o文件重定位符号查看指令:objdump -—macho -—reloc .o目标文件
案例说明:新建一个只有main.m文件的工程,代码如下:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
NSLog(@"Hellow World");
return 0;
}
分别编译main.m文件和整个工程得到main.o文件和macho文件,通过分别查看两个文件代码
段对比发现main.o文件的callq _NSLog 调用是没有地址的,也就是说NSLog是一个外部符号,需要通过符号的
地址来找到对应的符号
main.o文件的重定位符号表查看结果如下及符号表中对应的字段说明:
全局符号与本地符号
概述:全局符号即整个工程可见的符号比如定义一个全局变量,所有文件都能访问到,本地符号即当前文件可访问,比如
在当前文件定义一个static修饰的变量。
查看指令:objdump --macho --syms .o或.macho文件
例如在main.m文件中定义了一个全局变量和一个static变量代码如下:
int global_int_value = 10;
static int static_int_value = 20;
int main(int argc, const char * argv[]) {
NSLog(@"%d",static_int_value);
return 0;
}
通过上述查看指令输出main.o文件中用到的全局与本地符号如下图所示:
输出的符号中各标签的含义
l:表示本地符号;
g:表示全局符号;
O:表示数据;
F:表示方法
d:表示调试符号(在链接过程通过OTHER_LDFLAGS = -Xlinker -S指令隐藏)
隐藏全局符定义方式:int global_int_value __attribute__((visibility("hidden")))
__attribute__((visibility("hidden")))指令将会使一个全局符号变成本地符号;
__attribute__((visibility("default")))指令会使一个本地符号变成一个全局符号;
导出符号
概述:比如main.m文件中使用到的外部方法,都是该方法所属文件的导出符号.
查看指令:objdump --macho --exports-trie .o或.macho文件
结论:在Objective-C中所有的符号都默认是导出符号,导出符号也是全局符号。自定义动态库可以在链接的时候通过以下指令将全局符号变成本地符号,以此达到剥离不想导出的符号来达到减小动态库体积的目的。
不导出指令:OTHER_LDFLAGS =$(inherited) -Xlinker -unexported_symbol -Xlinker +符号名
举例说明:
工程文件如下:
BodyObject.h(无代码)
BodyObject.m(无代码)
main.m
int main(int argc, const char * argv[]) {
return 0;
}
通过编译连接生成的macho分别输出的全局符号与导出符号结果以及隐藏导出符号之后结果示意图如下图所示,可以看到
隐藏导出符号之后便不再显示,隐藏的导出符号也由全局符号变成本地符号。
间接符号
概述:使用的外部符号叫间接符号,例如使用的外部动态库符号,间接符号存在间接符号表中,链接合成macho文件时会统一存到
Symbol Table中。
查看指令:CMD = objdump --macho --indirect-symbols .o文件或macho
举例说明:
工程文件如下:
BodyObject.h(无代码)
BodyObject.m(无代码)
main.m
int main(int argc, const char * argv[]) {
NSLog(@"Hello World");
}
间接符号输出如下图所示:
NSLog属于Foundation动态库;
adress字段表示间接符号链接地址,name 表示符号名
弱符号Weak Symbol
Weak Reference Symbol: 表示此未定义符号是弱引用。如果动态链接器找不到该符号的定义,则将其设置为0。链接器会将此
符号设置弱链接标志。
Weak defintion Symbol: 表示此符号为弱定义符号。如果静态链接器或动态链接器为此符号找到另一个(非弱)定义,则弱定义
将被忽略。只能将合并部分中的符号标记为弱定义。
弱定义符号举例说明:
BodyObject.h
BodyObject.m
int global_int_value;
main.m
int global_int_value;
在main.m与BodyObject.m分别定义一个全局变量global_int_value,编译器会报错duplicate symbol for
architecture x86_64(符号重定义);
解决报错:将任意一个文件中的全局变量声明为弱定义符号 int global_int_value __attribute__((weak));链接的过程
中只要找到一个名为global_int_value的弱定义的符号就不会往下找同名的符号。
弱引用举例说明:
BodyObject.h
void test(void) __attribute__((weak_import));定义一个没有实现体的弱引用方法test
BodyObject.m
main.m
if(test){
test()
}
在main.m文件中调用test方法,编译报错
报错信息:文件在链接的时候没有找到_test符号(Undefined symbols for architecture x86_64:"_test", referenced
from:_main in main.o)
解决方案:OTHER_LDFLAGS =$(inherited) -Xlinker -U -Xlinker _test (通过此命令在链接的过程中告诉编译器
这是个没有定义的弱引用符号需要去动态查找),此时可以正常编译成功在执行时if(test)条件判断会是0,因为test没有方法实现体,就不会往下执
行,程序也不会闪退。
提示:将动态库声明为弱引用;
总结TIPS
1.App链接静态库时,静态库的所有符号包括导出符号和全局符号最后会被放到App的Symbol Table中,也就意味着静态库的符号在App的符号表中可能会变成本地符号或导出符号,App除了要保留间接符号表之外,其他的符号都可以通过strip剥离(一般情况下App不会向外界提供符号)。 2.App链接动态库时,App使用到的动态库的导出符号和全局都会存到App符号间接表中,不能通过strip剥离,否则会出现找不到符号的错误。所以就符号而言使用静态库的App相比使用动态库的App体积要小一些。