momo zone

调核人的blog

了解libtool

之前对这个东西一知半解,最近在porting几个库的时候感觉还是了解一下比较好。

libtool与动态链接库有很大关系,初衷是为了解决库依赖的问题,打个比方a.so依赖b.so,那么编译的时候要在-l参数后面把这些so都加上去。libtool的概念是不使用-l,而是指定描述依赖关系的文件进而自动生成编译参数。有点像apt,zypper,yum等工具的原理。

libtool是一个脚本文件,如果要使用,既可以直接安装,也可以使用软件包中的configure生成。然而在实际使用中由configure生成的libtool脚本优先级高于系统安装的,或者根本就不用系统安装的,所以不单独安装libtool也没有问题。

为了说明简单,这里抛弃configure工具,直接安装并使用libtool进行示范。

先随便写一个foo.c里面就一个函数,然后使用libtool编译成动态库而不是gcc

1.先编译出目标文件:

libtool --mode=compile --tag=CC mipsel-linux-gcc -c foo.c

输出

libtool: compile:  mipsel-linux-gcc -c foo.c  -fPIC -DPIC -o .libs/foo.o
libtool: compile:  mipsel-linux-gcc -c foo.c -o foo.o >/dev/null 2>&1

第一行生成一个位置无关的obj,第二行生成一个位置相关的obj,两个分别为接下来的动态库和静态库准备。

可以看出libtool实际上封装了gcc,除了gcc的work外他还做了一些其他工作,另外输出的文件和目录都是有规范的。

–tag=CC在交叉编译的时候必须指定。

最重要的是它生成了.lo文件,该文件描述了前述两个obj文件的路径。

# foo.lo - a libtool object file
# Generated by ltmain.sh (GNU libtool) 2.2.6
#
# Please DO NOT delete this file!
# It is necessary for linking the library.

# Name of the PIC object.
pic_object='.libs/foo.o'

# Name of the non-PIC object
non_pic_object='foo.o'


2.生成库:

./libtool --mode=link --tag=CC mipsel-linux-gcc -o foo.la foo.lo -rpath /tmp

输出

libtool: link: mipsel-linux-gcc -shared  .libs/foo.o      -Wl,-soname -Wl,libfoo.so.0 -o .libs/libfoo.so.0.0.0
libtool: link: (cd ".libs" && rm -f "libfoo.so.0" && ln -s "libfoo.so.0.0.0" "libfoo.so.0")
libtool: link: (cd ".libs" && rm -f "libfoo.so" && ln -s "libfoo.so.0.0.0" "libfoo.so")
libtool: link: mipsel-linux-ar cru .libs/libfoo.a  foo.o
libtool: link: mipsel-linux-ranlib .libs/libfoo.a
libtool: link: ( cd ".libs" && rm -f "libfoo.la" && ln -s "../libfoo.la" "libfoo.la" )

-rpath参数必须指定否则无法生成动态库,该参数在gcc中意思是runtime时库的搜索路径,这里也同时告诉libtool该库要安装到哪个目录。obj不再是输入文件而是改为了.lo。最后生成了动态库,静态库还有.la文件。该文件描述了动态库的依赖,所以libtool折腾了半天为了就是这个:

# libfoo.la - a libtool library file
# Generated by ltmain.sh (GNU libtool) 2.2.6
#
# Please DO NOT delete this file!
# It is necessary for linking the library.

# The name that we can dlopen(3).
dlname='libfoo.so.0'

# Names of this library.
library_names='libfoo.so.0.0.0 libfoo.so.0 libfoo.so'

# The name of the static archive.
old_library='libfoo.a'

# Linker flags that can not go in dependency_libs.
inherited_linker_flags=''

# Libraries that this one depends upon.
dependency_libs=''

# Names of additional weak libraries provided by this library
weak_library_names=''

# Version information for libdaemon.
current=0
age=0
revision=0

# Is this an already installed library?
installed=no

# Should we warn about portability when linking against -modules?
shouldnotlink=no

# Files to dlopen/dlpreopen
dlopen=''
dlpreopen=''

# Directory that this library needs to be installed in:
libdir='/tmp'

libdir指出了库的安装位置;library_names记录了动态库的名字;old_library记录了静态库的名字。

如果在链接过程中加入-l参数,也就是有依赖库,那么生成的.la中的dependency_libs中就会加入这个库。可以想象对于一个比较复杂的项目,透过.la可以形成一个完整的依赖链。libtool再处理-l参数时,比如-lz,并不是找libz.so,而是libz.la,一切都明了了。

这里有个需要注意的地方,因为libtool一般是makefile调用,所以当使用别人编译出来的so,la文件时,libdir指定的目录往往和自己的环境不一样,导致makefile报错,如果不知道这个错误实际上是由libtool引发的话你会很郁闷,因为即使so就放在那里但其实还是按照la指定的去找。解决方法是修改la文件,更dirty的方法是删除la文件,让gcc按照链接器搜索的顺序去找so(见下文的具体解释)

3.安装

./libtool --mode=install install -c libfoo.la /tmp/

这里要再一次指定相同的tmp目录,尽管链接的时候指明了。

输出:

libtool: install: install -c .libs/libfoo.so.0.0.0 /tmp/libfoo.so.0.0.0
libtool: install: (cd /tmp && { ln -s -f libfoo.so.0.0.0 libfoo.so.0 || { rm -f libfoo.so.0 && ln -s libfoo.so.0.0.0 libfoo.so.0; }; })
libtool: install: (cd /tmp && { ln -s -f libfoo.so.0.0.0 libfoo.so || { rm -f libfoo.so && ln -s libfoo.so.0.0.0 libfoo.so; }; })
libtool: install: install -c .libs/libfoo.lai /tmp/libfoo.la
libtool: install: install -c .libs/libfoo.a /tmp/libfoo.a
libtool: install: chmod 644 /tmp/libfoo.a
libtool: install: mipsel-linux-ranlib /tmp/libfoo.a
libtool: finish: PATH="/run/media/root/RAID_STO-4_645G/DEV/PORTING/baffalo-HP54-G/TOOLCHAIN/OpenWrt-SDK-Linux-x86_64-1/bin/:/sbin:/usr/local/sbin:/root/bin:/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games:/sbin" ldconfig -n /tmp
ldconfig: /tmp/libfoo.so.0.0.0 is for unknown machine 8.

ldconfig: /tmp/libfoo.so is for unknown machine 8.

ldconfig: /tmp/libfoo.so.0 is for unknown machine 8.

----------------------------------------------------------------------
Libraries have been installed in:
   /tmp

If you ever happen to want to link against installed libraries
in a given directory, LIBDIR, you must either use libtool, and
specify the full pathname of the library, or use the `-LLIBDIR'
flag during linking and do at least one of the following:
   - add LIBDIR to the `LD_LIBRARY_PATH' environment variable
     during execution
   - add LIBDIR to the `LD_RUN_PATH' environment variable
     during linking
   - use the `-Wl,-rpath -Wl,LIBDIR' linker flag
   - have your system administrator add LIBDIR to `/etc/ld.so.conf'

See any operating system documentation about shared libraries for
more information, such as the ld(1) and ld.so(8) manual pages.
----------------------------------------------------------------------

4.卸载

libtool --mode=uninstall rm /tmp/libfoo.la

输出

libtool: uninstall: rm /tmp/libfoo.la /tmp/libfoo.so.0.5.0 /tmp/libfoo.so.0 /tmp/libfoo.so /tmp/libfoo.a

5.使用库
假设有个main.c作为程序入口,需要libfoo.so,则libtool的用法是:

libtool --mode=compile gcc -c main.c
libtool --mode=link gcc -o main main.lo /tmp/libfoo.la

可以想象如果是一个非常复杂的项目,借助libtool可以将编译参数变得像编译helloworld一样简洁

-L: “链接”的时候,去找的目录,也就是所有的 -lFOO 选项里的库,都会先从 -L 指定的目录去找,然后是默认的地方。
-rpath: “运行”的时候,去找的目录。运行的时候,要找 .so 文件,会从这个选项里指定的地方去找。对于交叉编译,只有配合 –sysroot 选项才能起作用。
-rpath_link (或者 -rpath-link):这个也是用于“链接”的时候的,例如你显示指定的需要 FOO.so,但是 FOO.so 本身是需要 BAR.so 的,后者你并没有指定,而是 FOO.so 引用到它,这个时候,会先从 -rpath-link 给的路径里找。

也就是说,-rpath指定的路径会被记录在生成的可执行程序中,用于运行时。
-rpath-link 则只用于链接时。

这里再引出一个问题,如果libtool的依赖链断了怎么办?也就是某个依赖的动态库不是libtool生成的,没有那个关键的.la文件。

libtool确实不会处理这种情况了,但他会交给链接器处理,链接器依据以下顺序去搜索动态库so文件:

1. 所有由’-rpath-link’选项指定的搜索路径。
2. 所有由’-rpath’指定的搜索路径。’-rpath’跟’-rpath_link’的不同之处在于,由’-rpath’指定的路径被包含在可执行文件中,并在运行时使用,而’-rpath-link’选项仅仅在连接时起作用。
3. 在一个ELF系统中, 如果’-rpath’和’rpath-link’选项没有被使用。
会搜索环境变量’LD_RUN_PATH’的内容.它也只对本地连接器起作用。
4. 在SunOS上, ‘-rpath’选项不使用, 只搜索所有由’-L’指定的目录。
5. 对于一个本地连接器,环境变量’LD_LIBRARY_PATH’的内容被搜索。
6.对于一个本地ELF连接器,共享库中的`DT_RUNPATH’和`DT_RPATH’操作符会被需要它的共享库搜索。
如果’DT_RUNPATH’存在了, 那’DT_RPATH’就会被忽略。
7. 缺省目录, 常规的,如’/lib’和’/usr/lib’。
8. 对于ELF系统上的本地链接器, 如果文件’/etc/ld.so.conf’存在,这个文件中有的目录会被搜索.
从以上可以看出,在使用本地工具链进行本地编译情况下,只要库存在于某个位置,gcc总能通过如上策略找到需要的共享库。但在交叉编译下,上述八种策略,可以使用的仅仅有两个:-rpath-link,-rpath。这两个选项在上述八种策略当中优先级最高,当指定这两个选项时,如果链接需要的共享库找不到,链接器会优先到这两个选项指定的路径下去搜索需要的共享库。通过上面的描述可以看到:-rpath指定的路径将被写到可执行文件中;-rpath-link则不会;我们当然不希望交叉编译情况下使用的路径信息被写进最终的可执行文件,所以我们选择使用选项-rpath-link。

libtool和pkg-config的关系:

这两个东西都很常见,libtool已经了解了,那pkg-config和它是什么关系?

如果configure确实使用了pkg-config的话也就是获得libtool所需要的-I -L -l参数而已,然后传给libtool,所以他们之间不直接打交道。

最后,libtool不能处理头文件,也就是说你用libtool安装了so却没有安装头文件,所以还要手动cp头文件到相应目录。

对于多数库项目还提供pkg-config信息。有些项目只生成可执行文件,自身不再作为动态库为别的共享,那么往往在Makefile见不到libtool,而只是利用pkg-config生成编译参数,直接用gcc完成编译。

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: