静态链接中的那点事儿(1)
作为一个程序员或者说C++程序开发人员,想必对ELF目标文件从整体轮廓到某些局部的细节都非常熟知。该系列帖子主要为了解决一个疑惑:当我们有多个目标文件时,如何将它们连接起来形成一个可执行文件?这个过程发生了什么?其实,读到这里,可能就了解到,这其实就是链接的核心内容:静态链接。
1.应用到的两个源代码文件
<span style="font-size:18px;">/* a.c */ extern int shared;int main() {int a = 100;swap(&a, &shared);cout << a;cout << endl;cout << shared; }</span>
<span style="font-size:18px;">/* b.c */ int shared = 1;void swap(int* a, int* b) {*a ^= *b ^= *a ^= *b; }</span>
首先使用GCC将“a.c”、“b.c”分别编译称目标文件“a.o”、“b.o”。从代码中,我们可以看到,“b.c”一共定义了两个全局符号:一个是变量“shared”,另一个是函数“swap”;“a.c”中定义了一个全局符号就是“main”。模块“a.c”中引用到了“b.c”中的swap和shared。我们要研究的工作就是“a.o”文件与“b.o”文件是怎么样链接成一个可执行文件 “ab”的?
2.空间与地址分配
对于连接器而言,整个链接过程中,他就是将几个输出目标文件加工后合并成一个输出文件。根据我们已有的ELF文件格式知识,我们知道可执行文件中的代码段和数据段都是由输入的目标文件合并而来的。这里我们首先来探索第一个问题:对于多个输入目标文件,链接器如何将它们的各个段合并到输出文件?或者说,输出文件中的空间如何分配给输入文件?
按序叠加:
按序叠加的思想非常简单粗暴,就是直接将各个目标文件一次合并,该思路可以用下面图示说明:
该种方法的确很简单,但是带来一个很直接的问题:在很多输入文件爱你的情况下,输出文件会有很多零散的段。比如说,一个规模稍大的应用程序可能会有数百个目标文件,如果每个目标文件都分别有.text段、.data段、.bss段,那最后的输出文件将会有成百上千个零散的段。这种做法非常消耗空间,造成内存空间中大量的内部碎片,并不是一个很好的方案。
相似段合并:
一个更实际的方法就是将相同性质的段合并到一起,其设计思想如下图所示:
正如我们了解到的,“.bss”段在目标文件和可执行文件中并不占用文件空间,但是在装载时需要占用地址空间。所以链接器在合并各个段的同时,也会将“.bss”段合并,并且分配虚拟空间。这里有一个问题,先前一直很迷惑,这里可以做一个小小的理解。所谓的“空间分配”到底是什么空间?
其实“连接器为目标文件分配地址和空间”这句话中的“地址和空间”有两个含义:第一个是指在输出的可执行文件中的空间;第二个是在装载后的虚拟地址中的虚拟地址空间。对于有实际数据的段,比如".text"和“.data”来说,他们在文件中和虚拟地址中都要分配空间,因为他们在这两个里面都存在!然而,对于“.bss”这样的段来说,分配空间的意义只限于虚拟地址空间,因为他在文件中并没有内容。事实上,我们在这里谈到的空间分配只关注与虚拟地址空间的分配。
当代操作器多采用后一种空间分配策略。整个链接过程可以分为两步:
1.空间与地址分配。扫描所有输入的目标文件,并且获得他们的各个段的长度、属性和位置,并且将输入目标文件中的符号表综所有的符号定义和符号引用收集起来,统一放到一个全局符号表中。这一步,链接器将能够获得所有目标文件的段长度,并且将它们合并。而且通过计算出输出文件中各个段合并后的长度和位置,建立映射关系。2.符号解析与重定位。在上面收集到信息的基础上,读取输入文件中段的数据、重定位信息,并且进行符号解析和重定位、调整代码中的地址。这一步才是链接过程的核心,特别是重定位过程。
连接前后各个段的属性分析:
注:VMV(Virtual Memory Address,虚拟地址);LMA(Load Memory Address,加载地址)。正常情况下两者一样。
这个连接前后,目标文件各段的分配、程序虚拟地址等可以用下图表示:
符号地址的确定:
在第一步完成之后,连接器开始计算各个符号的虚拟地址。因为各个符号在段内的相对位置是固定的,所以这个时候“main”、"shared"、"swap"等地址也就确定了,只不过链接器需要给每个符号加上一个偏移量,使他们能够调整到正确的虚拟地址。
3.符号解析与重定位
重定位:
在完成空间和地址的分配步骤以后,链接器就进入符号解析与重定位的步骤,这也是静态链接的核心内容。在分析符号解析与重定位之前,首先看看“a.o”里面是怎么样使用那两个东东的("shared"、"swap")【也就是说,我们在a.c源文件中使用了“shared”变量和“swap”函数,那么编译器再将“a.c”编译成指令时,是如何访问该变量?以及如何调用该函数的呢?】
对a.o文件进行反汇编得到下面代码清单:
注:在程序的代码里面使用的都是虚拟地址,从上图中我们可以看到“main”函数的起始地址是0x0000000,这是因为在未进行空间分配之前,目标文件代码段中的起始地址都应该以0x0000000开始,等待空间分配完成以后,各个函数才会确定自己在虚拟地址空间中的位置。
通过反汇编结果,我们能够清楚的看到,”a.o“共定义了一个main函数,共占用0x33个字节,共17条指令;对于变量”shared“的引用是一条”move“指令,它的作用是将”shared“的地址赋值到ESP寄存器+4的偏移地址中去。对于函数”swap“的引用是一条”call“指令。
重定位表:
那么链接器是怎样知道哪些指令是要被调整的呢?这些指令有哪些部分需要部分调整呢?又怎么进行调整?这些工作其实就是依赖重定位表完成的!该结构专门用来保存这些与重定位有关的信息。
符号解析:
其实在我们普通的观念中,之所以要进行链接是因为我们的目标文件中用到的符号被定义在其他的目标文件中了,所以我们要把他们整合起来。比如,如果我们直接用ld命令来链接”a.o“文件,而不输入”b.o“文件,那么链接器就会报错,提示我们没有发现shared和swap两个符号的定义,链接就会失败,实验效果如下图所示:
其实,这也是我们平时在编写程序的时候最常见到的问题:链接是符号没有定义!导致这个问题的原因有很多,最常见的一般都是链接时缺少了某个库,或者输入目标文件路径不正确或符号的生命与定义不一样!!!
4.小结
通过前面的介绍,我们可以更加深层次地理解为什么缺少符号的定义会导致连接错误!其实重定位过程也伴随符号的解析过程,每个目标文件都可能定义一些符号,也可能引用到定义在其他目标文件中的符号。重定位的过程中,每个重定位的入口都是对一个符号的引用,那么当链接器需要对某个符号的引用进行重定位时,就必须明白该富豪的目标地址。这时候,链接器就会去查找由所有目标文件的符号所组成的全局符号表,找到相应的符号后就可以开展重定位工作。
静态链接中的那点事儿(1)相关推荐
- 静态链接中的那点事儿(2):C++二进制兼容性及跨平台初步
C++的一些语言特性使之必须由编译器和链接器共同支持才能完成工作.最主要的有两个方面,其一,C++的重复代码的消除:其二,全局构造与析构.此外,由于C++的各种特性,比如虚函数.函数重载.继承.异常等 ...
- 动态链接库dll,静态链接库lib, 导入库lib
目前以lib后缀的库有两种,一种为静态链接库(Static Libary,以下简称"静态库"),另一种为动态连接库(DLL,以下简称"动态库")的导入库(Imp ...
- linux gcc 静态编译,GCC 程序编译的静态链接和动态链接
(给Linux爱好者加星标,提升Linux技能)转自:Mr_Bluyee 在链接阶段中,所有对应于源文件的 .o 文件.'-l' 选项指定的库文件.无法识别的文件名(包括指定的.o目标文件和.a库文件 ...
- 动态链接库dll,静态链接库lib, 导入库lib 转
动态链接库dll,静态链接库lib, 导入库lib 在用VS编译工程的时候,我们会选择动态链接库dll,静态链接库lib(static library),可是为什么在编译动态链接库的时候也可以指定输出 ...
- libcurl linux 静态链接库_GCC 程序编译的静态链接和动态链接
转自:Mr_Bluyee 在链接阶段中,所有对应于源文件的 .o 文件."-l" 选项指定的库文件.无法识别的文件名(包括指定的.o目标文件和.a库文件)按命令行中的顺序传递给链接 ...
- 动态连接库和静态链接库
文章出处:http://www.cnblogs.com/gaoyihan/p/4723332.html 本文参考了以下博客: 1. http://blog.csdn.net/gamecrea ...
- 静态链接库(LIB)和动态链接库(DLL),DLL的静态加载和动态加载,两种LIB文件。
静态链接库(LIB)和动态链接库(DLL),DLL的静态加载和动态加载,两种LIB文件. 一. 静态链接库(LIB,也简称"静态库")与动态链接库(DLL,也简称"动态库 ...
- “.dll .obj .lib”和“ .so .o .a”文件与动态链接和静态链接
".dll .obj .lib"和" .so .o .a"文件 (1) .dll .obj .lib使用在windows平台下. .dll:动态链接库,作为共享 ...
- 静态链接库与动态链接库区别
一. 静态链接库与动态链接库区别 静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib 中的指令都全部被直接包含在最终生成的 EXE 文件中了.但是若使 ...
最新文章
- ASP.NET页面动态添加js脚本
- java.util.Date和java.sql.Date
- 移动网页设计9大原则——第1部分
- Android Studio下载及安装3.0版本
- 除了 Python ,这些语言写的机器学习项目也很牛
- GCC Inline ASM GCC内联汇编
- centos安装mysql5.7.12_CentOS二进制安装MySQL5.7.12
- python连接influxdb_python 访问 InfluxDB 数据库
- 创建Podfile,添加类库,中途添加库指令
- 实现粗糙表面_你了解什么是表面粗糙度吗?
- tia v15 添加项目_基于博途V15 西门子S7-1200 星三角启动实例演示
- csv文件的使用,csv空白行问题
- 我为什么不再推荐 RxJava
- Teamviewer远程黑屏
- java获取手机型号
- 量化交易员珍藏的10本书,一般人不会教你的事
- Linux下,为应用程序添加桌面图标(ubuntu18.4)
- 转向新页,控制页面大小并传值
- 动态平衡网格交易_微笑每周答——定投基金,哪种方法最好?低估值、动态再平衡、网格交易法、价值平均策略,总有一款适合你...
- 读《测试构架师修炼之道》-Chapter4 测试构架师的知识能力模型