> 文章列表 > 静态链接TvT

静态链接TvT

静态链接TvT

                当有两个目标文件时,如何将它们链接起来形成一个可执行文件?假设我们使用下面图1的2个源文件代码a.c和b.c作为例子分析。

图1

                首先我们使用gcc将"a.c"和"b.c"分别编译成目标文件"a.o"和"b.o"(gcc -c 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"。

空间与地址分配

                对于连接器来说,整个链接过程中,它就是将几个输入目标文件加工后合并成一个输出文件。那么在这个例子里,我们的输入就是目标文件"a.o"和"b.o",输出就是可执行文件"ab"。我们知道,可执行文件中的代码段和数据段都是由输入的目标文件合并而来的。那么我们链接过程就很明显产生了第一个问题:对于多个输入目标文件,连接器如何将它们的各个段合并到输出文件?或者说,输出文件中的空间如何分配给输入文件。

        按序叠加

                一个简单的方案就是将输入的目标文件按照次序叠加起来,如图2所示。

图2

                 图2的做法很简单,就是直接将各个目标文件依次合并。但是这样做会造成一个问题,在有很多输入文件的情况下,输出文件将会由很多零散的段。比如一个规模稍大的应用程序可能会有数百个目标文件,如果每个目标文件都分别有.text段,.data段和.bss段,那最后的输出文件将会由成百上千个零散的段。这种做法非常浪费空间,因为每个段都需要有一定的地址和空间对齐要求,比如对于x86的硬件来说,段的装载地址和空间的对齐单位是也,也就是4096字节。那么就是说如果一个段的长度只有1个字节,它也要在内存中占用4096字节。这样会造成内存空间大量的内部碎片,这并不是一个很好的方案。

        相似段合并

                一个更实际的方法是将相同性质的段合并到一起,比如讲所有输入文件的".text"合并到输出文件的".text"段,接着是".data"段,".bss"段,如图3所示。

图3

                 现在连接器空间分配的策略基本上都采用上述方法中的第二种,使用这种方法的链接器一般都采用一种叫做两步链接的方法。也就是说整个链接过程分两步。

                第一步  空间与地址分配  扫描所有的输入目标文件,获得它们的各个段的长度,属性和位置,并且将输入目标文件中的符号表中所有的符号定义和符号引用收集起来,统一放到一个全局符号表。这一步中,链接器将能够获得所有输入目标文件的段长度,并且将它们合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系。

                第二步 符号解析与重定位 使用上面第一步中收集到的所有信息,读取输入文件中段的数据,重定位信息,并且进行符号解析与重定位,调整代码中的地址等。事实上第二步是链接过程的核心,特别是重定位过程。

                我们使用ld连接器将"a.o"和"b.o"链接起来:

                ld a.o b.o -e main -o ab

                -e main 表示将main函数作为程序入口,ld连接器默认的程序入口为_start。

                -o ab 表示链接输出文件名为 ab,默认为a.out。

                让我们使用objdump来查看链接前后地址分配情况,如图4所示。

                图4

                 从图4可以看到链接前后各个段的熟悉,VMA表示Virtual Memory Address,即虚拟地址,LMA表示Load Memory Address,即加载地址,正常情况下这两个值应该是一样的。

                链接前后的程序中所使用的地址程序在进程中的虚拟地址,即我们关心上面各个段中的VMA和Size,而忽略文件偏移。我们可以看到,在链接之前,目标文件中的所有段的VMA都是0,因为虚拟空间还没有被分配,所以它们默认都为0。等到链接之后,可执行文件"ab"中的各个段都被分配到了相应的虚拟地址。这里的输出程序"ab"中,".text"段被分配到了地址0x400e8,大小0x71字节;".data"段从地址0x6001b8,大小为4字节。整个链接过程前后,目标文件各段的分配,程序虚拟地址如图5所示。

               图5

                在图5中,可以看到"a.o" 和"b.o"的代码段被先后叠加起来,合并成了"ab"的一个.text段,加起来长度为0x71,所以"ab"的代码段里面肯定包含了main函数和swap函数的指令代码。

图6 

                使用objdump命令看到地址0x6001b8所得内容是0x64,对照图1可以看到变量shared的值等100,能相互对的上。