全局符号 和本地符号
我们先看一段代码
查看Mach-O的符号
通过如下命令
objdump --macho --syms patch
- 可以看到main.m 生成的mach-O中的符号信息, 全局符号为g
- static 修饰的变量 为 l
所以 l :就是 local 本地的意思 ,g 就是global 全局的意思
按照功能划分
Type | 说明 |
---|---|
f | File |
F | Function |
O | Data |
d | Debug |
*ABS* | Absolute |
*COM* | Common |
*UND* | ? |
调试符号
可以通过 连接器的参数 再链接的时候 不把调试符号放入我们可执行文件
OTHER_LDFLAGS=$(inherited) -Xlinker -S
编译一下 再次查看符号表
- 此时我们发现 调试符号已经被我们脱掉了,更能清晰的对照原始代码来查看
- 调试符号:当一个文件通过汇编器生成.o的时候 会DWARF 格式的调试信息 它被放在了专门的mach-O中的一个 __DWARF段 当去连接的时候它会把整个段变成符号放在符号表中,当链接的时候所有的符号都是放在符号表中的。
DWARF文件 在.o的时候是一个段 ,此时跟符号表并没有产生关系,当变成可执行文件的时候在链接的时候 这个段被干掉最终变成了符号 最终 放在了 可执行文件中exec
visibility
可通过visibility属性,控制文件导出符号,限制符号可见性。- hidden : 用它定义分符号将不被导出
- default: 用它定义的符号将被导出
- protected: 受保护的
__attribute__ : 可以把编译器支持的一些参数传递给编译器(如上面是传入的是控制符号的可见性,那最好的隐藏符号是什么?就是是把全局符号变成本地符号)。
拓展
面试题:全局变量 和静态变量最大区别是什么?
作用域不一样,本质的区别就是可见性。
如果全局变量 那么会对整个项目可见
如是静态变量 则会变成 local-
关于 一个同名 全局方法 本地也写了一个同名方法 调用 是调用 本地的 还是 别的地方的。
直接说答案:此时编译不会报错,并且还会调用到
当我在本地写一个实现:
发现本地的被调用起来了
为什么 会调用起来呢 为啥不冲突 因为连接器默认采用二级命名空间 修改为一级就报错了。
导入和导出符号
是导入 还是导出 相对于来说的,比如你调用了NSLog 那相对于Foundation 库来说就是 导出,相对于你来说就是导入。
那导出符号一定是 全局符号了?从字面上是这样的 那么我来看一下
查看mach-O的导出符号命令
objdump --macho --exports-trie 黑不溜秋的
- 所以我们要注意 当我们使用全局变量 全局符号的 时候 它默认会为导出符号,对外界暴露的,也就意味着 其他空间 可以使用这个符号。
那导出符号一定是全局符号吗?按道理是的 ,但是我们可以通过连接器控制它。
动态库 是在运行的时候加载,那就意味这它在编译连接阶段的时候提供符号就可以了。
间接符号表,保存着当前使用的其他动态库的符号
查看间接符号表
objdump --macho --indirect-symbols 黑不溜秋的
好像只认识NSlog 但是它是Fundation 给我们提供的导出符号
从上面我们可以理解:
1、全局符号可以变成导出符号 ,那么变成导出符号之后 可以给外面使用
2、当我们处理符号,因为符号存在我们Mach-o中是占用一定体积的 。 那能处理能删除间接符号吗?那肯定必须的不能删除啊,为什么?因为对于我们项目mach-o来说 间接符号表 里保存着当前使用着的动态库的符号,而动态库是运行的时候去加载。所以也就意味着 我们在编译链接的时候只需要提供它所用到的符号就行,而这符号就存在 间接符号表里。
3、当我们站在动态库的角度去看,全局符号 可以变成导出符号,供外界使用,那我要去脱符号也就意味着,只能去脱不是全局符号的符号。
这里会有一个问题 假如你的一个动态库 有好多好多的全局符号,并且 是OC 这里提一点 OC默认的都是导出符号。那么为了缩小体积该怎么办?
这就需要链接器给我们提供的一个参数不导出符号
-Xlinker -unexported_symbol -Xlinker _OBJC_CLASS_$_LGOneObjc
这样也就可以把不需要对外暴露的导出符号 变成一个不导出也就是local符号。尽可能的来减小体积。
如果有多个符号不想导出 要如上☝️一个一个得类似,链接器也为我们提供了相应的处理方法
-unexported_symbols_list file
The specified filename contains a list of global symbol names
that will not remain as global symbols in the output file.
The symbols will be treated as if they were marked as __pri-
vate_extern__ (aka visibility=hidden) and will not be global
in the output file. The symbol names listed in filename must
be one per line. Leading and trailing white space are not
part of the symbol name. Lines starting with # are ignored,
as are lines with only white space. Some wildcards (similar
to shell file matching) are supported. The * matches zero or
more characters. The ? matches one character. [abc] matches
one character which must be an 'a', 'b', or 'c'. [a-z]
matches any single lower case letter from 'a' to 'z'.
- 也可以将当前可执行文件 用到了哪些库 所有符号输出出来
-map map_file_path
Writes a map file to the specified path which details all
symbols and their addresses in the output image.
具体信息可以通过 来查看
man ld
按照符号种类划分
Symbol type | 说明 ①:⼩写代表local symbol |
---|---|
U | undefined(未定义) |
A | absolute(绝对符号) |
T① | text section symbol(__TEXT.__text) |
D① | data Section symbol(__DATA.__data) |
B① | bass section symbol(__DATA.bass) |
C | commom symbol (只能出现在‘MH_OBJECT’类型的‘Mach-O’文件中) |
- | debugger symbol table |
S① | 除了上面所述的,存放在其他‘section’的内容,例如未初始化的全局变量存放在(__DATA,__common)中 |
I | indirect symbol (符号信息相同,代表同一符号) |
u | 动态共享库中的小写u表示一个未定义引用对同一库中另外一个模块中私有外部符号 |
通过如下命令真实看一下
nm -pa 地址
Weak Symbol
Weak defintion Symbol :
表示此符号为若定义符号。如果静态链接器或动态链接器为此符号找到另一个非弱定义,则若定义将被忽略。只能将合并部分中的符号标记为弱定义
- 讲道理 如果不对他进行weak修饰,它默认是一个全局符号,还是一个到处符号,我们看一下声明为若定义之后会不会影响导出符号。
将其加入到Compilp Sources里 并编译
查看可执行文件的导出符号
objdump -macho --exports-trie 地址
- 可以看到并不影响 它是一个全局 导出符号。
- 被hidden修饰的weak 符号 变成 了弱定义的本地符号。
那我在main函数里写一个同名实现方法,编译会报错吗?
- 相同的作用域空间,重复的全局符号, 是会报符号冲突的,但是弱定义修饰之后,可以编译成功。
Weak Reference Symbol:
表示此未定义符号是弱引用。如果动态链接器找不到该符号的定义,则将其符号为0.静态链接器会将此符号设置弱链接标志。
- 通过 weak_import 告诉编译器 此符号是弱引用的
此时 运行成功,那有什么意义呢?往下看
此时我们可以把 下图标红的地方去掉,在运行。
运行
咦报错了? 为啥报错,因为在ld连接的过程中 找不到当前符号的地址。
那我们可以通过告诉连接器 这个符号你别管,它是动态链接的,通过如下命令
-U symbol_name
Specified that it is ok for symbol_name to have no defini-
tion. With -two_levelnamespace, the resulting symbol will be
marked dynamic_lookup which means dyld will search all loaded
images.
再次运行
我们想一下 此时他们在同一片作用域空间中,假如 我编写的是一个 动态库,我将所有的符号变为弱引用符号,那是不是意味着 延迟到dyld加载的时候 找到某个符号的时候 再去做事情 Σ(⊙▽⊙"a
重新导出符号
我们先看一下当前可执行文件的符号表
objdump --macho --syms
上面我们将的NSLog 对于我们当前的可执行文件来说,
我们看到了 它的符号表它是一个未定义的符号 *UND*,
那假如说别的 库也想使用我的这个可执行文件中的NSLog怎么办?所以就需要重新导出一下。
同样连接器也给我们提供了相对的接口
-alias symbol_name alternate_symbol_name
Create an alias named alternate_symbol_name for the symbol
symbol_name. By default the alias symbol has global visibil-
ity. This option was previous the -idef:indir option.
- 这也就意味这我们可以通过其别名的形式让它进行一个全局可见性。
- 注意只能给间接符号 表里的符号 起一个别名。
我们试一下
我们先看一下当前的符号表
objdump --macho --syms
- 可以看到我们定义符号已经出现了。
还可以用nm 命令查看一下 可更友好的查看
nm -m
再次查看导出符号
objdump -macho --exports-trie 地址
- 可以看到完美的被我们导出了
OC 的符号 默认都是导出符号,因为它是动态型语言
swift是编译型语言,所以很多符号在编译期就知道是什么类型,可通过public private open 等控制。
Common Symbol
在定义时,未初始化的全局符号
未定义符号作用:
1、当它找到定义之后,在编译连接的时候会把未定义的删掉。
2、 如果是未定义的符号 链接器会把它强制已经定义的 ,比如直接赋值为 0;这就是为什么我定义了一个符号,没有赋值也没有使用的时候xcode会报警告 ,这就是链接器会识别common 符号 按照一定规则 ,如你有未定义的 要么给你强制变成 已定义的, 要么给你报警告 要么报错这是可以选择的。
strip
首先通过 上面文章 我们知道
对于动态库 我们只能去脱不是全局符号的符号。
那对于app来说 我们分析:
1.首先你要考虑要不要给别人使用。那这里这个奇奇怪怪的问题,要不是奇奇怪怪的app 也用不到。
2.通过上面我们也知道 除了间接符号表里的符号不能脱,那是不是意味着别的都可以脱掉 本地的 全局的 弱定义的 都可以干掉 只留下 间接符号表中的。
那对于静态库来说呢?
静态库 是 .o的合集 也就意味着,它里边还有重定位符号表,那重定位符号表里面是啥来,是保存着你项目用到的符号,那它能删吗? 删了那我连接还怎么重定位。那还有什么可以脱 是不是还有调试符号
大家不妨想一想:
那就符号来说
app 使用静态库体积小 还是动态库体积小?
答案是静态库 因为 静态库你只能去脱掉调试符号,那app 是不是除了间接符号表 其他的都可以脱掉?
那动态库 的符号 是不是最终都放在了app的间接符号表中?