连接原理
简介:开发大型程序的时候,往往要多人协作,编写库进行共享,从而提高效率。那么库的原理是什么?和每个源程序是如何结合在一起的?程序的装载过程是什么样的?本文主要解释了这些问题,链接的原理和库文件的编写和使用。
1.1连接器的任务
连接器将多个目标文件连接成为一个完整的、可加载的、可执行的目标文件。其输入是一组可重定位目标文件。任务如下:
符号解析:将目标文件内的引用符号和该符号的定义联系起来。
将符号定义与存储器的位置联系起来,修改对这些符号的引用。
1.2目标文件
可重定位目标文件:包含二进制代码和数据和对其他模块的符号引用,不可以直接执行;对未知符号的解析需要连接器将所有的模块进行链接。它的后缀名通常是.o
可执行目标文件:同样包含二进制代码和数据,但是已经经过链接操作,和所有的模块都产生了联系,可以被直接执行。
共享目标文件:也称为共享库或者动态库,需要它的程序在运行时加载。后缀名通常是.so
1.3ELF格式的可重定位目标文件
Linux上的可重定位目标文件和可执行的目标文件都采用这种格式,包括二进制的代码和数据,同时包含很多帮助连接器解析符号和解释目标文件的信息。一个典型的ELF格式的可重定位目标文件结构主要包括ELF文件头和目标文件的段,如下:
各个部分的意义说明如下(从上到下依次为从低地址到高地址):
ELF头:前16个字节构成了一个字节序,描述了生成该文件系统的字长和字节序。剩下的部分包括:ELF头的大小,目标文件的类型,目标机器的机型等等。我们可以用readelf -h filename.o来查看,例如:
[10:32huangyk@command]$>readelf -h echo.o ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: REL (Relocatable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x0 Start of program headers: 0 (bytes into file) Start of section headers: 292 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 0 (bytes) Number of program headers: 0 Size of section headers: 40 (bytes) Number of section headers: 11 Section header string table index: 8------------------------------------------------------------------------------------
.text:代码段
.data&&.rodata:数据段
.bss:为初始化数据段
------------------------------------------------------------------------------------(以上四个会实际加载到内存中)
.symtab:该节区保存着一个符号表,正如在这个节区里符号表的描述
.ref.text:代码重定位需要的信息,通常是一些函数名称和语句标号
.rel.data:数据段重定位需要的信息,通常是一些全局变量
.debug:调试信息,gcc -g 会生成它。
.line:源程序的行号映射,gcc -g会生成它。
.strtab:字符串表
1.4目标文件中的符号表
符号表中的符号主要分为三类:本模块中引用的其他模块定义的全局符号;本模块中定义的局部符号;本模块中定义和引用的局部符号。
注意:局部变量在栈中,仅仅是一个内存中出现的概念;局部符号包括静态变量和局部标号,这些内容也可能出现在磁盘文件中。
这里可以使用readelf命令,查看并分析符号表中的符号。
1.5重定位的概念
重定位过程中,链接器需要将所有参与连接的目标文件合并,并为每个符号分配存储内容的运行时地址。重定位分为一下两个步骤进行:
重定位段:合并同类段,之后程序中的指令和变量就拥有一个统一而且唯一的运行时地址。
重定位符号引用:修改引用符号的地址,使其指向正确的运行时地址
1.6符号的重定位信息
编译器生成一个目标文件之后,并不知道代码和变量的最终存储位置,也不知道定义在其他文件中的外部符号。所以编译器会生成一个重定位表目,里面存储关于每一个符号的信息。这个表目告知连接器在合并目标问件事应该如何修改每个目标文件中对符号的引用。这种重定位表目存储在.rel.text和rel.data段中。
库
2.1使用库的优势
程序库:包含了一些通用函数的数据和二进制可执行机器码的文件。他们是目标文件的一种,不能单独执行,可以与其他程序合并执行。好处是:模块化更强,加快程序编译,提高代码复用性,易于升级和管理。
2.2静态库的概念
静态哭是一些目标代码的集合,linux下静态哭目标文件一般以.a结尾。静态库使用简单,编译快速,在应用程序生成式,已经编译为可重定位目标文件,节省了编译时间。由于静态库在编译时已经复制到可执行程序的代码段中,所以不需要在程序加载时再次链接。
2.3创建静态库
Eg:创建自己的数学函数库:
编译成目标文件:
gcc -c static_lib.c
生成动态库:
ar rcs static_lib.a static_lib.o其中,ar的参数rcs表示r——insert(将列表中文件加入指定库),C——create(如果库不存在就创建它),s——更新静态库的索引。
2.4使用静态库
为了使用静态库,首先要编写头文件告知应用程序有哪些可用资源。然后制定静态库就可以使用了。
编写头文件:
应用程序:
指定静态库:参见gcc相关命令
gcc main.c -lstatic_lib.a -o output.exe
gcc mainc -static ./static_lib.a -o output.exe
2.5动态库的概念
多个进程同时使用动态库只有一个副本。特点是:1)模块性强,便于更新和管理
2.6动态库的创建
gcc -shared -fPIC -o share_lib.so share_lib.c
2.7动态库的使用
gcc main.c ./share_lib.so output.exe