符号与符号表 - ShenYj/ShenYj.github.io GitHub Wiki

符号与符号表

符号

符号就是程序中的变量名、函数名。

符号按照模块划分 (可见性)

  • global 全局符号
  • local 本地符号,除了static 修饰,比如clang编译器可以使用 __attribute__((visibility("hidden"))) 将符号隐藏
    • e.g. double default_x __attribute__((visibility("hidden")));

    • visibility:clang参数
      default:用它定义的符号将被导出。(不对其进行隐藏,默认是什么就是什么)
      hidden:用它定义的符号将不被导出。 (会被标记成本地符号(local))

符号按照功能划分

Type 说明
f File
F Function
O Data
d Debug
*ABS* Absolute
*COM* Common
*UND* ? (UnDefined)

符号按照种类划分

Symbol Type 说明
U undefined (未定义)
A absolute (绝对符号)
T text section symbol (__TEXT.__text)
D data section symbol (__DATA.__data)
B bss section symbol (__DATA.__bss)
C common symbol (只能出现在 MH_OBJECT类型的Mach-O文件中, 未定义的全局符号(在定义的时候未初始化的全局符号))
- debugger symbol table
S 除了上面所述的,存放在其他 section的内从,例如未初始化的全局变量存放在(__DATA.__common)中
I indirect symbol (符号信息相同,代表同一符号)
u 动态共享库中的小写u表示一个未定义引用对同一库中另一个模块中私有外部符号

common symbol: 比如如下这段代码

int global_int_value = 10;
int global_int_value;

在编译时不会报错,因为第一行是一个全局符号,第二行是一个common symbol符号

未定义的全局符号在找到定义后,会将未定义的符号删掉
未定义的全局符号另一个特点是在链接过程中,链接器会强制将其标记为强制变成定义的符号

导入(import)导出(Export)符号

export symbol:导出符号意味着,告诉别的模块,我有个这样的符号,你可以将其导入(import)

OC 默认都是导出符号,即便是没有暴露的方法

重新导出符号

NSLog作为Foundation中的一个API暴露给开发者使用,而Foundation是一个系统及动态库

对于我们使用的模块中,NSLog是一个存在间接符号表中的未定义符号

Xlinker -alias : 只能给间接符号表中的符号起别名,起别名后会将间接符号的别名变成导出符号

  • e.g. -Xlinker -alias -Xlinker _NSLog -Xlinker _XXLog -> (导出符号 _XXLog)

可以借助 nm -m ${MACH_PATH} 命令来验证

  • nm -m ${MACH_PATH} | grep '_XXLog',会得到 (indirect) external _XXLog (for _NSLog)的记录

使用objdump --macho --exports-trie ${MACH_PATH}查看导出符号表时,也会显示[re-export]的标记: [re-export](indirect) external _XXLog (for _NSLog)

作用:
当一个动态库链接另一个动态库,默认被链接的那个动态库对链接的动态库是不可见的,通过重新导出的方式,让这个动态库可见(指定符号或整个动态库)
通过-reexported_symbols_list file参数可以传入一个文件, 将所需要配置的符号全都添加到文件中实现批量处理

Weak Symbol

  • Weak defintion Symbol: 表示此符号为弱定义符号。如果静态链接器或动态链 接器为此符号找到另一个(非弱)定义,则弱定义将被忽略。只能将合并部分中的符号标记为弱定义。

    • e.g. void weak_function(void) __attribute__((weak));
      • 不会改变其导出/全局符号的作用
      • 链接器找到一个被弱定义的符号后,其他相同的符号就会被忽略 (可以解决符号冲突)
    • e.g. void weak_hidden_function(void) __attribute__((weak, visibility("hidden")));
      • 弱定义的本地符号 (修改了可见性)
  • Weak Reference Symbol: 表示此未定义符号是弱引用。如果动态链接器找不到该符号的定义,则将其符 为0。静态链接器会将此符号设置弱链接标志。

    • e.g. void weak_import_function(void) __attribute__((weak_import)); 声明为弱引用符号, 在链接时传入Xlinker -U -Xlinker _weak_import_function 动态链接
      • 链接时如果找不到不会报错,那么使用时就要进行检查

以上示例都是针对单独符号的操作,同理,可以通过链接器参数,将整个库标记为弱引用、弱定义(-weak-lx),已解决一些业务上的需求

two_levelnamespace & flat_namespace

二级命名空间与一级命名空间。链接器默认采用二级命名空间,也就是除了会记录符号 名称,还会记录符号属于哪个动态库的,比如会记录下来_NSLog来自Foundation

