关于链接的一些总结

时间:2024-01-23 22:01:24

库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)。

a. 静态库 
  之所以称为【静态库】,是因为在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。首先,静态库对函数库的链接是放在编译时期完成的。其次,程序在运行时与函数库再无瓜葛,移植方便。静态库也有缺点如下图所示,造成空间内存浪费;

b. 动态库
  动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行时才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。

程序的编译过程

   假设现在有如下两个c程序文件,sum.c内容如下:

int sum(int *a, int n){
	int i, s = 0;
	for( i = 0; i < n ; i++){
		s += a[i];
	}
	return s;
}

  主程序main.c的内容如下:

int sum(int *a,int n);

int array[2]  = {1, 2};

int main(){
	int val = sum(array,2 );
	return val;
}

  在linux上执行如下语句:

gcc -Og -o prog main.c sum.c

  以main.c为例,程序首先调用C预处理器,将main.c翻译成一个Ascii码的中间件,mian.i,接下来将main.i翻译成一个汇编语言文件main.s,然后调用汇编器将main.s翻译可重定位的目标文件main.o,同理会生成相应的sum.o文件。最后,运行链接器程序ld,将main.o和sum.o以及一些必要的文件组合起来,创建一个可执行文件prog。

  在构造可执行文件的时候,链接器要完成两个任务:

  1、符号解析,符号解析的目的是将每个全局符号引用与符号定义关联起来,即可重定位的目标文件定义了符号,而可执行的目标文件引用符号,将两者的符号相互绑定,这里的符号对应一个函数、一个全局变量、或者一个静态变量。

  2、重定位,编译器和汇编器生成从0开始的的代码和数据节,链接器将符号定义和内存的位置关联起来来重定位代码和数据节,然后修改所有这些对符号的引用,使得它们指向内存的位置。

一些基础知识

  目标文件有三种类型:

    可重定位的目标文件:包含二进制数据和代码,其形式可以在编译时与其他可重定位合并起来,创建一个可执行的目标文件

    可执行的目标文件:包含二进制和代码,可以直接复制到内存并执行

    共享目标文件:一类特殊的可重定位目标文件,可以在加载或者运行时被动态的加载到内存执行。

  一个典型的ELF(可执行、可链接格式)的可重定位目标文件包含几个重要的部分,如下图:

 

  .text 已编译程序的机器代码

  .data 已初始化的全局和静态C变量,局部C变量的值保存在栈中。

  .bss 未初始化的全局和静态C变量,以及所有被初始化为0的静态或全局变量。

  .sysmtab 一个符号表,存放程序中定义和引用的函数以及全局或静态变量,不包含局部变量的信息。

 

链接器实际上会处理三种不同的符号,对应于代码中不同写法的部分:
全局符号 Global symbols
在当前模块中定义,且可以被其他代码引用的符号,例如非静态 C 函数和非静态全局变量。
外部符号 External symbols 
同样是全局符号,但是是在其他模块(也就是其他的源代码)中定义的,但是可以在当前模块中引用。
本地符号 Local symbols
在当前模块中定义,只能被当前模块引用的符号,例如静态函数和静态全局变量。
注意,Local linker symbol 并不是 local program variables

链接过程

第一步 符号解析 Symbol resolution 
  符号解析会将每个符号引用刚好和一个符号定义联系起来。汇编器生成可重定位目标文件后,内部符号都已被正确地符号解析, 外部符号可能会引用了非本模块的符号定义,汇编器无法找到符号定义,因此无法解析。 汇编器把外部符号放入”符号表“.symtab,同时把如何解析该符号的方法放入”重定位表“。链接器只知道非静态的全局变量/函数,而对于局部变量一无所知,局部非静态变量和局部静态变量的区别:

局部非静态变量会保存在栈中
局部静态变量会保存在 .bss 或 .data 中

链接器符号解析时会用到符号表:

强符号与弱符号: 函数和初始化的全局变量叫强符号, 未初始化的全局变量叫弱符号。(extern int a是一个弱符号定义, int a 也是弱符号)

符号解析规则:

 1.(定义多个强符号) 当引用符号时,该符号的符号定义有不止一个强符号定义时,会出现符号重定义错误。
 2.(定义一个强符号和一个或者多个弱符号)当引用符号时, 该符号的符号定义有个强符号定义和一个和多个弱符号定义, 使用强符号定义。
 3.(定义多个弱符号)当引用符号时, 该符号的符号定义都是弱符号时, 选择任意一个定义。

第二步 重定位 Relocation 

就是把不同可重定位对象文件拼成可执行对象文件,有三步,如下:

1.合并可重定位目标文件中相同的节。
2.重定位节和符号定义,修改符号表。为节和符号定义分配虚拟地址。修改符号表中符号定义的值为刚分配的虚拟地址。
3.重定位节中的符号引用,修改代码段和数据段符号引用。使用重定位表.rel.text .rel.data, 修改text,data中符号引用的地址。

例如下图,多个可重定位对象文件合并成可执行文件的过程所示:

 

对重定位的理解,在重定位,有两个步骤。

  首先重定位节和符号定义,链接器将不同目标文件所有相同类型的节合并到同一个新的聚合节中,例如,来自所有输入模块的.data节会被合并到同一个节中。然后链接器将运行时内存的地址赋给新的聚合节,赋给输入模块中定义的那个节和其中定义的每个符号。此时,程序中的每条指令和全局变量都有一个唯一的运行时内存了。

  其次是重定位符号的引用,修改数据节和代码节中符号的引用,使得他们指向正确的运行时内存地址。

 

处理目标文件的一些工具,如下:

AR :创建静态库,插入、删除、列出和提取成员。

NM :列出一个目标文件的符号表中定义的符号。

LDD :列出一个可执行程序文件在运行时所需的共享库。

 

 

收藏:不周山之读薄 CSAPP

符号及符号解析

参考:《深入理解计算机系统》