符号与符号表 - 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表示一个未定义引用对同一库中另一个模块中私有外部符号 |
int global_int_value = 10;
int global_int_value;
在编译时不会报错,因为第一行是一个全局符号,第二行是一个common symbol
符号
未定义的全局符号在找到定义后,会将未定义的符号删掉
未定义的全局符号另一个特点是在链接过程中,链接器会强制将其标记为强制变成定义的符号
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 defintion Symbol: 表示此符号为弱定义符号。如果静态链接器或动态链 接器为此符号找到另一个(非弱)定义,则弱定义将被忽略。只能将合并部分中的符号标记为弱定义。
- e.g.
void weak_function(void) __attribute__((weak));
- 不会改变其导出/全局符号的作用
- 链接器找到一个被弱定义的符号后,其他相同的符号就会被忽略 (可以解决符号冲突)
- e.g.
void weak_hidden_function(void) __attribute__((weak, visibility("hidden")));
- 弱定义的本地符号 (修改了可见性)
- e.g.
-
Weak Reference Symbol: 表示此未定义符号是弱引用。如果动态链接器找不到该符号的定义,则将其符 为0。静态链接器会将此符号设置弱链接标志。
- e.g.
void weak_import_function(void) __attribute__((weak_import));
声明为弱引用符号, 在链接时传入Xlinker -U -Xlinker _weak_import_function
动态链接- 链接时如果找不到不会报错,那么使用时就要进行检查
- e.g.
以上示例都是针对单独符号的操作,同理,可以通过链接器参数,将整个库标记为弱引用、弱定义(-weak-lx),已解决一些业务上的需求
二级命名空间与一级命名空间。链接器默认采用二级命名空间,也就是除了会记录符号 名称,还会记录符号属于哪个动态库的,比如会记录下来_NSLog来自Foundation
符号定义的本质是:指被分配了存储空间。如果是函数名则指代码所在区;如果是变量名则指其所在的静态数据区。
所有定义的符号的值就是其目标所在的首地址。
因此,符号的解析就是将符号引用和符号定义建立关联后,将引用符号的地址重定位为相关联的符号定义的地址
-
链接符号的类型
- Global symbols(模块内部定义的全局符号)
- 由模块m定义并能被其他模块引用的符号。
例如,非static C函数和非static C全局变量,如,main.c 中的全局变量名buf - 全局符号有强、弱的特性。
- 强符号:函数名和已初始化的全局变量名是强符号。
- 弱符号:未初始化的全局变量名是弱符号。
- 由模块m定义并能被其他模块引用的符号。
- External symbols(外部定义的全局符号)
- 由其他模块定义并被模块m引用的全局符号
如,main.c 中的函数名swap
- 由其他模块定义并被模块m引用的全局符号
- Local symbols(本模块的局部符号)
- 仅由模块m定义和引用的本地符号。
例如,在模块m中定义的带static的C函数和全局变量,如,swap.c中的static变量名bufp1.
注意:链接器的局部符号不是指程序中的局部变量(分配在栈中的临时性变量),链接器不关心这种局部变量
- 仅由模块m定义和引用的本地符号。
- Global symbols(模块内部定义的全局符号)
在 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
,再将变量标识符var
、int
关键字标识符和地址0x1004
等信息记录到符号表中,
int* p = &var;
系统再在内存上分配一块内存,地址为0x1001
,再将变量标识符p
、int*
关键字标识符和地址0x1001
等信息记录到符号表中,
| 0x10000 | 0x10001 | 0x10002 | 0x10003 | 0x10004 | 0x10005 |
+-------------+-------------+-------------+-------------+-------------+-------------+
| | | | | | |
| | 1004 | | | 100 | |
| | | | | | |
+-------------+-------------+-------------+-------------+-------------+-------------+
^ | ^ ^
| | | |
| +-------------------------------------+ |
p var
当符号在被用到时,系统去符号表里查询地址,例如var
,系统查询到地址是0x1004
的位置,根据类型int
返回数值100
;
p
,系统查询到地址0x1001
的位置,根据类型int*
返回数值0x1004
;
*p
,系统查询到地址0x1001
的位置,根据类型int
返回数值100
。