GCC编译、链接、运行时库查找顺序(最真实可信)

参考了不少资料,其中最靠谱是这个:http://www.mingw.org/wiki/librarypathhowto

和http://www.kaizou.org/2015/01/linux-libraries/

经过线上实际验证,GCC编译、链接、运行时库查找顺序如下,这个顺序真实可信,网上很多说法有些地方都是有些问题的,

导致遇到问题时总是不确定到底是哪里出了问题,花了不少时间,绝知此事要躬行。

略微让人头疼的是,没有找到官方文档清晰说明什么情况下该如何查找依赖库目录,链接时查找直接依赖库和间接依赖库的方式

差别挺大。。。

一、概括GCC链接时搜索直接依赖库的先后顺序:

1、LDFLAGS选项 -L 参数指定的路径。
2、系统环境变量 LIBRARY_PATH(某些系统或编译器下可能无效)。
3、gcc安装时自身配置的搜索路径,gcc --print-search-dir | grep libraries 可查看,

一般会包含该版本gcc必需的库而不一定包含当前系统库路径,链接时会以-L参数形式传递给ld。

4、ld安装时自身配置的搜索路径,ld -verbose | grep SEARCH_DIR 可查看,

gcc通过调用collect2工具调用ld。

To summarize, when linking an executable against a static library, you need to specify explicitly all dependencies towards shared libraries introduced by the static library on the link command.


二、概括GCC链接时搜间接依赖库直接依赖库的依赖库的先后顺序:(即secondary dependencies

特别说明:搜索直接依赖库的链接查找顺序同样适用于编译生成动态的( shared)、可重定位(relocatable)目标的情形。

特别说明:本条目所列链接顺序仅适用于编译生成非动态的( non-shared)、非可重定位(non-relocatable)目标的情形,比如生成二进制可执行程序,ld手册中有明确说明,是否适合其它情形不确定。在此种情形下,查找一个依赖库自身的依赖库时,-L参数指定的搜索目录无效(至少某些情况下是如此),这不同于链接一个二进制时查找直接依赖目录的顺序。
(1) 参考linux man手册 ld说明:https://linux.die.net/man/1/ld  “ -rpath-link= dir”章节
(2)参考同样问题的分析:http://www.kaizou.org/2015/01/linux-libraries/
链接时对于依赖库中依赖的搜索顺序如下(摘自ld手册):

-rpath-link=dir

When using ELF or SunOS, one shared library may require another. This happens when an "ld -shared" link includes a shared library as one of the input files.

When the linker encounters such a dependency when doing a non-shared, non-relocatable link, it will automatically try to locate the required shared library and include it in the link, if it is not included explicitly. In such a case, the -rpath-link option specifies the first set of directories to search. The -rpath-link option may specify a sequence of directory names either by specifying a list of names separated by colons, or by appearing multiple times.

This option should be used with caution as it overrides the search path that may have been hard compiled into a shared library. In such a case it is possible to use unintentionally a different search path than the runtime linker would do.

The linker uses the following search paths to locate required shared libraries:

(1)  Any directories specified by -rpath-link options.

(2) Any directories specified by -rpath options. The difference between -rpath and -rpath-link is that directories specified by -rpath options are included in the executable and used at runtime, whereas the -rpath-link option is only effective at link time. Searching -rpath in this way is only supported by native linkers and cross linkers which have been configured with the --with-sysroot option.

(3) On an ELF system, for native linkers, if the -rpath and -rpath-link options were not used, search the contents of the environment variable "LD_RUN_PATH".

(4) On SunOS, if the -rpath option was not used, search any directories specified using -L options.

(5) For a native linker, the search the contents of the environment variable "LD_LIBRARY_PATH".

(6) For a native ELF linker, the directories in "DT_RUNPATH" or "DT_RPATH" of a shared library are searched for shared libraries needed by it. The "DT_RPATH" entries are ignored if "DT_RUNPATH"entries exist.

(7) The default directories, normally /lib and /usr/lib.(这里应该是指 ld -verbose | grep SEARCH_DIR显示的搜索目录)

(8) For a native linker on an ELF system, if the file /etc/ld.so.conf exists, the list of directories found in that file.

If the required shared library is not found, the linker will issue a warning and continue with the link.


“Ok, this is not crystal-clear, but what it actually means is that when specifying the path for a secondary dependency, you should not use -L but -rpath-link

To summarize, when linking an executable against:

  • static library, you need to specify all dependencies towards other shared libraries this static library depends on explicitly on the link command.

  • shared library, you don’t need to specify dependencies towards other shared libraries this shared library depends on, but you may need to specify the path to these libraries on the link command using the -rpath/-rpath-link options.

Note however that expressing, discovering and adding implicit libraries dependencies is typically a feature of your build system (autotoolscmake), as demonstrated in my samples.”


二、概括GCC运行时库的搜索库先后顺序:
1、程序自身的RPATH(Library rpath), readelf -d bin可查看,在链接时通过-rpath参数写入程序ELF结构信息中,
而传入链接器中默认的-rpath参数是来自安装gcc时配置的文件specs(其中的配置项linker)。
2、编译时LDFLAGS选项 -L 参数指定的路径。
3、系统环境变量LD_LIBRARY_PATH。
4、在 /etc/ld.so.conf.d/ 目录下的配置文件指定的动态库绝对路径(通过ldconfig生效,一般是非root用户时使用)。
5、gcc安装时自身配置的搜索路径,gcc --print-search-dir | grep libraries 可查看,一般会包含该版本gcc必需的库
而不一定包含当前系统库路径。
 
三、补充说明:
1、在链接和运行查找库的过程中,只要找到同名的库则不管该库是否兼容均停止查找,
即便兼容的库可能出现在靠后的搜索路径中。
2、安装高版本gcc时一般是直接解压已经编译好的gcc,然后直接复制一份至新的路径下,
bcloud就是这么做的,可能是为了便于恢复编译环境吧。
3、如果系统自带的是低版本gcc,而需要使用高版本gcc编译程序,如果该程序依赖了非系统库(低版本gcc编译),
如果这个非系统库的安装目录下恰好有和高版本gcc依赖的库同名的库,那么为了能够使用这个非系统库,
这时高版本gcc编译时很可能会出现问题,必需保证链接器在查找库时, 查找高版本gcc自身库一定要先于任意系统库(或任意库)所在的目录
原因是高版本gcc应该是改进了相当一部分系统库,和当前低版本系统库并不兼容,而对于兼容的那些库,高版本gcc库可以直接使用当前系统库。 

  • You can pass the -v option to gcc so that it shows you how it invokes the linker. In fact, it normally does not invoke ld directly, but indirectly via a tool called collect2 (which lives in one of its internal directories), which in turn invokes ld. That will show you what -L options are being used.
  • You can add -Wl,--verbose to the gcc options to make it pass --verbose through to the linker, to see the linker script as described above.

四、库版本号

  如果 major version number不一致,说明库的ABI接口是不兼容的,不同major version 库之间不能相互引用或依赖,相同major version一般是可以相互引用和依赖的,但工作时具体行为是否正确需要进一步验证。

  Linux上的shared library有三个名字,分别是:

  • 共享库本身的文件名(real name)

    其通常包含完整的版本号,比如:libmath.so.1.1.1234 。lib是Linux库的约定前缀,math是共享库名字,so是共享库的后缀名,1.1.1234的是共享库的版本号,由主版本号+小版本号+build号组成。主版本号,代表当前动态库的版本,如果共享库的接口发生变化,那么这个版本号就要加1;后面的两个版本号(小版本号和 build号)是用来指示库的更新迭代号,表示在接口没有改变的情况下,由于需求发生变化等因素,开发的新代码。

  • 共享库的soname(Short for shared object name)

    用来告诉应用程序,在加载共享库的时候,应该使用的文件名。其格式为lib + math + .so + (major version number) 其只包含主版本号,换句话说,也就是只要共享库的接口没有变,soname就能与real name保持一致,因为主版本号一样。所以在库的real name的小版本号和 build号发生改变时,应用程序仍然可以通过soname得知,要使用的是哪个real name。

  • 共享库的链接名(link name)

    是专门为应用程序在编译时的链接阶段而用的名字。这个名字就是lib + math +.so ,比如libmath.so。其是不带任何版本信息的。在共享库的编译过程中,编译器将生成一个共享库及real name,同时将共享库的soname写在共享库文件里的文件头里面。可以用命令readelf -d sharelibrary | grep soname查看。 在应用程序引用共享库时,链接选项里面用的是共享库的link name。通过link名字找到对应的real name动态库,并且把其中的soname提取出来,写在应用程序自己的文件头的共享库字段里面。当应用程序运行时,就会通过soname,结合动态链接程序(ld.so),在给定的路径下加载real name的共享库。

以上。

你可能感兴趣的:(linux系统编程)