链接器符号解析算法小解以及静态库链接顺序等等问题

一、编译链接

在编写linux驱动程序时,时常会发现链接出错,当时往往不知道错误在哪。现在了解到链接器的工作原理之后,明白当时为什么出错了。对于以后有效率地编写驱动程序有很大帮助。
一个C语言程序,经过诸如GCC之类的编译器编译成可执行文件一般会经历4个处理过程,这个大部分的linux入门书籍都有讲到过,如果没有扔掉它,:)!
分别经过C预处理器(CPP)、C编译器(CL)、C汇编器(AS)、C链接器(LD)。这个怎么处理生成什么不是本文的主要内容。
ASCII源文件.C----》ASCII中间文件.i(由CPP处理后生成)------》汇编语言文件.s(由CL处理后生成)-----》可重定位目标文件.o(由AS处理后生成)-----》可执行目标文件(由LD处理后生成)。

 

二、可执行文件


说到可执行目标文件和可重定位目标文件不得不讲讲ELF(可执行可链接格式),但ELF在大部分书籍都有讲到,且网上资料相当多。
ELF定义了可重定位和可执行目标文件构成的大致框架。可重定位目标文件中,有很多的节,每个节都是固定大小的条目。这些条目存储了代码、符号、引用、变量和调试等等相关信息。
一个点C文件会引用其他文件的函数或者是全局变量,那么经过上面那一套走下来,会生成点O文件也就是可重定位目标文件。在可重定位目标文件中也会存储引用的函数或全局变量信息,而这些引用的函数和全局变量在链接生成可执行程序时都要进行重定位。
典型地,.rel.text:代码段需要重定位的信息。主要是一些引用的函数;.rel.data:数据段需要重定位的信息。主要是本模块引用的全局变量等。

 

三、链接与引用


那么,当我们将所有的可重定位目标文件和静态库生成完成之后,输入命令:
GCC ex1.c ex2.o ex3.o ex4.a会发生什么呢?
可以很简单的说,就是LD链接器会将它们连接生成可执行文件。具体有两个步骤:1是符号解析,2是重定位。
那么符号到底是如何解析的呢?
还是上面的例子:
GCC ex1.c ex2.o ex3.o ex4.a
这其中ex4.a包含ex5.o和ex6.o
不失一般性,我们假设有:
ex1.c引用了ex2.o中的符号2.A,ex3.o中的3.A,ex4.a里面的ex5.o中5.A,ex7.o中的7.A;
ex2.o则是ex3.o中的3.B
ex3.o则是ex2.o中的2.B,
就是如下图所示的链接器命令引用实例:

当然此处假设ex2.o定义了2.A和2.B符号,ex3.o和ex5.o 、ex6.o与此类似。

 

四、符号解析


在符号解析阶段链接器从左往右按照命令行的顺序来扫描可重定位目标文件和静态库。它维持了三个集合E、U和D。
E是可重定位目标文件的集合
U是未解析符号的集合
D为已定义的符号的集合
链接器按照如下步骤来解析符号:

  步骤1 步骤2 步骤3 步骤4
E Ex1.o Ex1.o Ex2.o Ex1.o Ex2.o Ex3.o  Ex1.o Ex2.o Ex3.o Ex5.o 
U 2.A 3.A 5.A 3.A 5.A 3.B 5.A
D   2.A 2.B 2.A 2.B 3.A3.B 2.A 2.B 3.A 3.B 5.A

步骤1,未解析的符号有2.A3.A 4.A三个;
步骤2,由于EX2.O的加入,更新E集合,由于EX2.O本身定义了2.A 2.B两个符号,所以U集合中的2.A 2.B引用可以被解析。解析后的符号被放入到D集合中
步骤3同步骤2;
步骤4中,静态库包含了EX5.O和EX6.O两个可重定位目标文件,那么LD会逐个拿U集合中未解析的符号和静态库的可重定位目标文件中定义的符号匹配,如果有匹配的,这将其加入到E中,如果没有则进行下个可重定位目标文件的匹配过程。本例中,由于Ex5.o中定义的符号5.A正好和U集合中的5.A匹配,那么Ex5.o自身就被放入到E集合中了。像Ex6.o定义个符号和U匹配不上,就没有被放入到E集合中。从这点来看,LD不会将那么没有引用的可重定位目标文件链接到可执行文件中。
最后LD发现,扫描完所有的目标文件和静态库了且U集合为空,那么就会合并和重定义E集合中的目标文件,从而构建出可执行文件了。
若将上面命令改为如下命令:
GCC ex1.c ex2.o ex3.o ex4.a ex7.o
ex7.o引用了ex4.a中ex6.o中的6.A,那么会怎么样呢?
答案是LD会输出错误,并终止。
考虑到步骤4之后,扫描ex7.o的过程。
步骤4之后,还有ex7.o要扫描,那么LD继续扫描ex7.o。ex7.o被加入到E中,
将6.A加入到U中。
这样扫描完了所有的目标文件和静态库了,但是且U集合不为空。LD就会输出错误并终止。
所以一般书籍给的忠告是,将静态库尽量放在命令的最后,也可以反复输入静态库。
由此,为了避免上面的错误,我们可以这样:
GCC ex1.c ex2.o ex3.o ex7.o ex4.a
或是这样
GCC ex1.c ex2.o ex3.o ex4.a ex7.o ex4.a
从上面不难看出,LD只会把引用了的可重定位目标文件链接到可执行文件中,那些没有引用的则不会被包含进去。
以上只是说静态库和可重定位目标文件有先后顺序之分,顺序不当会造成链接错误。如果全部都是可重定位目标文件,那么就没有先后顺序之分了。
更多信息请参见:
《C专家编程》c5以及《computer systems a programmer's perspective》c7
本人享有博客文章的版权,转载请标明出处http://blog.csdn.net/baidu20008

posted @ 2018-01-10 22:01  zzfx  阅读(464)  评论(0编辑  收藏  举报