目录
一、链接操作的步骤概述
1.符号解析
- 程序中有定义和引用的符号(包括变量和函数)。
- 编译器将定义的符号存放在一个符号表中。结构数组,.symtab节中。每个表项包含符号名、位置、长度等信息。
- 编译器将对符号的引用放在重定位节rel.text、rel.data。
- 链接器将每一个符号的引用与一个确定的符号定义建立关联。
2.重定位
- 多个代码段,数据段合并为一个单独的代码段和数据段
- 计算每个定义的符号在虚拟地址空间的绝对地址
- 可执行文件中的符号引用处的地址修改为重定位后的地址信息
二、符号解析
1.符号
①每个可重定位目标模块中都有一个符号表,包含了在m中定义的符号
- Global全局符号:m定义,外部可引用(非static C函数和非static全局变量)
- Extern外部符号:外部定义,m引用
- Local局部符号:m定义并引用(static函数或全局变量)
②.symtab节记录符号表信息,结构数组,.symtab中每个表项结构:
typedef struct {
Elf32_Word st_name; /*符号对应字符串.即在strtab节中的偏移量*/
Elf32_Addr st_value; /*在对应节中的偏移量 ,或虚拟地址*/
Elf32_Word st_size; /*符号对应目标字节数*/
unsigned char st_info;/*前4位符号的类型(Type)、后4位绑定的属性(Bind)*/
unsigned char st_other;
Elf32_Half st_shndx; /*符号对应目标所在的节,或其他情况*/
} Elf32_Sym;
/*
符号类型:数据,函数,源文件,节,未知
绑定属性:全局符号,局部符号,弱符号
*/
/*
其他情况:
①不该被重定位ABS
②未定义UND
③COM未初始化数据(.bss)此时value表对齐要求,size表最小大小。
.bss节的大小在节头表给出,每个符号的大小在符号表
*/
buf是main.o中第3节( .data )偏移为0的符号,是全局变量,占8B
main是第1节( .text )偏移为0的符号,是全局函数,占33B
swap是未定义的符号,不知道类型和大小,全局的(在其他模块定义)
2.符号解析
- 每个模块中引用的符号与某个目标模块中的定义符号关联。
- 每个定义符号都在代码段或数据段中分配了存储空间,引用符号和定义符号建立关联以后,就可以在重定位时将引用符号的地址重定位为相关联的定义符号的地址。
- 全局符号的解析涉及多个模块
①符号定义的实质?指被分配了存储空间,定义符号的值就是其所在的首地址。函数名:代码所在区;变量名:所占的静态数据区。
②全局符号的强/弱特性
强符号:函数,初始化的全局变量。
弱符号:未初始化的全局变量,函数的声明也是。
③多重定义的符号处理规则:
①不允许多个强符号。
②一个强符号和多个弱符号:选择强
③多个弱符号:任意选择一个 ,gcc -fno-common会告诉链接器这种情况输出警告信息。
每个符号只能占一处存储空间。
图1 图2
图2中编译是按模块的,编译的时候把d当作double类型,链接的时候把d的引用链接到main里面的int d定义。
但是由于d=1.0对应的指令是在编译阶段确定,因此还是把d当作浮点类型来赋值,最后数据存到main中d对应的地址内,并且溢出了。
④因此模块间相互引用容易出错,尽量避免用全局变量
使用的话:
- 尽量用本地变量(static)
- 全局变量赋初值,变为强符号
- 外部全局变量使用extern,以示其定义在其他模块。
3.静态链接和符号解析
A.静态链接
①静态链接对象:
多个可重定位模块(.o) +静态库(标准库、自定义库, .a文件,其中包括多个.o模块)
②库函数模块:许多函数无需自己写,可使用共享库的函数。数学库,输入/出库,存储管理库,字符春处理等。
③对于自定义的模块,注意不能:
- 将所有函数放在一个源文件:修改一个函数,所有函数重新编译,空间时间效率不高。
- 一个源文件包含一个函数:显示链接,模块太多,繁琐。
④静态库
- 所有相关目标模块(.o)打包一个单独库文件(.a),静态库文件 、存档文件
- 经常使用的函数
- 可增强链接器功能,通过查找一个/多个库文件中定义的符号来解析符合。
- 构建可执行文件时,只需指定库文件名,链接器会自动到库中寻找那些应用程序用到的目标模块,只把用到的目标模块从库中拷贝出来,链接到可执行文件中。
⑤自定义一个静态库文件
B.符号解析
E将被合并以组成可执行文件的所有目标文件集合
U当前所有未解析的引用符号的集合
D当前所有定义符号的集合
过程:
- 扫描main.o 加入E, myfun1加入U, main加入D
- 扫描mylib.a,U中所有符号与mylib.a中所有目标模块依次匹配,myproc1.o中定义了myfunc1因此加入E,myfunc1从U转移到D.
- 在myproc1.o中还有未解析的printf,加入U
- 不断在mylib.a各模块上进行迭代匹配U中符号,直到U、D不发生变化。
- U中还有printf,D中有main和myfunc1
- 模块myproc2.o没有加入E,被丢弃
- 扫描默认的libc.a,printf.o定义了printf,printf.o加入E,printf从U加入D,同时把printf.o定义的所有符号加入D,未解析符号加入U
- 处理完libc.a,U一定为空
应该按照调用顺序来指定
$ gcc -static -0 myproc main.o ./ mylib.a会发生什么?
- 首先,扫描mylib ,因是静态库,应根据其中是否存在U中未解析符号对应的定义符号来确定哪个.o被加入E。
- 因为开始U为空,故其中两个.o模块都不被加入E中而被丢弃。
- 然后,扫描main.o ,将myfunc1加入U,直到最后它都不能被解析,它只能用mylib. a中符号来解析,而mylib中两个.o模块都已被丢弃!