链接与加载简介 - LD/LD-LINUX/LOADER
链接
链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载到存储器并执行。链接可以执行于编译时(compile-time,由LD完成),也就是在源代码被翻译成机器代码时;也可以执行于加载时(load-time,由LD-LINUX完成),也就是在程序被加载器(LOADER)加载到存储器并运行时;甚至执行于运行时(run-time,由APP完成),也就是dl(open|sym|close)系列函数的功能。
这里,我们区分三个概念:
- 可重定位目标文件。也就是汇编器产生的.+\.o文件,其包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建可执行目标文件;
- 可执行目标文件。也就是可执行文件.+,其形式可以被直接由LOADER加载并执行;
- 共享目标文件。一种特殊类型的可重定位目标文件,也就是链接器产生的.+\.so文件,可以由LOADER加载或运行时被动态地加载到存储器并连接。
为构造可执行文件,链接器必须完成两个主要任务:
- 符号解析(symbol resolution)。目标文件定义和引用符号。符号解析的目的是将每个符号引用刚好和一个符号定义联系起来。对于延迟绑定需要使用PLT(Procedure Linkage Table);
- 重定位(relocation)。编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义与一个存储器位置联系起来,然后修改所有对这个符号的引用,使得他们指向这个存储器位置,从而从定位这些节,对于延迟绑定需要使用GOT(Global Offset Table)。
加载
要运行可执行目标文件foo,可以在Linux外壳的命令行中输入它的名字:
Linux> ./foo
shell先判定该命令是否为其内置命令,若否,则认为是一个可执行目标文件,其通过调用驻留在存储器中称为LOADER的操作系统代码来运行它。任何Linux程序都可以通过调用execve系统调用来调用加载器。加载器将可执行目标文件中的代码和数据从磁盘拷贝到存储器中,并根据ELF格式中段头部表的指导下分别填入对应的section到进程地址空间中,然后通过跳转到程序的第一条指令或入口点(entry point)来运行该程序,也就是符号_start的地址,其符号是在ctrl.o中定义,然后执行所有从C/C++都需要的startup/exit流程(call __libc_init_first => _init => atexit => main => _exit)。这个过程我们称之为加载。
请注意,LOADER只是在当前调用它的进程中完成对虚拟地址空间的映象映射并转移控制权到_start,而创建进程并不是其责任。另外,在现在的Linux系统中,在加载可执行目标文件时,并不会完成整个目标文件的加载,而仅仅是满足于能够映射虚拟地址空间映象而已,其真正的.text与.data等section是由操作系统利用它的页面调度机制自动将页面从磁盘传送到存储器。
另外,若可执行目标文件包含加载时链接.+\.so文件,LOADER还将在转交控制权与_start之前将控制权转交给LD-LINUX完成加载时的链接。