linux编译时和运行时,库搜索路径和顺序

【原文:http://linux.chinaunix.net/techdoc/develop/2008/12/26/1054992.shtml
转自:
http://docs.sun.com/app/docs/doc/820-1204/aeudh?l=zh&a=view
http://docs.huihoo.com/gnu/gcc/gcc_howto/GCC-HOWTO-6.html
有改动。
LD_LIBRARY_PATH 环境变量
使用 LD_LIBRARY_PATH 环境变量指定链接程序应在哪些目录路径中搜索用 -llibrary 选项指定的库。
可以指定多个目录,其间用冒号分隔。通常,LD_LIBRARY_PATH 变量包含两个用冒号分隔的目录列表,列表间用分号隔开:
dirlist1;dirlist2 
首先搜索 dirlist1 中的目录,接着是命令行上用任何显式 -Ldir 指定的目录,再接着是 dirlist2 以及标准目录。
也就是说,如果使用多个 -L 调用编译器,如下所示:
f95 ... -Lpath1 ... -Lpathn ... 
则搜索顺序是:
dirlist1 path1 ... pathn dirlist2 standard_paths 
当 LD_LIBRARY_PATH 变量只包含一个用冒号分隔的目录列表时,它会被解释为 dirlist2。
注 – 


  • 强烈建议不要在编译软件使用 LD_LIBRARY_PATH 环境变量。尽管它作为一种影响链接程序搜索路径的临时机制很有用,包含该环境变量的终端要运行的要动态链接的可执行程序的搜索路径都会被改变。您可能会看到了意想不到的结果或性能降低。


. 库搜索路径和顺序-静态链接
使用 -llibrary 编译器选项对链接程序在解析外部引用时要搜索的其他库命名。例如,用选项 -lmylib 将库 libmylib.so 或 libmylib.a 添加到搜索列表中。
链接程序会在标准目录路径中查找其他的 libmylib 库。-L 选项(和 LD_LIBRARY_PATH 环境变量)会创建一个路径列表,告知链接程序到哪里查找位于标准路径以外的库。
假如 libmylib.a 位于 /home/proj/libs 目录中,则选项 –L/home/proj/libs 会告知链接程序在生成可执行文件时到哪里查找:
demo% f95 -o pgram part1.o part2.o -L/home/proj/libs -lmylib
-l library 选项的命令行顺序
对于任何未解析的特殊引用,只对库进行一次搜索,并且只搜索在搜索时未定义的符号。如果命令行上列出了多个库,则会按其在命令行上出现的顺序来搜索这些库。-llibrary 选项放置在以下位置:


  • 将 -llibrary 选项放置在任一 .f.for.F.f95 或 .o 文件之后。

  • 如果调用了 libx 中的函数,并且这些函数引用了 liby 中的函数,则将 -lx 置于 -ly 之前。

-Ldir 选项的命令行顺序
-Ldir 选项会将 dir 目录路径添加到库搜索列表中。链接程序首先在 -L 选项指定的任何目录中搜索库,然后在标准目录中进行搜索。只有将其放在它所应用的 –llibrary 选项之前,该选项才有用。
. 库搜索路径和顺序-动态链接
对于动态库,库搜索路径和加载顺序的更改与静态情况不同。实际链接发生在运行时而不是编译时。
. 在编译时指定动态库
编译可执行文件时,链接程序会在可执行文件本身中记录共享库的路径。这些搜索路径可以用 -Rpath 选项指定。这一点与 -Ldir 选项相反,该选项在编译时指示到哪里查找 -llibrary 选项所指定的库,但不会将该路径记录到二进制可执行文件中。
使用 dump 命令可以查看创建可执行文件时内置的目录路径。
示例:列出内置于 a.out 之中的目录路径:
demo% f95 program.f -R/home/proj/libs -L/home/proj/libs -lmylib
demo% dump -Lv a.out | grep RPATH
[5]      RPATH    /home/proj/libs:/opt/SUNWspro/lib
. 在运行时指定动态库
运行时,链接程序会确定到哪里查找可执行文件所需的动态库:


  • 运行时 LD_LIBRARY_PATH 的值

  • 生成可执行文件时已由 -R 指定的路径

