链接:
链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以被加载(复制)到内存并执行。链接可以执行于编译时,也就是在源代码被翻译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行;甚至执行于运行时,也就是应用程序来执行。
首先:我们要了解目标文件
目标文件有三种形式:
- 可重定位目标文件(二进制代码和数据)
- 可执行目标文件(二进制代码和数据)
- 共享目标文件
我们再来了解可重定位目标文件:
我们以典型的ELF可重定位目标文件举例,下面是ELF文件的格式:
| ELF头 |
| .text |
| .rodata |
| .data |
| .bss |
| .symtab |
| .ral.text |
| .rel.data |
| .debug |
| .line |
| .strtab |
| 节头部表 |
其中:
- ELF头
包括16字节的标识信息、文件类型(.o,exec,.so)、机器类型(如Intel 80386)、节头表的偏移、节头表的表项大小及表项个数。
- .text节
编译后的代码部分。
- .rodata节
只读数据,如 printf用到的格式串、switch跳转表等。
- .data节
已初始化的全局变量和静态变量。
- .bss节
未初始化全局变量和静态变量,仅是占位符,不占据任何磁盘空间。区分初始化和非初始化是为了空间效率。
- .symtab节
存放函数和全局变量(符号表)的信息,它不包括局部变量。
- .rel.text节
.text节的重定位信息,用于重新修改代码段的指令中的地址信息。
- .rel.data节
.data节的重定位信息,用于对被模块使用或定义的全局变量进行重定位的信息。
- .debug节
调试用的符号表(gcc -g)
- .strtab节
包含 .symtab节和 .debug节中的符号及节名
- 节头表(Section header table)
包含每个节的节名在.strtab节中的偏移、节的偏移和节的大小.
接下来我们看下符号和符号表:
符号就是其实程序中的变量名、函数名。
注意:局部变量temp分配在栈中,不会在函数外部被引用,因此不是符号定义。
符号定义的本质是:指被分配了存储空间。如果是函数名则指代码所在区;如果是变量名则指其所在的静态数据区。
所有定义的符号的值就是其目标所在的首地址。
因此,符号的解析就是将符号引用和符号定义建立关联后,将引用符号的地址重定位为相关联的符号定义的地址。
全局符号的强、弱
全局符号有强、弱的特性。
- 强符号:函数名和已初始化的全局变量名是强符号。
- 弱符号:未初始化的全局变量名是弱符号。
链接器对符号的解析规则
符号解析时,只能有一个确定的定义(即每个符号仅占一处存储空间)。
所以,如果碰到符号存在多重定义时,就得有相应的处理规则:
- Rule 1:强符号不能多次定义
强符号只能被定义一次,否则链接错误。
- Rule 2:若一个符号被定义为一次强符号和多次弱符号,则按强符号定义为准。
- Rule 3:若有多个弱符号定义,则任选其中一个。