符号定义的本质

符号定义的本质是:指被分配了存储空间。如果是函数名则指代码所在区;如果是变量名则指其所在的静态数据区。

所有定义的符号的值就是其目标所在的首地址。

因此,符号的解析就是将符号引用和符号定义建立关联后,将引用符号的地址重定位为相关联的符号定义的地址

  • 链接符号的类型

    • Global symbols(模块内部定义的全局符号)
      • 由模块m定义并能被其他模块引用的符号。
        例如,非static C函数和非static C全局变量,如,main.c 中的全局变量名buf
      • 全局符号有强、弱的特性。
        • 强符号:函数名和已初始化的全局变量名是强符号。
        • 弱符号:未初始化的全局变量名是弱符号。
    • External symbols(外部定义的全局符号)
      • 由其他模块定义并被模块m引用的全局符号
        如,main.c 中的函数名swap
    • Local symbols(本模块的局部符号)
      • 仅由模块m定义和引用的本地符号。
        例如,在模块m中定义的带static的C函数和全局变量,如,swap.c中的static变量名bufp1.
        注意:链接器的局部符号不是指程序中的局部变量(分配在栈中的临时性变量),链接器不关心这种局部变量

Swift

在 Swift中符号与文件访问权限有关

比如 public 默认就是 global的, 而 private 就是 local的

在Swift查看符号时,编译器做了处理, 可以使用xcrun swift-demangle来符号的进行还原

符号表

程序编译成可执行文件后,文件中会有一个专门的表用来保存函数名,变量名,段名和代码或者数据的对应关系,这个表就是符号表(编译阶段产生的)。

符号表是一种用于语言翻译器(例如编译器和解释器)中的数据结构。在符号表中,程序源代码中的每个标识符都和它的声明或使用信息绑定在一起,比如其数据类型、作用域以及内存地址。

目标文件中通常会有一个包含了所有外部可见标识符的符号表。在链接不同的目标文件时,链接器会使用这些文件中的符号表来解析所有未解析的符号引用。

符号表可能只存在于翻译阶段,也可能被嵌入到该阶段的输出文件中,以供后续阶段使用。比如,它可用于交互式的调试器中,也可以在程序执行过程中或结束后提供格式化的诊断报告。

在逆向工程中,许多任务具会通过符号表来检查全局变量和已知函数的地址。如果可执行文件的符号表被strip这样的工具去除掉了,则逆向工程会更加困难。

在进行动态内存分配和变量访问时,编译器需要完成许多任务作,其中扩展的栈模型就需要用到符号表。

符号表用来体现作用域与可见性信息,符号表中语言符号可分为关键字符号、操作符符号及标识符符号

经过编译后,代码中的各种符号都被分配了地址,并将各种信息记录在符号表中。

ELF文件中通常会有两张符号表。一张叫符号表(.symtab) ,另一张叫动态符号表(.dynsym),一般只用对符号表(.symtab) 进行去除的工作。

  • .symtab : 包含大量的信息(包括全局符号global symbols)
  • .dynsym : 只保留.symtab中的全局符号

在 mach-o中

  • Symbol Table:就是用来保存符号。
  • String Table:就是用来保存符号的名称。
  • Indirect Symbol Table:间接符号表。保存使用的外部符号。更准确一点就是使 用的外部动态库的符号。是Symbol Table的子集。

例如:

int var = 100; 系统先在内存上分配一块内存,地址为0x1004,再将变量标识符varint关键字标识符和地址0x1004等信息记录到符号表中,

int* p = &var; 系统再在内存上分配一块内存,地址为0x1001,再将变量标识符pint* 关键字标识符和地址0x1001等信息记录到符号表中,

| 0x10000     | 0x10001     | 0x10002     | 0x10003     | 0x10004     | 0x10005     |
+-------------+-------------+-------------+-------------+-------------+-------------+
|             |             |             |             |             |             |
|             |    1004     |             |             |    100      |             |
|             |             |             |             |             |             |
+-------------+-------------+-------------+-------------+-------------+-------------+
                    ^  |                                     ^ ^
                    |  |                                     | |
                    |  +-------------------------------------+ |
                    p                                          var 

当符号在被用到时,系统去符号表里查询地址,例如var,系统查询到地址是0x1004的位置,根据类型int 返回数值100

p,系统查询到地址0x1001的位置,根据类型int* 返回数值0x1004

*p,系统查询到地址0x1001的位置,根据类型int 返回数值100

符号及符号表

⚠️ **GitHub.com Fallback** ⚠️