如前所述,使用 LD_LIBRARY_PATH 会带来意想不到的副作用,因而不建议这样做。
. 修复动态链接期间的错误
当动态链接程序找不到所需库的位置时,它会发出以下错误消息:
ld.so: prog: fatal: libmylib.so: can’t open file:
此消息表明这些库不在其应在的位置。
使用 ldd 确定可执行文件期望在哪儿找到这些库:
demo% ldd a.out
libfui.so.1 =>   /opt/SUNWspro/lib/libfui.so.1
    libfai.so.1 =>   /opt/SUNWspro/lib/libfai.so.1
    libfai2.so.1 =>  /opt/SUNWspro/lib/libfai2.so.1
    libfsumai.so.1 =>    /opt/SUNWspro/lib/libfsumai.so.1
    libfprodai.so.1 =>   /opt/SUNWspro/lib/libfprodai.so.1
    libfminlai.so.1 =>   /opt/SUNWspro/lib/libfminlai.so.1
    libfmaxlai.so.1 =>   /opt/SUNWspro/lib/libfmaxlai.so.1
    libfminvai.so.1 =>   /opt/SUNWspro/lib/libfminvai.so.1
    libfmaxvai.so.1 =>   /opt/SUNWspro/lib/libfmaxvai.so.1
    libfsu.so.1 =>   /opt/SUNWspro/lib/libfsu.so.1
    libsunmath.so.1 =>   /opt/SUNWspro/lib/libsunmath.so.1
    libm.so.1 =>     /usr/lib/libm.so.1
    libc.so.1 =>     /usr/lib/libc.so.1
    libdl.so.1 =>    /usr/lib/libdl.so.1
    /usr/platform/SUNW,Ultra-5_10/lib/libc_psr.so.1
