静态链接
一组可重定位目标文件和命令行参数作为输入,生成一个可完全链接的可以加载和运行的可执行目标文件作为输出。
构造可执行文件,连接器的任务:
- 符号解析:
1.将每个符号引用和符号定义联系起来
2.编译器将定义的符号存放在一个符号表中。结构数组,.symtab节中。每个表项包含符号名、位置、长度等信息。 - 重定位
1.将各自的代码和数据段合并到一起
2.将.o文件中符号的相对位置重定位到可执行文件中该
符号相应的绝对存储位置
3.更新所有的符号引用到其绝对位置
目标文件
目标文件有三种:可重定位目标文件、可执行目标文件和共享目标文件(即动态链接库),个个系统上对目标文件的叫法不一致,Unix叫a.out,Windows NT叫PE(Portable Executable)。现代Unix使用ELF格式(EXecutable and Linkable Format 即可执行和可链接格式)。
下面详细介绍“可重定位目标文件”
ELF头描述了生成该文件的系统的字的大小和字节序。ELF和节头部表之间每个部分都称为一个节(section)
.text:已编译程序的机器代码
.rodada:只读数据,比如printf语句中的格式串。
.data:已经初始化的全局C变量。局部变量在运行时保存在栈中。即不再data节也不在bss节
.bss:未初始化的全局C变量。不占据实际的空间,仅仅是一个占位符。所以未初始化变量不需要占据任何实际的磁盘空间。C++弱化BSS段。可能是没有,也可能有。
.symtab:一个符号表,它存放“在程序中定义和引用的函数和全局变量的信息”。
.rel.text:一个.text节中位置的列表。(将来重定位使用的)
.rel.data:被模块引用或定义的任何全局变量的重定位信息。
.debug:调试符号表,其内容是程序中定义的局部变量和类型定义。
.line:原始C源程序的行号和.text节中机器指令之间的映射。
.strtab:一个字符串表.
符号解析
1.符号
每个可重定位目标模块中都有一个符号表,包含了在m中定义的符号
- Global全局符号:m定义,外部可引用(非static C函数和非static全局变量)
- Extern外部符号:外部定义,m引用
- Local局部符号:m定义并引用(static函数或全局变量)
2.符号表
.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节的大小在节头表给出,每个符号的大小在符号表
————————————————
版权声明:本文为CSDN博主「hnu你深哥」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_43496675/article/details/106111789
- buf是main.o中第3节( .data )偏移为0的符号,是全局变量,占8B
- main是第1节( .text )偏移为0的符号,是全局函数,占33B
- swap是未定义的符号,不知道类型和大小,全局的(在其他模块定义)
3.符号解析
- 每个模块中引用的符号与某个目标模块中的定义符号关联。
- 每个定义符号都在代码段或数据段中分配了存储空间,引用符号和定义符号建立关联以后,就可以在重定位时将引用符号的地址重定位为相关联的定义符号的地址。
强符号:函数,初始化的全局变量。
弱符号:未初始化的全局变量
多重定义的全局符号处理规则:
①不允许多个强符号。
②一个强符号和多个弱符号:选择强
③多个弱符号:任意选择一个
模块间相互引用容易出错,尽量避免用全局变量
使用的话:
尽量用本地变量(static)
全局变量赋初值,变为强符号
外部全局变量使用extern,以示其定义在其他模块。
重定位
1.步骤:
- 重定位节和符号定义:对应的节合并(比如所有输人模块的.data节被合成为输出可执目标文件的.data节。);链接器将运行时存储器地址分配给新的节,这样,程序中的每个指令和全局变量都有唯一的运行时存储器地址了。
- 重定位节中的符号引用:链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。
2.重定位条目
汇编器生成一个目标模块时,不知道数据代码最终放在存储器哪个位置,也不知道这个模块引用的任何外部定义的函数或全局变量的位置。汇编器遇到一个位置未知的目标引用,会生成重定位条目,告诉连接器在将目标文件合并成可执行文件时如何修改引用。
代码的重定位条目放在rel.text;已初始化的重定位条目放在rel.data
3.重定位的符号引用
- pc相对地址引用
12:R_386_PC32 SWAP为ELF重定位条目
这里fc ff ff ff是小端法
,书上把这个值叫做“引用的值
”(其实有点容易引起歧义);e8是操作码。
若设.text节开始地址
ADDR(.text)=0x8048380
swap的代码节开始地址为
ADDR(swap)=0x80483b0
(经过重定位第一步:重定位节和符号定义,上面两步都是确定的,因为第一步连接器会将运行是存储器地址付给新的聚合节
)
则引用符号的运行时的地址:
refaddr=0x8048380+0x12=0x8048392
*refptr=ADDR(SWAP)+*refptr-refaddr=0x1a
最后pc指向的就是0x8048396+0x1a=0x80483b0
-
绝对地址引用示例1
若符号buf的绝对地址
ADDR(buf)=0x8049629
*refptr=ADDR(r.symbol)+*refptr=0x8049629+0x0=0x8049629
注意使用小端法! -
绝对地址引用示例2
swap.o模块将全局指针bufp0初始化为指向全局数组buf的第一个元素地址:
int *bufp0=&buf[0]
因为bufp0是一个已经初始化的数据目标,它被存在可重定位目标模块swap.o的.data节中。因为它被初始化为一个全局数组的地址,所以要重定位。
其中左图是.data节的反汇编列表。由重定位条目可知这是一个绝对引用,开始于偏移0处,要使用重定位指向符号buf。
假设已经确定ADDR(buf)=0x8049454,
那么*refptr=ADDR(r.symbol)+*refptr=0x8049454+0x0=0x8049454