1. 符号表

  • 符号表(Symbol Table):保存符号的在字符串表的位置信息,包含方法名、函数名、类名.
  • 字符串表(String Table): 保存符号名字的。
  • 间接符号表(Indirect Symbol Table):保存间接符号的,即使用到的外部动态库的符号。例如使用到 Foundation 库里面的 NSLog方法。NSLog符号就会存在间接符号表中。间接符号表是符号表的子集。对于当前MACH-O ,NSLog是未定义的符号。如果这个MACH-O被另外的一个项目A使用,想在A中使用NSLog,则需要将NSLog重新导出,通过起别名的方式,可以将符号重新导出。
  • 重定位符号表:在生成.o文件的时候,使用到的别的库的符号,会放到重定位符号表中,等链接后,会被放到间接符号表中。

image.png

2. 命名空间

flat_namespace: 一级命名空间, 符号表中存储的符号名就是自身符号名
two_levelnamespace: 二级命名空间, 符号表中存储的符号名 = 库名 + 符号名
动态库的链接器默认采用二级命名空间,也就是除了会记录符号名称,还会记录符号属于哪个 Mach-O 的,比如会记录下来 _NSLog 来自 Foundation。
例如在当前项目 A 中定义了一个全局函数test(), 如果引入的动态库 B 中也有这样一个全局函数,并不会报错。如果当前项目有该函数的实现,会调用当前项目的实现,没有则会调用动态库 B 中的函数实现。

3. 读符号

1. 间接符号

  1. // 显示间接符号, 也就是引用的动态库里的全局符号
  2. // --indirect-symbols - Print indirect symbol table for Mach-O objects (requires -macho)
  3. objdump -m --indirect-symbols ${path}

image.pngimage.png

2.导出符号

// 显示导出符号
// --exports-trie                   - Display mach-o exported symbols
 objdump -m --exports-trie ${path}

3.重定位符号

// 显示重定位符号, 缩写为 -r (后面接目标文件的Mach-O文件)
--reloc                          - Display the relocation entries in the file
// 显示动态库重定位符号, 缩写为 -R
--dynamic-reloc                  - Display the dynamic relocation entries in the file

4.恢复符号

// 恢复被加密处理的符号
 --demangle                       - Demangle symbols names

5.读取完整符号表的 2 种方法

1. objdump命令, 展示符号可见性、符号功能、所在位置等信息

// --syms 可缩写为 -t
objdump -m --syms test

image.png

2. nm命令,展示符号种类、可见性、是否导出等信息

  • -p: 不排序
  • -a: 显示所有符号,包含调试符号
  • -m:显示(N_SECT)符号,格式(seg-ment_name, section_name) (external non-

    external) (undefined), (common), (absolute) (indirect)
    image.png
    这些输出结果中的 l/g, F/O, t/d/s/U , external/non-external 代表的含义,下面马上讲到~

    3. 符号的分类

    1. 按符号种类区分

    Symbol Type:

  • U:undefined(未定义)

  • A: absolute(绝对符号)
  • -: debugger symbol table
  • T①:text section symbol(TEXT.text)
  • D①:data section symbol(DATA.data) ,已初始化的静态变量/全局变量
  • B①: bss section symbol(DATA.bss) , **未初始化
  • S①: common section symbol (DATA,common), 除了上面所述的,存放在其他section的内容,例如未初始化的全局变量
  • C: common symbol(只能出现在MH_OBJECT 类型的Mach-O文件中, 也就是.o目标文件中,表示未初始化的全局变量,上面的“S” 是指在可执行mach-o文件中表示未初始化的全局变量)
  • I: indirect symbol(符号信息相同,代表同一符号)
  • u: 动态共享库中的小写u表示一个未定义引用对同一库中另一个模块中私有外部符号

其中带①的T/D/B/S小写表示本地符号。

2. 按照功能分类

  • f: file 文件
  • F: Function 函数/方法
  • O: Data 数据
  • d: Debug 调试
  • ABS: Absolute 绝对
  • COM: Common 普通
  • UND: undefind 未定义

image.png

3. 按照符号的模块/可见性分类

  • 本地符号(local symbol):只在当前文件内可用的符号
  • 全局符号(global symbol):对当前所在项目可见,当打包成动态库以后,全局符号变成导出符号,就会对使用这个动态库的项目可见。也就是项目 A 使用了动态库 B,B 内的全局符号,A也可见。

如下图中,test 方法为本地符号,un_init_global 为全局符号:
image.png

动态库的全局符号默认为导出符号,可以修改成本地符号、也可以不导出。
动态库全局符号不可脱:因为只有在运行的时候才会去加载动态库,编译时会被存储到间接符号表,在链接的时候需要去间接符号表找到相应符号,就可以生成可执行文件了。由此动态库的全局符号,不能在优化的时候删掉。