如果可能的话,将这些库移动或复制到正确的目录中,或者在链接程序搜索的目录中建立到该目录的软链接(使用 ln -s)。或者,可能是没有正确设置 LD_LIBRARY_PATH。检查 LD_LIBRARY_PATH 是否包含运行时所需库的路径。
///
. 建立你自己的程序库 
控制版本
与其它任何的程序一样,程序库也有修正不完的bugs的问题存在。它们也可能产生出一些新的特点,更改目前存在的模组的功效,或是将旧的移除掉。这对正在使用它们的程序而言,可能会是一个大问题。如果有一支程序是根据那些旧的特点来执行的话,那怎么办? 
所以,我们引进了程序库版本编号的观念。我们将程序库*次要*与*主要*的变更分门别类,同时规定*次要*的变更是不允许用到这程序库的旧程序发生中断的现象。你可以从程序库的文件名分辨出它的版本(实际上,严格来讲,对ELF而言仅仅是一场天大的谎言;继续读将下去,便可明白为什么了): libfoo.so.1.2的主要版本是1,次要版本是2。次要版本的编号可能真有其事,也可能什么都没有---libc在这一点上用了*修正程度*的观念,而订出了像libc.so.5.2.18这样的程序库名称。次要版本的编号内若是放一些字母、底线、或是任何可以打印的ASCII字符,也是很合理的。 
ELF与a.out格式最主要的差别之一就是在设置共享程序库这件事上;我们先看ELF,因为它比较简单一些。 
ELF?它到底是什么东东ㄋㄟ? 
ELF(Executable and Linking Format)最初是由USL(UNIX System Laboratories)发展而成的二进位格式,目前正应用于Solaris与System V Release 4上。由於ELF所增涨的弹性远远超过Linux过去所用的a.out格式,因此GCC与C程序库的发展人士於1995年决定改用ELF为Linux标准的二进位格式。 
怎么又来了?
这一节是来自於‘/news-archives/comp.sys.sun.misc’的文件。 
ELF(“Executable Linking Format”)是於SVR4所引进的新式改良目的档格式。ELF比起COFF可是多出了不少的功能。以ELF而言,它*是*可由使用者自行延伸的。ELF视一目的档为节区(sections),如串行般的组合;而且此串行可为任意的长度(而不是一固定大小的数组)。这些节区与COFF的不一样,并不需要固定在某个地方,也不需要以某种顺序排列。如果使用者希望捕捉到新的数据,便可以加入新的节区到目的档内。ELF也有一个更强而有力的调试法式,称为DWARF(Debugging With Attribute Record Format)□目前Linux并不完全支持。DWARF DIEs(Debugging Information Entries)的连结串行会在ELF内形成 .debug的节区。DWARF DIEs的每一个 .debug节区并非一些少量且固定大小的信息记录的集合,而是一任意长度的串行,拥有复杂的属性,而且程序的数据会以有范围限制的树状数据结构写出来。DIEs所能捕捉到的大量信息是COFF的 .debug节区无法望其项背的。(像是C++的继承图。) 
ELF文件是从SVR4(Solaris 2.0 ?)ELF存取程序库(ELF access library)内存取的。此程序库可提供一简便快速的界面予ELF。使用ELF存取程序库最主要的恩惠之一便是,你不再需要去察看一个ELF档的qua了。就UNIX的文件而言,它是以Elf*的型式来存取;调用elf_open()之后,从此时开始,你只需调用elf_foobar()来处理文件的某一部份即可,并不需要把文件实际在磁盘上的image搞得一团乱。 
ELF的优缺点与升级至ELF等级所需经历的种种痛苦,已在ELF-HOWTO内论及;我并不打算在这儿涂浆糊。ELF HOWTO应该与这份文件有同样的主题才是。 
ELF共享程序库
若想让libfoo.so成为共享程序库,基本的步骤会像下面这样: 
$ gcc -fPIC -c *.c
$ gcc -shared -Wl,-soname,libfoo.so.1 -o libfoo.so.1.0 *.o
$ ln -s libfoo.so.1.0 libfoo.so.1
$ ln -s libfoo.so.1 libfoo.so
$ LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH ; export LD_LIBRARY_PATH
这会产生一个名为libfoo.so.1.0的共享程序库,以及给予ld适当的连结(libfoo.so)还有使得动态载入程序(dynamic loader)能找到它(libfoo.so.1)。为了进行测试,我们将目前的目录加到LD_LIBRARY_PATH里。 
当你津津乐道於程序库制做成功之时,别忘了把它移到如/usr/local/lib的目录底下,并且重新设定正确的连结路径。libfoo.so.1与libfoo.so.1.0的连结会由ldconfig依日期不断的更新,就大部份的系统来说,ldconfig会在开机过程中执行。libfoo.so的连结必须由手动方式更新。如果你对程序库所有组成份子(如标头档等)的升级,总是抱持著一丝不苟的态度,那么最简单的方法就是让libfoo.so -> libfoo.so.1;如此一来,ldconfig便会替你同时保留最新的连结。要是你没有这么做,你自行设定的东东就会在数日後造成千奇百怪的问题出现。到时候,可别说我没提醒你啊! 
$ su
# cp libfoo.so.1.0 /usr/local/lib
# /sbin/ldconfig
# ( cd /usr/local/lib ; ln -s libfoo.so.1 libfoo.so )
版本编号、soname与符号连结 
每一个程序库都有一个soname。当连结器发现它正在搜寻的程序库中有这样的一个名称,连结器便会将soname箝入连结中的二进位档内,而不是它正在运作的实际的文件名。在程序执行期间,动态载入程序会搜寻拥有soname这样的文件名的文件,而不是程序库的文件名。因此,一个名为libfoo.so的程序库,就可以有一个libbar.so的soname了。而且所有连结到libbar.so的程序,当程序开始执行时,会寻找的便是libbar.so了。 
这听起来好像一点意义也没有,但是这一点,对于了解数个不同版本的同一个程序库是如何在单一系统上共存的原因,却是关键之钥。Linux程序库标准的命名方式,比如说是libfoo.so.1.2,而且给这个程序库一个libfoo.so.1的soname。如果此程序库是加到标准程序库的目录底下(e.g. /usr/lib),ldconfig会建立符号连结libfoo.so.1 -> libfoo.so.1.2,使其正确的image能於执行期间找到。你也需要连结libfoo.so -> libfoo.so.1,使ld能於连结期间找到正确的soname。 
所以罗,当你修正程序库内的bugs,或是添加了新的函数进去(任何不会对现存的程序造成不利的影响的改变),你会重建此程序库,保留原本已有的soname,然后更改程序库文件名。当你对程序库的变更会使得现有的程序中断,那么你只需增加soname中的编号---此例中,称新版本为libfoo.so.2.0,而soname变成libfoo.so.2。紧接著,再将libfoo.so的连结转向新的版本;至此,世界又再度恢复了和平! 
其实你不须要以此种方式来替程序库命名,不过这的确是个好的传统。ELF赋予你在程序库命名上的弹性,会使得人气喘呼呼的搞不清楚状况;有这样的弹性在,也并不表示你就得去用它。 
ELF总结:假设经由你睿智的观察发现有个惯例说:程序库主要的升级会破坏兼容性;而次要的升级则可能不会;那么以下面的方式来连结,所有的一切就都会相安无事了。 
gcc -shared -Wl,-soname,libfoo.so.major -o libfoo.so.major.minor
a.out---旧旧的格式□
建立共享程序库的便利性是升级至ELF的主要原因之一。那也是说,a.out可能还是有用处在的。上ftp站去抓 
[url=javascript:if(confirm('ftp://tsx-11.mit.edu/pub/linux/packages/GCC/src/tools-2.17.tar.gz  \n\nThis file was not retrieved by Teleport Pro, because it is addressed on a domain or path outside the boundaries set for its Starting Address.  \n\nDo you want to open it from the server?'))window.location='ftp://tsx-11.mit.edu/pub/linux/packages/GCC/src/tools-2.17.tar.gz']ftp://tsx-11.mit.edu/pub/linux/packages/GCC/src/tools-2.17.tar.gz[/url]
;解压缩後你会发现有20页的文件可以慢慢的读哩。我很不喜欢自己党派的偏见表现得那么的淋璃尽致,可是从上下文间,应该也可以很清楚的嗅出我从来不拿石头砸自己的脚的脾气吧!:-) 
ZMAGIC vs QMAGIC 
QMAGIC是一种类似旧格式的a.out(亦称为ZMAGIC)的可执行档格式,这种格式会使得第一个分页无法map。当0-4096的范围内没有mapping存在时,则可允许NULL dereference trapping更加的容易。所产生的边界效应是你的执行档会比较小(大约少1K左右)。 
只有即将作废的连结器有支持ZMAGIC,一半已埋入棺材的连结器有支持这两种格式;而目前的版本仅支持QMAGIC而已。事实上,这并没有多大的影响,那是因为目前的核心两种格式都能执行。 
*file*命令应该可以确认程序是不是QMAGIC的格式的。 
文件配置
一a.out(DLL)的共享程序库包含两个真实的文件与一个连结符号。就*foo*这个用于整份文件做为范例的程序库而言,这些文件会是libfoo.sa与libfoo.so.1.2;连结符号会是libfoo.so.1,而且会指向libfoo.so.1.2。这些是做什么用的? 
在编译时,ld会寻找libfoo.sa。这是程序库的*stub*文件。而且含有所有执行期间连结所需的exported的数据与指向函数的指标。 
执行期间,动态载入程序会寻找libfoo.so.1。这仅仅是一个符号连结,而不是真实的文件。故程序库可更新成较新的且已修正错误的版本,而不会损毁任何此时正在使用此程序库的应用程序。在新版---比如说libfoo.so.1.3---已完整呈现时,ldconfig会以一极微小的操作,将连结指向新的版本,使得任何原本使用旧版的程序不会感到丝毫的不悦。 
DLL程序库(我知道这是无谓的反覆---所以对我提出诉讼吧!)通常会比它们的静态副本要来得大多。它们是以*洞(holes)*的形式来保留空间以便日後的扩充。这种*洞*可以不占用任何的磁盘空间。一个简单的cp调用,或是使用makehole程序,就可以达到这样效果。因为它们的地址是固定在同一位置上,所以在建立程序库後,你可以把它们拿掉。不过,千万不要试著拿掉ELF的程序库。 
``libc-lite''?
libc-lite是轻量级的libc版本。可用来存放在磁盘片上,也可以替大部份低微的UNIX任务收尾。它没有包含curses, dbm, termcap等等的程序代码。如果你的/lib/libc.so.4是连结到一个lite的libc,那么建议你以完整的版本取代它。 
连结:常见的问题
把你连结时所遭遇的问题寄给我!我可能什么事也不会做,但是只要累积了足够的数量,我会把它们写起来*。 
你想共享,偏偏程序却连结成静态的! 
检查你提供给ld的连结是否正确,使ld能找到每一个对应的共享程序库,就ELF而言,这是指一个符号连结libfoo.so,连结至image;就a.out而言,就是libfoo.sa档了。很多人将ELF binutils 2.5升级至2.6之后,就产生了这个问题---早期的版本搜寻共享程序库时较有智慧,所以并没有将所有的连结建立起来。後来,为了与其它的架构兼容,这项充满智慧的行为被人给删除掉了,另外,这样的*智慧*判断错误的机率相当高,所造成的麻烦比它所解决的问题还多,所以留著也是害人精;不如归去兮! 
DLL的工具程序‘mkimage’找不到libgcc? 
自libc.so.4.5.x之后,libgcc已不再是共享的格式。因此,你必须在*-lgcc*出现之处以`gcc -print-libgcc-file-name`替代(完整的倒单引号(back-quotes))。另外,删除所有/usr/lib/libgcc*的文件。这点很重要哩。 
__NEEDS_SHRLIB_libc_4 multiply defined messages 
是同样的问题所造成的另一种结果。 
``Assertion failure'' message when rebuilding a DLL ? 
这一条神秘的讯息最有可能的原因是,在原始的jump.vars文件内,由於保留的空间太少,以致於造成其中一个jump table slots溢满。你可以执行工具程序□由2.17.tar.gz套件所提供的‘getsize’命令,定出所有嫌疑犯的踪迹。可能唯一的解决方法是,解除此程序库主要的版本编号,强迫它回到不兼容的年代。 
ld: output file needs shared library libc.so.4 
通常这是发生在当你连结的程序库不是libc(如X程序库),而且在命令列用了-g的参数,却没有一并使用-static,所发出的错误讯息。 
共享程序库的.sa stubs通常有一个未定义的符号_NEEDS_SHRLIB_libc_4;这一点可藉由libc.sa stub来解决,然而,以-g来编译时,会使得连结以libg.a或libc.a来结束;因此这个符号一直就没有解决,也就会导致上面的错误讯息了。 
总之,以-g的旗号编译时别忘了加上-static,不然就别用-g来连结。通常,以-g编译各个独立的文件时,所获得的调试信息已经足够,连结时就可以不需要它了。 
nm 程式库名称应该会列出此程式库名称所参考到的所有符号。这个指令可以应用在静态与共享程式库上。假设你想知道tcgetattr()是在哪儿定义的:你可以如此做, 
$ nm libncurses.so.1 |grep tcget U tcgetattr 
*U*指出*未定义*---也就是说ncurses程式库有用到tegetattr(),但是并没有定义它。你也可以这样做, 
$ nm libc.so.5 | grep tcget 00010fe8 T __tcgetattr 00010fe8 W tcgetattr 00068718 T tcgetpgrp 
*W*说明了*弱态(weak)*,意指符号虽已定义,但可由不同程式库中的另一定义所替代。最简单的*正常*定义(像是tcgetpgrp)是由*T*所标示: 
注意:以上是一般的ld搜索方式,编译程序时还会碰到一种情况:在ld搜索到.so文件但该文件很小,里面是ld script语句,又将ld 指向了别处,这种情况如果ld script 中有绝对路径就容易出问题,所以在库文件很小时(百十k左右),就要打开看看了,如果引起问题可以将该含有ld script的so文件删除,建立一个指向正确库文件的连接来替代它。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值