链接的作用
链接:将各种代码和数据片段收集组成一个单一文件的过程,这个文件可以加载(复制)到内存并执行。
分为:编译时、加载时(加载器加载到内存并执行时)、运行时
1、构造大型程序
2、避免一些危险的编程错误
3、理解语言的作用域是如何实现的
4、理解其他重要的系统概念
5、更好的利用共享库
gcc -Og -o prog main.c sum.c
1、预处理生产.i文件(ccp)
2、转换成汇编文件.s(cc)
3、汇编变成目标代码.o(as)
4、链接目标代码生成可执行程序(ld)
5、shell调用OS中一个叫(loader)函数:它将可执行prog中代码与data装入内存,然后将控制转移到这个程序的开头。
链接器有两个任务:将模块连接起来,确定连接块运行位置,并且修改代码与数据中位置
1、符号解析:目标文件定义和引用符号,每个符号对应一个函数,一个全局变量或者一个静态变量(static声明的变量)。将每个符号引用正好与一个符号定义关联起来。符号定义存在目标文件obj里面的符号表(结构数组+名字、大小、位置)
2、重定位:链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节。修改所有对这些符号的引用,使得它们指向这个内存位置
目标文件
目标文件纯粹是字节集合
可重定位目标文件(.o文件)、可执行目标文件(.a文件)、共享目标文件(.so文件)
编译器汇编器:生成可重定位文件 链接器生成可执行目标文件
可重定位文件
ELF文件(可执行可链接格式文件)
1、elf头部:字大小、字节顺序、文件类型、机器类型、页大小、每段虚拟地址、段大小
2、.text:代码段
3、.rodata:只读data
4、.data:初始化空间变量(全局+static)
5、.bss:定义未初始化空间,但是不占用任何空间,只是占位符(程序加载时分配)
6、.symtab:不包含局部变量,此条目可以用STRIP除去这个表
7、.rel.text:外部函数调用与全局变量引用指令需要修改。调用本地函数不需要修改
8、.rel.data:被模块引用或定义的所有全局变量的重定位信息(7、8重定位信息部分)
9、debug:调试符号表信息
节头部表:告诉不同部分的起始位置
ELF首部+不同的section+描述section信息的表
7f 45 4c 46(ELF Magic头部) 02(类型) 01(大小端) 01 00 00 00 00 00 00 00 00 00
可执行文件
可执行文件被设计得很容易加载到内存中,可执行文件连续片被映射到连续内存段。程序头部表 描述了这种映射段头部表。
elf头部包括prog入口点执行第一条指令地址
定义了一个init函数,初始化调用
因为已经完全连接了故而没了rel节
符号与符号表
全局变量:被其他模块调用:非静态函数和全局变量
外部符号:其他的定义被main调用:在其他模块定义的函数和变量
局部符号:只能自己用:带static属性的函数和全局变量
但是static的变量不在栈中管理在.bss和.data中
函数名称或已初始化全局变量为强符号,未初始化的为弱符号
符号解析
链接器解析符号引用的方法是将每个引用于它输入的可重定位目标文件的符号表中的一个确定符号定义关联起来。
1、局部变量只能有一个定义
2、static变量也会有本地链接器符号,且确保唯一名字
3、多个目标文件可能会定义相同名字的全局符号(①标志一个错误②要么以某种方法选出一个定义并抛弃)
链接器输入是一组可重定位目标模块。每个模块定义一组符号,有些局部有些全局
强符号:函数和已初始化全局变量 弱符号:未初始化全局变量
1、不允许有多个同名强符号
2、一个强和多个弱选强
3、多个弱选一个弱
与静态库链接
所有编译系统都一共一种机制,将所有相关模块打包成为一个单独文件称为静态库。
可以用作linker输入。当linker构造一个可输出的可执行文件时,只复制static、lib被应用程序引用的目标模块
如果不用静态库则
1、编译器辨认出对标准函数的调用,并生成代码(Pascal)
2、将所有标准函数都放在一个单独可重定位目标模块中,然后将其链接进可执行文件中
缺点:但是每个可执行文件都有一个副本浪费空间,另外如果标准函数库改变,则全部文件需要重新编译
3、为每个标准函数创建独立可重定位文件,存放在一定的目录下。(难记且易错)
静态库提出可以解决上述三者问题
相关函数可以编译成独立模块并装入单独静态模块(.a文件)
链接时只复制被程序引用的目标模块,名字少好记
静态库以一种称为存档的特殊文件格式存放在磁盘中。存档文件是一组连接起来的可重定位目标文件集合。
链接解析引用
链接器从左到右按照编译器驱动程序命令行上出现的顺序来扫描重定位文件和存档文件。
E:可重定位文件
D:在E中输入文件中定义的符号集合
U:在E中未解析符号
扫描到一个文件放入E,将其中的符号分类到UD中,当扫描所有文件后U为空则构成输出的可执行文件。
加载可执行文件
Linux > ./prog
所有Linux程序可以调用execve函数来调用加载器。加载器将可执行目标文件的数据和代码从磁盘中加载到主存中,然后跳到程序的入口来运行程序。
_start()→_libc_start_main()→main()→_libc_start_main()→linux内核
动态链接共享库
静态库缺点:1、静态库需要定期维护与更新。2、几乎每个程序都需要IO函数,这些函数代码会复制进代码中从而浪费内存。
使用共享库来完善,共享库运行或加载时,可以被加载到任意的内存地址、还可以与内存的程序链接起来,整个过程称为动态链接。
在链接器ld中不会复制共享文件的代码和数据到可执行文件中,只是复制了符号表与一些重定位信息。(生成的prog中包含一个interp节:包含动态链接器的路径)
重定位libc.so文本和数据到某个内存段
重定位libvector.so文本和数据到另一个内存段中
重定位prog对lib.so和libvector.so定义符号引用
重定位条目:重定位PC相对引用、绝对引用