`
qtzsq84c
  • 浏览: 20051 次
最近访客 更多访客>>
社区版块
存档分类
最新评论

【zz】静态库与动态库搜索路径

 
阅读更多

【zz】静态库与动态库搜索路径
2011年03月10日
  1. 连接和运行时库文件搜索路径到设置
  库文件在连接(静态库和共享库)和运行(仅限于使用共享库的程序)时被使用,其搜索路径是在系统中进行设置的。一般 Linux 系统把 /lib 和 /usr/lib 两个目录作为默认的库搜索路径,所以使用这两个目录中的库时不需要进行设置搜索路径即可直接使用。对于处于默认库搜索路径之外的库,需要将库的位置添加到库的搜索路径之中。设置库文件的搜索路径有下列两种方式,可任选其一使用:
  (1). 在 /etc/ld.so.conf 文件中添加库的搜索路径。(或者在/etc/ld.so.conf.d 下新建一个.conf文件,将搜索路径一行一个加入-junziyang)
  将自己可能存放库文件的路径都加入到/etc/ld.so.conf中是明智的选择添加方法也极其简单,将库文件的绝对路径直接写进去就OK了,一行一个。例如: 
  /usr/X11R6/lib
  /usr/local/lib
  /opt/lib 
  需要注意的是:这种搜索路径的设置方式对于程序连接时的库(包括共享库和静态库)的定位已经足够了,但是对于使用了共享库的程序的执行还是不够的。这是因为为了加快程序执行时对共享库的定位速度,避免使用搜索路径查找共享库的低效率,所以是直接读取库列表文件 /etc/ld.so.cache 从中进行搜索的。/etc/ld.so.cache 是一个非文本的数据文件,不能直接编辑,它是根据 /etc/ld.so.conf 中设置的搜索路径由 /sbin/ldconfig 命令将这些搜索路径下的共享库文件集中在一起而生成的(ldconfig 命令要以 root 权限执行)。
  因此,为了保证程序执行时对库的定位,在 /etc/ld.so.conf 中进行了库搜索路径的设置之后,还必须要运行 /sbin/ldconfig 命令更新 /etc/ld.so.cache 文件之后才可以。ldconfig ,简单的说,它的作用就是将/etc/ld.so.conf列出的路径下的库文件缓存到/etc/ld.so.cache 以供使用。因此当安装完一些库文件,(例如刚安装好glib),或者修改ld.so.conf增加新的库路径后,需要运行一下 /sbin/ldconfig使所有的库文件都被缓存到ld.so.cache中,如果没做,即使库文件明明就在/usr/lib下的,也是不会被使用的,结果编译过程中抱错,缺少xxx库,去查看发现明明就在那放着,搞的想大骂computer蠢猪一个。 
  在程序连接时,对于库文件(静态库和共享库)的搜索路径,除了上面的设置方式之外,还可以通过 -L 参数显式指定。因为用 -L 设置的路径将被优先搜索,所以在连接的时候通常都会以这种方式直接指定要连接的库的路径。 
  这种设置方式需要 root 权限,以改变 /etc/ld.so.conf 文件并执行 /sbin/ldconfig 命令。而且,当系统重新启动后,所有的基于 GTK2 的程序在运行时都将使用新安装的 GTK+ 库。不幸的是,由于 GTK+ 版本的改变,这有时会给应用程序带来兼容性的问题,造成某些程序运行不正常。为了避免出现上面的这些情况,在 GTK+ 及其依赖库的安装过程中对于库的搜索路径的设置将采用另一种方式进行。这种设置方式不需要 root 权限,设置也简单。
  (2). 在环境变量 LD_LIBRARY_PATH 中指明库的搜索路径。
  设置方式:
  export LD_LIBRARY_PATH=/opt/gtk/lib:$LD_LIBRARY_PATH 
  可以用下面的命令查看 LD_LIBRAY_PATH 的设置内容:
  echo $LD_LIBRARY_PATH
  至此,库的两种设置就完成了。
  2.交叉编译时候如何配置连接库的搜索路径
  交叉编译的时候不能使用本地(i686机器,即PC机器,研发机器)机器上的库,但是在做编译链接的时候默认的是使用本地库,即/usr/lib, /lib两个目录。因此,在交叉编译的时候,要采取一些方法使得在编译链接的时候找到需要的库。
  首先,要知道:编译的时候只需要头文档,真正实际的库文档在链接的时候用到。 (这是我的理解,假如有不对的地方,敬请网上各位大侠指教) 然后,讲讲如何在交叉编译链接的时候找到需要的库。
  (1)交叉编译时候直接使用-L和-I参数指定搜索非标准的库文档和头文档的路径。例如:
  arm-linux-gcc test.c -L/usr/local/arm/2.95.3/arm-linux/lib -I/usr/local/arm/2.95.3/arm-linux/include 
  (2)使用ld.so.conf文档,将用到的库所在文档目录添加到此文档中,然后使用ldconfig命令刷新缓存。
  (3)使用如下命令:
  export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/arm/2. 95.3/arm-linux-lib
  参见《ld.so.conf 文档和PKG_CONFIG_PATH变量》这篇文章。
  通过环境变量LD_LIBRARY_PATH指定动态库搜索路径(!)。
  通过设定环境变量LD_LIBRARY_PATH也可以指定动态库搜索路径。当通过该环境变量指定多个动态库搜索路径时,路径之间用冒号":"分隔。
  不过LD_LIBRARY_PATH的设定作用是全局的,过多的使用可能会影响到其他应用程序的运行,所以多用在调试。(LD_LIBRARY_PATH 的缺陷和使用准则,可以参考《Why LD_LIBRARY_PATH is bad》 )。通常情况下推荐还是使用gcc的-R或-rpath选项来在编译时就指定库的查找路径,并且该库的路径信息保存在可执行文件中,运行时它会直接到该路径查找库,避免了使用LD_LIBRARY_PATH环境变量查找。
  (4)交叉编译时使用软件的configure参数。例如我编译minigui-1.3.3,使用如下配置:
  #!/bin/bash
  rm -f config.cache config.status
  ./configure --build=i686-linux --host=arm-linux --target=arm-linux \
  CFLAGS=-I/usr/local/arm/2.95.3/arm-linux/include \
  LDFLAGS=-L/usr/local/arm/2.95.3/arm-linux/lib \
  --prefix=/usr/local/arm/2.95.3/arm-linux \
  --enable-lite \
  --disable-galqvfb \
  --disable-qvfbial \
  --disable-vbfsupport \
  --disable-ttfsupport \
  --disable-type1support \
  --disable-imegb2312py \
  --enable-extfullgif \
  --enable-extskin \
  --disable-videoqvfb \
  --disable-videoecoslcd
  这里我配置了CFLAGS和LDFLAGS参数,这样一来,我就不用去修改每个Makefile里-L和-I参数了,也不用再去配置 LD_LIBRARY_PATH或改写ld.so.conf文档了。
  Linux下动态库使用小结
  1. 静态库和动态库的基本概念
  静态库,是在可执行程序连接时就已经加入到执行码中,在物理上成为执行程序的一部分;使用静态库编译的程序运行时无需该库文件支持,哪里都可以用,但是生成的可执行文件较大。动态库,是在可执行程序启动时加载到执行程序中,可以被多个可执行程序共享使用。使用动态库编译生成的程序相对较小,但运行时需要库文件支持,如果机器里没有这些库文件就不能运行。
  2. 如何使用动态库
  如何程序在连接时使用了共享库,就必须在运行的时候能够找到共享库的位置。linux的可执行程序在执行的时候默认是先搜索/lib和/usr/lib这两个目录,然后按照/etc/ld.so.conf里面的配置搜索绝对路径。同时,Linux也提供了环境变量LD_LIBRARY_PATH供用户选择使用,用户可以通过设定它来查找除默认路径之外的其他路径,如查找/work/lib路径,你可以在/etc/rc.d/rc.local或其他系统启动后即可执行到的脚本添加如下语句:LD_LIBRARY_PATH =/work/lib:$(LD_LIBRARY_PATH)。并且LD_LIBRARY_PATH路径优先于系统默认路径之前查找(详细参考《使用 LD_LIBRARY_PATH》)。
  不过LD_LIBRARY_PATH的设定作用是全局的,过多的使用可能会影响到其他应用程序的运行,所以多用在调试。(LD_LIBRARY_PATH 的缺陷和使用准则,可以参考《Why LD_LIBRARY_PATH is bad》)。通常情况下推荐还是使用gcc的-R或-rpath选项来在编译时就指定库的查找路径,并且该库的路径信息保存在可执行文件中,运行时它会直接到该路径查找库,避免了使用LD_LIBRARY_PATH环境变量查找。
  3.库的链接时路径和运行时路径
  现代连接器在处理动态库时将链接时路径(Link-time path)和运行时路径(Run-time path)分开,用户可以通过-L指定连接时库的路径,通过-R(或-rpath)指定程序运行时库的路径,大大提高了库应用的灵活性。比如我们做嵌入式移植时#arm-linux-gcc $(CFLAGS)  o target  L/work/lib/zlib/ -llibz-1.2.3 (work/lib/zlib下是交叉编译好的zlib库),将target编译好后我们只要把zlib库拷贝到开发板的系统默认路径下即可。或者通过- rpath(或-R )、LD_LIBRARY_PATH指定查找路径
  Linux是一个多用户的操作系统。每个用户登录系统后,都会有一个专用的运行环境。通常每个用户默认的环境都是相同的,这个默认环境实际上就是一组环境变量的定义。用户可以对自己的运行环境进行定制,其方法就是修改相应的系统环境变量。
  常见的环境变量
  对于PATH和HOME等环境变量大家都不陌生。除此之外,还有下面一些常见环境变量。
  ◆ HISTSIZE是指保存历史命令记录的条数。 
  ◆ LOGNAME是指当前用户的登录名。 
  ◆ HOSTNAME是指主机的名称,许多应用程序如果要用到主机名的话,通常是从这个环境变量中来取得的。 
  ◆ SHELL是指当前用户用的是哪种Shell。 
  ◆ LANG/LANGUGE是和语言相关的环境变量,使用多种语言的用户可以修改此环境变量。 
  ◆ MAIL是指当前用户的邮件存放目录。 
  ◆ PS1是基本提示符,对于root用户是#,对于普通用户是$。PS2是附属提示符,默认是">"。可以通过修改此环境变量来修改当前的命令符,比如下列命令会将提示符修改成字符串"Hello,My NewPrompt "。 
  # PS1=" Hello,My NewPrompt "
  Hello,My NewPrompt
  除了这些常见的环境变量,许多应用程序在安装时也会增加一些环境变量,比如使用Java就要设置JAVA_HOME和CLASSPATH等,而安装五笔输入法会增加环境变量"XMODIFIERS=@im=fcitx"等。
  定制环境变量
  环境变量是和Shell紧密相关的,用户登录系统后就启动了一个Shell。对于Linux来说一般是bash,但也可以重新设定或切换到其它的Shell。环境变量是通过Shell命令来设置的,设置好的环境变量又可以被所有当前用户所运行的程序所使用。对于bash这个Shell程序来说,可以通过变量名来访问相应的环境变量,通过export来设置环境变量。下面通过几个实例来说明。
  1. 显示环境变量HOME 
  $ echo $HOME
  /home/terry
  2. 设置一个新的环境变量WELCOME 
  $ export WELCOME="Hello!"
  $ echo $WELCOME
  Hello!
  3. 使用env命令显示所有的环境变量 
  $ env
  HOSTNAME=terry.mykms.org
  PVM_RSH=/usr/bin/rsh
  SHELL=/bin/bash
  TERM=xterm
  HISTSIZE=1000
  ...
  4. 使用set命令显示所有本地定义的Shell变量 
  $ set
  BASH=/bin/bash
  BASH_VERSINFO=([0]="2"[1]="05b"[2]="0"[3]="1"[4]=" release"[5]="i386-redhat-linux-gnu"
  BASH_VERSION='2.05b.0(1)-release'
  COLORS=/etc/DIR_COLORS.xterm
  COLUMNS=80
  DIRSTACK=()
  DISPLAY=:0.0
  ...
  5. 使用unset命令来清除环境变量
  set可以设置某个环境变量的值。清除环境变量的值用unset命令。如果未指定值,则该变量值将被设为NULL。示例如下: 
  $ export TEST="Test..." #增加一个环境变量TEST
  $ env|grep TEST #此命令有输入,证明环境变量TEST已经存在了
  TEST=Test...
  $ unset $TEST #删除环境变量TEST
  $ env|grep TEST #此命令没有输出,证明环境变量TEST已经存在了
  6. 使用readonly命令设置只读变量
  如果使用了readonly命令的话,变量就不可以被修改或清除了。示例如下: 
  $ export TEST="Test..." #增加一个环境变量TEST
  $ readonly TEST #将环境变量TEST设为只读
  $ unset TEST #会发现此变量不能被删除
  -bash: unset: TEST: cannot unset: readonly variable
  $ TEST="New" #会发现此也变量不能被修改
  -bash: TEST: readonly variable
  7. 用C程序来访问和设置环境变量
  对于C程序的用户来说,可以使用下列三个函数来设置或访问一个环境变量。
  ◆ getenv()访问一个环境变量。输入参数是需要访问的变量名字,返回值是一个字符串。如果所访问的环境变量不存在,则会返回NULL。
  ◆ setenv()在程序里面设置某个环境变量的函数。
  ◆ unsetenv()清除某个特定的环境变量的函数。
  另外,还有一个指针变量environ,它指向的是包含所有的环境变量的一个列表。下面的程序可以打印出当前运行环境里面的所有环境变量: 
  #include 
  extern char**environ;
  int main ()
  {
  char**var;
  for (var =environ;*var !=NULL;++var)
  printf ("%s \\n ",*var);
  return 0;
  }
  还可以通过修改一些相关的环境定义文件来修改环境变量,比如对于Red Hat等Linux发行版本,与环境相关的文件有/etc/profile和~/.bashrc等。修改完毕后重新登录一次就生效了
  stephen1w 发表于 >2006-5-31 13:46:38 [全文] [评论] [引用] [推荐] [档案] [推给好友] [收藏到网摘] 
  2006-5-31
  [C & C++ Programming]链接器介绍
  适当的了解一下底层的东西有助于更加有效的开发程序。
  链接器是什么?它与编译器、汇编器以及装载器之间又是什么关系?通过这篇我翻译的文章可以大概的了解一下链接器的功能及它所完成的任务。
  链接与编译和汇编类似,基本上是一个two pass process. 链接器的输入可能会包括一系列的输入目标文件,库,也可能包括command files,它运行的结果是输出一个目标文件,也可能会包括一些辅助信息,例如一个装载图(load map)或者一个保护调试符号的文件。
  每个输入文件包含一系列的需要放到输出文件的段(segments),连续的代码或数据块。每个输入文件也包含至少一个符合表(symbol table)。有些符号是可导出(exported)的,它们在一个文件中定义,但是其他文件也使用它们,它们通常是一些可以从其他地方调用的例程的名字。同时也导入一些本文件中使用了却没有给出定义的符号,它们通常也是一些本文件调用的例程的名字。
  当运行链接器时,它首先扫描所有的输入文件以计算出所有段的大小并收集所有的符号定义和引用信息。它创建一个列出输入文件所定义的全部段的段表(segment table)以及包含所有符号导入和导出信息的符号表。
  使用the first pass 获得的数据,链接器为符号赋予数字位置,计算所有段的大小和它们在输出地址空间中的位置。
  The second pass 利用the first pass 获得的数据来控制实际的链接过程。它读取和重定位(relocates)目标代码,将符号引用替换为数字地址,并且调整代码和数据中的内存地址以反映经过重定位后的段地址,同时将重定位的代码写道输出文件中。(relocated:经过重定位后的、重定位的。完成时)最后生成输出文件,一般包括头信息,重定位的段和符号表等信息。如果程序使用了动态链接,那么符号表包含了运行时链接器(runtime linker)为了解析动态符号所需要的信息。在许多情况下,输出文件也会包含少量的代码和数据,例如"glue code",这是用来调用overlays中的程序或者动态链接库,或者一个指向在程序起动时需要调用的初始化例程的指针数组。
  不管程序是否使用了动态链接,输出文件都可能会包含一个用于重链接或者调试的符号表,虽然程序本身不使用这些信息,但是其他程序可能会用到它们。
  一些目标格式是可重链接的(relinkable),也就是说,一次链接的输出可以作为另一次链接的输入。这要求输出文件包含一个类似与输入文件所含有的符号表以及所有出现在一个输入文件中的其他辅助信息。
  重定位和代码修改(Relocation and code modification)
  链接器或者装载器的核心动作是重定位和代码修改。编译器或者汇编器生成的目标文件中的代码通常从零地址开始。作为链接过程的一部分,链接器修改目标代码以反映实际分配的地址。例如,考虑下面的x86代码片断,它使用eax寄存器将变量a的内容复制到变量b中。
  mov a, %eax
  mov %eax, b
  如果a在本文件中定义,它位于0x1234,b从某个别的地方导入,那么生成的代码将是:
  A1 34 12 00 00 mov a,%eax
  A3 00 00 00 00 mov %eax,b
  每条指令包含一个单字节(one-byte)的操作码,紧跟着一个四字节的地址。第一个指令包含一个到1234的引用(byte reversed,因为x86使用小端格式),第二条指令包含一个到0的引用因为b是未知的。
  现在假设链接器链接这段代码,而且a所在的节(section)被重定位到0x10000,b所在的节被重定位到0x9A12。链接器将这段代码修改为:
  A1 34 12 01 00 mov a,%eax
  A3 12 9A 00 00 mov %eax,b
  也就是说,链接器把第一条指令中的地址增加了10000,因此现在它引用a的重定位的地址11234,同时也填补了b的地址。这些调整不但影响了指令,而且任何指向目标文件中的数据的指针也必须相应的调整。
  现代的计算机,包括所有的RISCs,需要相当复杂的代码修改。由于没有指令能够包含足够多的位来容纳一个直接地址,因此编译器和链接器不得不使用复杂的寻址技巧来处理那些位于任意地址的数据。某些情况下,有可能将一个地址嵌入在两条或三条指令中。此时,链接器不得不仔细的修改每条指令,在每条这里中都插入一些构成地址的比特。有时候,一个例程或者一组例程所使用的地址都被放在一个数组中,作为一个"address pool",初始化代码将某个寄存器指向这个数组,代码根据需要使用那个寄存器作为基址寄存器从address pool装载例程指针。链接器可能不得不根据一个程序所使用的所有地址来创建一个数组,然后修改指令,这样它们可以引用到正确的address pool entry。我们将在第七章讨论这个问题。
  Source file m.c
  extern void a(char *);
  int main(int ac, char **av)
  {
  static char string[] = "Hello, world!\\n";
  a(string);
  }
  Source file a.c
  #include 
  #include 
  void a(char *s)
  {
  write(1, s, strlen(s));
  }
  Main位于m.c中,它调用一个名字为a的例程,它位于a.c中,同时它调用了库例程strlen和printf。
  在我的pentium电脑中使用GCC编译m.c,得到一个165字节的目标文件,它的格式为经典的a.out目标格式。这个目标文件包含一个固定长度的头;16字节的"text"段,其中包括只读程序代码;16字节的"data"段,包含了那个字符串。紧跟着是两个relocation entries,一个标出了pushl指令,它将字符串的地址推入堆栈以为调用a作准备,另一个标出了call指令,它将控制传递到a。符号表导出了_main的定义,导入了_a,也包含一些用于调试的符号。(每个全局符号都带有一个前缀_,原因见第五章)注意pushl指令引用了位置0x10,字符串的临时地址,因为它们位于通过目标文件中,然而call引用了位置0因为_a的地址是未知的。
  Figure 1-4: Object code for m.o
  Sections:
  Idx Name Size VMA LMA File off Algn 0 .text 00000010 00000000 00000000 00000020 2**3
  1 .data 00000010 00000010 00000010 00000030 2**3 Disassembly of section .text: 00000000 : 0: 55 pushl %ebp
  1: 89 e5 movl %esp,%ebp
  3: 68 10 00 00 00 pushl $0x10
  4: 32 .data
  8: e8 f3 ff ff ff call 0
  9: DISP32 _a
  d: c9 leave
  e: c3 ret ... 子程序a.c编译的结果为160字节的目标文件,头部,一个28字节的text段,没有data。两个relocation entries标识了对strlen和write的调用,符号表导出_a,导入_strlen和_write。
  Figure 1-5: Object code for m.o
  Sections:
  Idx Name Size VMA LMA File off Algn
  0 .text 0000001c 00000000 00000000 00000020 2**2
  CONTENTS, ALLOC, LOAD, RELOC, CODE
  1 .data 00000000 0000001c 0000001c 0000003c 2**2
  CONTENTS, ALLOC, LOAD, DATA
  Disassembly of section .text:
  00000000 :
  0: 55 pushl %ebp
  1: 89 e5 movl %esp,%ebp
  3: 53 pushl %ebx
  4: 8b 5d 08 movl 0x8(%ebp),%ebx
  7: 53 pushl %ebx
  8: e8 f3 ff ff ff call 0
  9: DISP32 _strlen
  d: 50 pushl %eax
  e: 53 pushl %ebx
  f: 6a 01 pushl $0x1
  11: e8 ea ff ff ff call 0
  12: DISP32 _write
  16: 8d 65 fc leal -4(%ebp),%esp
  19: 5b popl %ebx
  1a: c9 leave
  1b: c3 ret
  为了产生一个可执行程序,链接器将这两个目标文件和C程序的标准起动初始化例程,以及必要的C库例程组合起来,产生一个可执行文件,如Figure 6所示。
  Figure 1-6: Selected parts of executable
  Sections:
  Idx Name Size VMA LMA File off Algn
  0 .text 00000fe0 00001020 00001020 00000020 2**3
  1 .data 00001000 00002000 00002000 00001000 2**3
  2 .bss 00000000 00003000 00003000 00000000 2**3
  Disassembly of section .text:
  00001020 :
  ...
  1092: e8 0d 00 00 00 call 10a4 
  ...
  000010a4 :
  10a4: 55 pushl %ebp
  10a5: 89 e5 movl %esp,%ebp
  10a7: 68 24 20 00 00 pushl $0x2024
  10ac: e8 03 00 00 00 call 10b4 
  10b1: c9 leave
  10b2: c3 ret
  ...
  000010b4 :
  10b4: 55 pushl %ebp
  10b5: 89 e5 movl %esp,%ebp
  10b7: 53 pushl %ebx
  10b8: 8b 5d 08 movl 0x8(%ebp),%ebx
  10bb: 53 pushl %ebx
  10bc: e8 37 00 00 00 call 10f8 
  10c1: 50 pushl %eax
  10c2: 53 pushl %ebx
  10c3: 6a 01 pushl $0x1
  10c5: e8 a2 00 00 00 call 116c 
  10ca: 8d 65 fc leal -4(%ebp),%esp
  10cd: 5b popl %ebx
  10ce: c9 leave
  10cf: c3 ret
  ...
  000010f8 :
  ...
  0000116c :
  ...
  链接器将每个输入文件中的对应段组合起来,因此形成了一个组合的text段,一个组合的data段和一个bss段(zero-initialized data,两个输入文件都没有使用它)。为了和x86页大小匹配,每个段被填充在4K边界上,因此text段的大小是4K(去掉虽然出现在文件中,但逻辑上不是段的一部分的20字节的a.out头部),data和bss段也都是4K。组合的text段包含库起动代码start-c,然后m.o中的text段被重定位到10a4,a.o被重定位到10b4,从C库链接的例程被重定位到text段中更高的地址处。这里没有显示的data段包含的组合的data段,它们的顺序和text段相同。因为_main中的代码已经被重定位到地址10a4,这个地址被填充到位于start-c中call指令。在main例程中,对字符串的引用被重定位到2024,它是字符串在data段中的最终地址,call被填充为10b4,_a的最终地址。在_a中对_strlen和_write的调用被填充为这两个例程的最终地址。
  可执行程序也包含一些来自C库的其他例程,在这里没有显示它们,它们被起动代码或_write(error例程,在后一种情况下)直接或间接的调用。可执行程序不包含可重定位数据,因为这个文件格式不是relinkable,并且操作系统将它装载到一个固定的地址处。它包含一个用于调试的符号表,虽然可执行程序不使用这些符号,为了节省空间可以将符号表去掉。
  在这个例子中,从库链接的代码比程序本身的代码要大的多。这是非常平常的,特别是当程序使用大的图形库或者windowing库时,正是它们导致了共享库的出现。链接的程序大小为8K,但是同样的程序如果使用共享库只有264字节。当然,这是一个小例子,但是实际的程序(如果使用共享库)经常也会节省大量的空间。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics