13

日常遐想之链接器

链接是一个很有意思的话题,动态链接,静态链接以及在各种变成语言中的应用。想想都是一个很有意思的。

ELF 文件格式

在计算机系统中,要想执行一个文件,那么这个文件就需要有特定的符合当前系统的执行文件格式。在windows 系统上的文件格式标准是PE(ortable Executable),而Unix和类Unix系统上所使用的是ELF(Executable and Linkable Format )格式。当然还是有其他文件格式的。在linux系统中,ELF文件格式是标准。

每个ELF文件是由一个ELF 头, 节头部表,以及夹在两者之间的节组成的。

ELF文件的格式

目标文件参与了程序链接(用来构建一个程序)和程序的执行(运行一个程序),从便利和效率上看,文件格式为文件的内容提供了不同视角。下图展示了一个目标文件的组织结构。

目标文件

  • 可重定位的文件:包含代码和数据,用于同其他文件链接起来的形成可执行的文件或者可共享的文件。
  • 可执行文件:包含代码和数据,可以直接用于加载器加载到存储器中并执行。
  • 可共享文件:包含代码和数据,可在两个上下文域中进行链接。首先,链接编辑器可以用它与可重定位文件或可共享文件去创建另外一个目标文件;其次,动态链接器将它与可执行文件和其他共享文件去创建一个进程镜像。

ELF 文件头中的信息

目标文件的节

目标文件定义的节,如下图所示:

节的内容

一下三个是这篇博文中要说的:

  • .data : 已经初始化的全局C变量,局部C变量运行时保存在栈。
  • .bss : 未初始化的全局C变量,仅作占位符之用。
  • .symtab : 存放在程序中定义和引用函数和全局变量,和编译器的符号表不同,.symtab不包含局部变量的条目。

符号和符号表

每个可重定位的目标文件obj都有一个符号表,符号表中的符号分为三种类型:

1. 由目标文件obj定义的并且能被其他目标文件所引用的全局符号,全局链接器符号对应于非静态的C函数以及被定义为不带 C static 属性的全局变量。

2. 由其他目标文件将定义的被目标文件obj引用的,称为外部符号,对应于定义在其他模块的C函数和变量。

3. 只被目标文件obj定义和引用的本地符号。对应于带 static 属性的 C函数和全局变量。带 static 的本地变量不在栈中管理,而是在 .data.bss为其分配空间。

符号解析

在编译的过程中,编译器想汇编器输出的每个全局符号,汇编器将它们隐含地包含在可重定位目标文件的符号表里。已初始化的全局变量为强符号,未初始化的全局变量为弱符号。Unix用以下规则来处理多重定义的符号。

  • 不允许拥有多个强符号
  • 如果同时拥有强符号和弱符号,那么选择强符号
  • 如果拥有多个弱符号,那么从中随意选择一个

静态链接

所谓的静态库,就是包装了很多函数的一个集合,这个集合中包含着关于相关库的路径,然后在编译程序的时候,链接器仅会将静态库中被引用的目标模块拷贝。静态库会以归档的特殊文件格式存储。

在符号解析的阶段,链接器从左到右来扫描可重定位目标文件和存档文件,并且维持一个可重定位目标文件集合E(当然了它们是用来形成可执行目标文件的),未解析的符号集合U,已定义的符号集合。

  • 对于命令行上输入的每个文件f,如果是一个目标文件,那么就将它放入E中,修改U和D来反应f中的符号定义和引用,并继续下一个文件。
  • 如果输入的这个文件是归档文件,那么链接器就尝试匹配U中未解析的符号和由这个归档文件的成员所定义的符号,如果该成员定义的符号与U中的符号相匹配,那么就将改成员添加到E中,并对其他成员进行相似的操作,知道成员结束。然后将未包含在E中的模块丢弃掉。
  • 当过程以后,如果U是非空的,那么此时将会报错。

在命令行中输入的库如果有相互依赖的关系,那么还是必须要分清楚先后关系的。比如说:

gcc foo.c lib1.a lib2.a

在运行的时候,如果 lib2.a 依赖 lib1.a ,那么在运行的时候,解析到lib2.a 的时候,因为在解析lib1.a 的时候未经引用的符号被简单的丢弃了,所以在解析lib2.a完了以后,U 的集合并没有为空,所以会报错。

动态链接

引用

<黑客与画家>