符号
符号(Symbol)常用来表示一个地址,这个地址可能是一端程序的起始地址,也可能是一个变量的起始地址,简而言之,将它当做是标记或名称即可。
上一节聊到的链接
过程,实质上就是将不同的目标文件汇集在一起。链接构成中,目标文件的相互拼合汇集,实际上是目标文件之间对地址的引用。链接过程中,我们将函数
和 变量
统一称作为符号
,函数名与变量名称就是符号名
,其记录的地址信息就是其符号值
。
符号分类
符号是有分类的,根据其特点,可分为内部符号和外部符号。
- 内部符号:内部函数或方法、变量名称(当前Mach-O)
- 如:AppDelegate,编译期就知道其地址,分为:
- 全局符号 :全局作用域的函数、符号,整个项目可见
- 本地符号 :当前文件内的符号,当前文件可见
- 如:AppDelegate,编译期就知道其地址,分为:
- 外部符号:外部函数或方法、变量名称(非当前Mach-O)
- 如:NSLog,这类符号又分为:
- 导入符号:对于当前 mach-O文件,导入了
NSLog
符号,称为:导入符号 - 导出符号:对于Foundation 来说,导出了
NSLog
符号,称为:导出符号
- 导入符号:对于当前 mach-O文件,导入了
- 导出符号,一定是全局符号,可推理出,全局符号不能被脱去
- 编译链接时不知道其地址,在动态链接的时候才能确定它的内存地址
- 统一位于
重定位符号表
又叫**间接符号表**
中
- 如:NSLog,这类符号又分为:
- 特殊符号:链接器生成可执行文件时生成的特殊符号等
- __executable_start, 程序的起始地址,非入口地址
- _end 或 end, 程序结束地址
? 为什么外部符号需要在动态链接时才能确定它的内存地址?
本地符号通过地址偏移可以得到,但是由于Mach-O 还没被加载,程序虚拟内存空间还没有被分配,导入的外部符号还没有内存地址。
全局符号
为了说明全局符号,在上述hello.c
文件中添加如下代码
#include <stdio.h>
#define MAX_AGE 120
int globalVal = 99;
int globalUndefineVal;
int main() {
printf("%s\n", "hello world~");
printf("%d\n", MAX_AGE); // 简单输出
return 0;
}
复制代码
执行编译输出a.out
查看,可见
- _globalVal 和 _globalUndefinedVal 符号签名都是
g
说明是全局符号 - _globalVal 和 _globalUndefinedVal 符号都在_DATA 段,但是一个在 __data 一个在 __common
本地符号
本地符号又叫静态符号,它和全局符号的区别是在可见性方面。为了对比本地符号,在上述hello.c
文件中添加如下代码
int globalUndefineVal;
static int staticVal = 88; # 静态变量
static int staticUndefineVal; # 未初始化的静态变量
int main() {
printf("%d %d\n", staticVal, staticUndefineVal); // 简单输出
...
}
复制代码
注意:如果静态变量
没有进行引用
的话,会变成调试符号,无法输出
导入导出符号
导入与导出是相对的,一方的导出符号,可以作为另一方的导入符号,查看一下 a.out
的导出符号
objdump --macho --exports-trie a
复制代码
可见,导出符号就是前面见到的 全局符号
,全局符号默认是导出符号
,当然也可以隐藏全局符号
,如下示例
int globalVal2 __attribute__((visibility("hidden"))) = 99;
复制代码
通常,用到的外部符号会被放入到间接符号表
中,查看一下 a.out
的间接符号表
objdump --macho --indirect-symbols a.out
复制代码
符号表
链接过程中,我们需要对各种符号
进行管理,每一个目标文件
都会有一个对应的 符号表 Symbol Table
,用来记录目标文件中所有用到的符号,在静态链接时候,会进行相同性质段的合并。
符号表分类
- Symbol Table: 所有符号表
- 存储所有的符号信息,包含动态链接符号表内的符号
- Dynamic Symbol Table:动态链接符号表,间接符号表(Indirect Symbol)
- 存储着所有使用到的
位于其他外部动态库中的符号信息
,动态链接时进行绑定
- 存储着所有使用到的
- String Table:符号的名称
常用命令
-----------------------------------------
# -a输出全部符号表,-p 不用排序
$ nm -pa hello.o
0000000000000000 T _main
U _printf
$ nm -pa a
0000000100008008 d __dyld_private
0000000100000000 T __mh_execute_header
0000000100003f30 T _main
U _printf
U dyld_stub_binder
-----------------------------------------
# 查看 Mach-Header 内容
$ otool -h a
a:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
0xfeedfacf 16777223 3 0x00 2 16 1368 0x00200085
$ objdump --macho --private-header a
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC_64 X86_64 ALL 0x00 EXECUTE 16 1368 NOUNDEFS DYLDLINK TWOLEVEL PIE
-----------------------------------------
# 查看 __TEXT 代码段 内容
$ objdump --macho -d a
a:
(__TEXT,__text) section
_main:
100003f30: 55 pushq %rbp
100003f31: 48 89 e5 movq %rsp, %rbp
100003f38: c7 45 fc 00 00 00 00 movl $0, -4(%rbp)
...
100003f75: 5d popq %rbp
100003f76: c3 retq
-----------------------------------------
# 查看符号
objdump --macho -syms hello.o
hello.o:
SYMBOL TABLE:
0000000000000048 g O __DATA,__data _globalVal
0000000000000000 g F __TEXT,__text _main
0000000000000004 *COM* 0000000000000004 _globalUndefineVal
0000000000000000 *UND* _printf
-----------------------------------------
# 查看导出符号
$ objdump --macho --exports-trie a
a:
Exports trie:
0x100000000 __mh_execute_header
0x100003F30 _main
0x100008010 _globalVal
0x100008014 _globalUndefineVal
-----------------------------------------
# 查看间接符号
$ objdump --macho --indirect-symbols a
a:
Indirect symbols for (__TEXT,__stubs) 1 entries
address index name
0x0000000100003f78 5 _printf
Indirect symbols for (__DATA_CONST,__got) 1 entries
address index name
0x0000000100004000 6 dyld_stub_binder
Indirect symbols for (__DATA,__la_symbol_ptr) 1 entries
address index name
0x0000000100008000 5 _printf
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END