1. 全局符号本地化的方法:

  • 使用 static 修饰 ,在静态区测试中,我们可知 static 可使变量的更改在当前文件内有效,所以static 修饰的是本地符号
  • 使用 Clang 编译器的 visibility 控制
    /**
          __attribute__ 是给编译器传参数
          visibility属性,控制文件导出符号,限制符号可见性(clang参数为-fvisibility)
    */
    // 将全局符号隐藏为本地符号
    int hidden_y __attribute__((visibility("hidden"))) = 99;
    // 默认属性,符号保持原可见性
    double default_y __attribute__((visibility("default"))) = 100;
    

    2. 不导出符号

    OC中的类,默认是全局符号即导出符号,当有的类不想对外暴露或者想减小库的体积,可以设置符号为不导出符号,转为本地符号。这样在脱符号的时候,就可以被脱掉了。 ``` // -unexportedsymbol 不导出符号_OBJC_METACLASS$LGOneObject OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_METACLASS$_LGOneObject

// unexported_symbols_list ${filepath} 多个符号需要不导出时,可以放到一个文件中,然后设置这个文件不导出 OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbols_list -Xlinker ${filepath}

<a name="Y4btM"></a>
## 4.按照存在空间分类
private: 私有<br />non-external: 非外部<br />external: 外部, 一般外部符号都是导出符号<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/94660/1617353767305-bbe68047-ba81-4134-9d2f-fbb759f6beef.png#height=266&id=M9tv2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=532&originWidth=1370&originalType=binary&ratio=1&size=227753&status=done&style=none&width=685)
<a name="pLl8m"></a>
## 5. 弱符号: **Weak Reference Symbol & Weak defintion Symbol**
**Weak Reference Symbol**: 表示此未定义符号是**弱引用**。

- 如果动态链接器找不到该符号的定义,则**将其符号置为0**
   - 可用来判断是否要执行该函数
   - 也可以将某个动态库设置成弱引用,这样就避免报库找不到的错误
- 静态链接器会将此符号设置**弱链接标志**。

// 1. 定义为弱引用符号 void weakfunction() _attribute((weak_import));

// 1.1 设置为弱定义的符号,且没有实现时符号,使用后,在编译时会报出符号找不到的错误 // 这时需要设置链接器参数,如下所示:

// 2. config 文件中,设置链接器参数,忽略该弱引用符号,等待动态查找 OTHER_LDFLAGS=$(inherited) -Xlinker -U -Xlinker _weak_function

// 3.调用时,如果不为0, 再执行这个函数 if (weak_function) { weak_function(); }

符号表中,弱引用符号的标识是 `**w**`: <br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/94660/1616503521895-2e16a26b-7245-42fa-a236-dd7aea8b635d.png#height=89&id=qfwDe&margin=%5Bobject%20Object%5D&name=image.png&originHeight=178&originWidth=754&originalType=binary&ratio=1&size=257307&status=done&style=none&width=377)<br />**Weak defintion Symbol**: 表示此符号为**弱定义符号**。

- 如果静态链接器或动态链接器为此符号**找到另一个非弱定义,则弱定义符号将被忽略**。
- 只能将合并部分中的符号标记为弱定义。
- 🌰:同一个项目中,Test 文件声明并实现了一个弱定义的函数,Test2 文件中也实现了一个非弱定义的weak_function,在编译时,会编译Test2 文件中的函数,如下图所示:

// 弱定义全局符号 void weakfunction() attribute((weak)); // 弱定义本地符号 void weakfunction() __attribute((weak, visibility(“hidden”)));

![image.png](https://cdn.nlark.com/yuque/0/2021/png/94660/1616502464718-c52eea30-6d0c-421a-9216-fc44bf427d0d.png#height=308&id=Sf0R9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=616&originWidth=2454&originalType=binary&ratio=1&size=378604&status=done&style=none&width=1227)
<a name="4jqq9"></a>
## 6. 导入(Import)导出(Export)符号:
**export symbol**:导出符号意味着,告诉别的模块,我有一个这样的符号,你可以将 其导入(**Import**)。

<a name="j5dbG"></a>
# 5.设置链接器参数
**链接器参数查看 **`**man ld **`, 切换英文输入反斜线`/` 可以查找命令**:**
<a name="jL9L0"></a>
### 1. 忽略符号:
xconfig 指定某个类,使用动态查找,这样就是告诉编译器不要因为找不到而报错

OTHERLDFLAGS = -XLinker -U -XLinker 具体符号(_OBJC_CLASS$_类名或者方法名) $(inherited) // -U 是可以不定义一个符号 // -u 是必须要定义符号,才能编译通过

<a name="AFX87"></a>
### 2.将符号信息和使用的库的信息写入到指定的文件中

OTHER_LDFLAGS = -XLinker -map ${path}

<a name="NP42L"></a>
### 3.重新导出符号,或者库。
例如使用到 Foundation 库里面的 NSLog方法。NSLog符号就会存在间接符号表中。间接符号表是符号表的子集。对于当前MACH-O ,NSLog是未定义的符号。如果这个MACH-O被另外的一个项目A使用,想在A中使用NSLog,则需要将NSLog重新导出,通过起别名的方式,可以将符号重新导出。**只能对间接符号起别名。**

// 起别名, 使符号重新导出 OTHER_LDFLAGS=$(inherited) -Xlinker -alias -Xlinker _NSLog -Xlinker Xxs_NSLog

// 查看符号的类型 nm -m ${path} | grep ‘Xxs’ // 打印结果: 导出符号,外部符号,Xxs_NSLog是_NSLog的别名 (indirect) external Xxs_NSLog (for _NSLog)

// 查看符号表 objdum -m —exports-trie ${path} // 打印结果:重导出符号表Xxs_NSLog [re-export] Xxs_NSLog (_NSLog from Foundation)

// 重新导出库 OTHER_LDFLAGS=$(inherited) -Xlinker -reexport_framework -Xlinker ${framework_name}

```