【符号解析】:
将每个符号引用与输入的可重定位目标文件的符号表中的一个确切符号定义关联起来对于那些符号引用与符号定义在同一个模块(文件)的情况,符号可以直接简单的解析。
对于那些符号引用不在本模块(文件)的情况,问题变得复杂。
在编译器单独编译一份源代码时,每当遇到一个不在本模块中定义的函数或者全局变量,会假设符号在其它位置定义。所以能够单独通过编译。同时生成一个链接器符号表条目。
如果链接器在所有输入模块找不到这个被引用符号的定义,那么输出错误信息。如果能够在其它模块找到这个被引用符号的定义,那么链接成功。另外需要注意的一点是,符号重复定义的情况,常见于在多个源文件中定义了同名的变量和函数。在被编译成目标文件后,又同时被输入到链接器中,对于同名的全局符号。链接器的策略为:
直接输出一个错误,指明链接过程中出现符号多重定义。任意选择其中一个符号解析,典型的选择第一个遇见的符号。Linux中,静态库是一组可重定位目标文件的集合。其文件格式为:Archive 存档格式 一般通过AR工具将多个.o目标文件连接成一个.a静态库文件。
ar rcs libvector.a addvec.o subvec.o静态库文件通常用于对语言标准函数的封装,如最常见的ISCO C99 其定义许多标准的IO函数、数学函数,通过库文件将这些标准函数生成的目标文件的打包,以供使用。但在链接静态库时,链接器操作并不是将所有的.o文件链接进最终文件。
因为典型的,当使用一个库文件时,通常只是使用其中少数函数。而库文件通常包含了数以百计的函数,如果全部链接进文件会造成可执行文件很大,造成很大的空间占用。而链接器的做法是:
只复制库文件中被程序引用的目标模块。这样可以减少可执行文件的磁盘占用以及加载进内存时的内存占用。
【重定位】:
在链接器完成符号解析以后,链接器可以知道输入的目标模块的代码段和数据段大小,合并模块后,为每个符号分配运行时地址。主要的步骤:
①重定位节和符号定义:链接器将所有相同类型的节合并到一起。典型地,将所有输入模块的.data节全部合并为一个.data节。这个节就成为了可执行文件的.data段。因此可以根据偏移和大小,为每个节以及节中定义的符号计算地址。即:程序中的每条指令和每个全局变量都有唯一的运行时内存地址。
②重定位节中符号引用:链接器修改代码节和数据节中对每个符号的引用,使它们指向正确的运行时地址。这一步需要依赖于可重定位条目的数据结构。
当编译器和汇编器生成一个目标文件时,并不知道数据段和代码段放置在内存中具体位置(也不可能知道),更不知道本模块中引用的外部符号所对应的函数或全局变量的位置。所以,当汇编器遇到位置未知的引用时,会生成一个重定位条目。放置于.rel.text以及.rel.data中。
ELF重定位条目:
typedef struct { long offset; long type:32,symbol:32; long addend; } ELF64_rela ;其中offset指明需要修改的引用的节偏移。symbol表明被修改的引用应该指向的符号。type是修改引用的方式。常见类型如下:
PC相对地址:
重定位一个使用PC相对地址的引用。一个PC相对地址是基于当前程序计数器PC的偏移量。当CPU执行到PC相对寻址的指令时,将偏移值累加到PC中,得到新的有效地址。
绝对寻址:
CPU直接使用指令中指定的地址作为有效地址。
