参考:
http://www.cnblogs.com/hanyan225/archive/2010/10/01/1839906.html
http://www.west263.com/info/html/wangzhanyunying/jianzhanjingyan/20080417/70218.html
http://www.cnblogs.com/lidp/archive/2009/12/02/1697479.html
GNU Binutils:http://en.wikipedia.org/wiki/GNU_Binutils
Linux静态链接(库)、动态链接(库)、可执行文件加载相关问题(创建、选项、环境变量等)
(1) 概念// File: test1.cpp
#include
void foo()
{
printf("This is foo in test1\n");
}
// File: test1.h
#ifndef TEST1_H
#define TEST1_H
void foo();
#endif
// File: test2.cpp
#include
void foo()
{
printf("This is foo in test2\n");
}
// File: test2.h
#ifndef TEST2_H
#define TEST2_H
void foo();
#endif
1. 静态链接.a:
首先,先编译得到中间文件.o:
#ls
test1.cpp test1.h test2.cpp test2.h
#gcc -c test1.cpp -o test1.o
#gcc -c test2.cpp -o test2.o
#ls
test1.cpp test1.h test1.o test2.cpp test2.h test2.o
#
然后,利用ar命令打包得到.a文件:
#ar crv libtest1.a test1.o
a - test1.o
#ls
libtest1.a test1.cpp test1.h test1.o test2.cpp test2.h test2.o
(libtest2.a可以同理得到,说明:这里是由一个.o得到一个.a的情况)
命令: ar说明:对于需要将多个.o打包成一个.a的情况,可以直接在后面列举所有的.o文件即可。另外,ar命令有很多选项,对于.a已经存在,往其中插入模块(.o),如何处理已经存在的.o等等其它的处理情况,这里不详细讨论。一般的使用是crv选项,c表示不管.a是否已经存在创建新的.a文件,r表示在库中插入模块(替换)。当插入的模块名已在库中存在,则替换同名的模块。假如若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,能够使用其他任选项来改变增加的位置。v表示显示详细操作信息。具体ar的使用参考:http://www.west263.com/info/html/wangzhanyunying/jianzhanjingyan/20080417/70218.html
关于nm命令,用来列出目标文档的符号清单,不仅仅适用于.a,也用于.so等。也不更多说明,一般常用的就是nm file或者nm -o file(信息会更详细),如下:
#ar crv libtest1.a test1.o
a - test1.o
#ar crv libtest2.a test2.o
a - test2.o
#ar crv libtest12.a test1.o test2.o
a - test1.o
a - test2.o
#nm libtest1.a
test1.o:
0000000000000000 T _Z3foov
U puts
#nm libtest2.a
test2.o:
0000000000000000 T _Z3foov
U puts
#nm libtest12.a
test1.o:
0000000000000000 T _Z3foov
U puts
test2.o:
0000000000000000 T _Z3foov
U puts
#
2. 动态链接得到.so:
首先,使用gcc的-fPIC选项编译得到PIC(位置无关代码)的中间文件。
然后,使用gcc的-shared选项对.o文件进行链接。
如下:
#gcc -c -fPIC test1.cpp -o test1.o
#gcc -c -fPIC test2.cpp -o test2.o
#gcc -shared test1.o -o libtest1.so
#gcc -shared test2.o -o libtest2.so
#gcc -shared test1.o test2.o -o libtest12.so
test2.o: In function `foo()':
test2.cpp:(.text+0x0): multiple definition of `foo()'
test1.o:test1.cpp:(.text+0x0): first defined here
collect2: ld 返回 1
#
(从这里也可以看到,gcc生成动态链接库会检查是否有重复定义的情况,上面的静态打包ar命令就不会检查。)
说明:必须使用-fPIC选项编译,否则使用-shared生成动态链接库的时候会出现链接错误。当然,上面是编译和链接分开的,也是可以一起完成,就不需要-c选项了,如gcc -fPIC -shared test.c -o libtest.so等。
关于nm在查看.so时候常见的使用方式:
上面有关于nm用于查看.a文件的说明,对于.so,通常使用下面的选项查看:
(参考:http://blog.sina.com.cn/s/blog_5dc87df60100bike.html
http://www.cppblog.com/noflybird/archive/2010/05/06/114618.aspx
http://apps.hi.baidu.com/share/detail/20625788)
-C:用C/C++的方式显示函数名(函数名在编译后会稍微改变一下,demangle和mangle)。
-g:仅显示外部符号。
我一般使用如下来查看动态链接库里面定义的函数(也可以用这个查看.a更好):
nm -C -g libtest1.so -A
或者进一步用T过滤结果:
nm -C -g libtest1.so -A | grep T
如下:
#nm -C -g libtest1.so -A | grep T
libtest1.so:000000000000057c T foo()
libtest1.so:00000000000005cc T _fini
libtest1.so:0000000000000460 T _init
1. 生成静态链接库的方式:先gcc编译得到.o文件,使用ar命令打包.o文件即可(ar crv xxx.a xxx.o xxx1.o xxx2.o...)。
2. 生成动态链接库的方式:用gcc -fPIC编译得到.o文件,然后用-shared选项进行链接即可。
3. 查看静态链接库和动态链接库的符号文件:nm,也可以查看其它文件的符号文件。比如:nm -C -g libtest1.so/libtest1.a -A | grep T的形式。
4. 经过上面的折腾,得到了如下的文件(下面会用到):
#ls
libtest12.a libtest1.a libtest1.so libtest2.a libtest2.so test1.h test2.h
#
(4) 静态链接库和动态链接库的“原始”使用
测试程序:
// File: main.cpp
#include "test1.h"
int main()
{
foo();
return 0;
}
1、和静态库链接:
直接参与链接即可:
#ls
libtest12.a libtest1.a libtest2.a main.cpp test1.h test2.h
#gcc main.cpp libtest1.a
#./a.out
This is foo in test1
#
当然,也可以把编译和链接分开,那么就先gcc -c main.cpp -o main.o得到main.o,然后gcc main.o libtest1.a即可。
这是最简单的和静态库链接的方式,直接作为gcc的输入参数(输入文件)参与链接。更复杂的情况是,要链接的库不在当前目录下,那么当然,需要指定文件的路径(相对路径或绝对路径)如下:
#ls
lib main.cpp test1.h test2.h
#gcc main.cpp ./lib/libtest1.a
#./a.out
This is foo in test1
#
2、和动态库链接:
直接参与链接即可,和静态链接库一样,gcc xxx.cpp xxx.so libs/abc/xxx.so.....,将要链接的库作为输入文件参数传递给gcc即可。
#ls
lib main.cpp test1.h test2.h
#gcc main.cpp lib/libtest1.so
#./a.out
This is foo in test1
#
总结:上面这是最“原始"的方式连链接静态库和动态库,即把库文件作为gcc的输入文件,规则当然和源文件和目标文件类似了,不管是使用相对路径还是绝对路径,总之是必须能让gcc直接找到的。对于这种方式,使用-L或者把libtest1.so复制到/lib、/usr/lib这些方式,都不能简化为"gcc main.cpp libtest1.so”(当然是libtest1.so不在当前目录的情况下)。说明:这一点是很基础的,但是时间久了反而犯迷糊了,以为-L也能对这种最原始的情况起到作用(gcc main.cpp libtest1.so -L./lib,其中libtest1.so在lib文件夹中)。
(5) 使用-l和-L链接静态库(-static)和动态库
上面的最原始的方式链接到动态库的方式有一个缺点在于:需要指定要链接的库的相对或绝对路径。那么,一个情况是,如果我们的程序需要链接到一些库,比如linux提供了很多库如pthread库等,如果需要链接到它们,当然,使用完整的路径是可以的。如下:
#gcc /lib64/libpthread.so.0 main.cpp lib/libtest1.so
#
这样的缺点是很明显的。为了改进,gcc提供了-l的方式来简化这个问题,在gcc选项中使用-lxxxx指定要链接的库的库名,对应的库名为libxxx.so或libxxx.so,同时,这个库是存在于“系统库目录”中。对于Linux而言,系统库目录默认为/lib和/usr/lib(或者/lib64和/usr/lib64,这里不讨论关于32和64位的问题)。另外,-L选项则用于将一个指定的目录加入到”系统库“的范畴,其实就是加入到链接器对库的搜索路径了。
所以,对于上面的例子,可以:
A、把libtest1.so复制到/lib中或者/usr/lib中,就可以直接使用:gcc -ltest1 main.cpp编译了。
B、如果libtest1.so不在搜索路径(/lib和/usr/lib中),那么编译会得到类似于:/usr/bin/ld: cannot find -ltest1的错误。可以使用-L将指定的目录加入到搜索路径,如:
#ls
lib main.cpp test1.h test2.h
#gcc -ltest1 main.cpp -Llib
#gcc -ltest1 main.cpp -L./lib
#
至于静态库,其规则和上面的一样,同样有"系统库目录"和-l的简写以及-L添加搜索路径。但是,默认情况下,gcc是进行动态链接的。如果需要使用静态链接,需要使用-static选项,使用了-static选项后,所有的-l指定的库都会去搜索路径中查找对应的.a静态库。而且,需要注意的是,-static也会改变gcc的默认链接的库的链接方式,也会链接静态库(关于gcc默认链接的库,比如libc等,默认都是动态库的,使用了-static后,会链接到libc.a静态库,关于哪些库是gcc的默认链接库下面会介绍)。
这个内容本来和这里的内容无关,但是一般总是和-L一起讨论,所以就这里说明一下。-include用于包含头文件,功能和#include一样,一般很少用,一般都是在代码里用#include了。-I用于指定额外的“系统头文件”的搜索路径,对应于-L选项类似的功能。
对于使用<>的#include,头文件会去系统头文件(/usr/include)搜索,如果某些头文件不在系统头文件路径中,就需要使用-I指定了。
PS:-I和-L都可以在一个命令行下指定多个,增加多个路径,如gcc -lxxx foo.cpp -Llib1/ -Llib2/ -Llib3/等。
(7) 可执行文件的加载和运行和ldd命令
上面的内容都是关于如何编译和链接的问题,得到可执行文件,事实上,上面的例子中,有些可执行文件是不能执行的。
对于链接静态库的程序,由于静态链接会将代码直接拷贝到可执行文件中,链接后的可执行文件是可以不依赖于静态库.a直接运行的,所以问题主要是关于使用了动态库的程序的运行问题。对于使用动态库的程序,在程序加载过程中,有一个重要的一步是动态库的加载(有时候也说成动态库的动态链接),loader为/lib/ld-linux.so(参考http://blog.csdn.net/gengshenghong/article/details/7096511的(3)理解)。
动态库的搜索路径搜索的先后顺序是:
1.编译目标代码时指定的动态库搜索路径;
2.环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
3.配置文件/etc/ld.so.conf中指定的动态库搜索路径;//只需在在该文件中追加一行库所在的完整路径如"/root/test/conf/lib"即可,然后ldconfig是修改生效。
4.默认的动态库搜索路径/lib;
5.默认的动态库搜索路径/usr/lib。
先看下面的例子,说明一个问题:
#tree
.
├── lib
│ ├── libtest1.so
│ └── libtest2.so
├── libtest1.so
├── libtest2.so
├── main.cpp
├── test1.h
└── test2.h
1 directory, 7 files
#gcc main.cpp libtest1.so
#./a.out
./a.out: error while loading shared libraries: libtest1.so: cannot open shared object file: No such file or directory
#gcc main.cpp ./libtest1.so
#./a.out
This is foo in test1
#gcc main.cpp lib/libtest1.so
#./a.out
This is foo in test1
#gcc main.cpp /home/sgeng2/labs_temp/temp/libtest1.so
#./a.out
This is foo in test1
这里,使用gcc main.cpp libtest1.so编译成功了,居然运行说找不到共享库,使用./就可以了。当然,对于上面的任意一种情况,链接后删除了.so肯定也是运行不了的。那么,如果是下面的情况呢:
#ls
lib libtest1.so libtest2.so main.cpp test1.h test2.h
#gcc main.cpp ./libtest1.so
#./a.out
This is foo in test1
#../temp/a.out
This is foo in test1
#cd ..
#temp/a.out
temp/a.out: error while loading shared libraries: ./libtest1.so: cannot open shared object file: No such file or directory
#
会发现,使用gcc mai.cpp ./libtest1.so编译后,运行时,这个"./libtest1.so“的信息是保存了的,所以,只能在当前目录运行a.out,如果cd切换到其它目录,那么它仍然是去寻找./libtest1.so,自然就找不到了。那么,如果这里使用的是lib/libtest1.so链接,也是一样的,运行的时候,当前工作目录下需要有一个lib/libtest1.so的文件,如果使用的是绝对路径,那么当然,只要.so没有删除,都是可以运行的。可见,这种最“原始"的链接方式,它还是很傻的,局限性比较大。
总结:这种使用"原始"的方式来链接得到可执行文件,.so的路径信息是保存在可执行文件中的,在加载的时候需要查找对应的.so是否存在。上面的提到了"动态库的搜索路径的搜索顺序",这是用于使用-l和-L来链接的动态链接库的情况,对于这里的"原始"的方式并无改变,比如使用gcc main.cpp ./libtest1.so编译后,哪怕将libtest1.so复制到了/usr/lib中也没用。(个人理解,这种原始的指定路径的编译方式,应该属于"编译目标代码时指定的动态库搜索路径”,当然,一般说到这个编译目标代码时指定的动态库搜索路径,是指另外一个选项,下一部分说明)。
ldd命令:ldd命令用于显示一个可执行文件中依赖的所有.so在当前系统中的位置,简单理解,就是它会模拟可执行文件的加载,输出各个.so文件的路径,比如上面的所有路径的顺序,在不同的路径中放入.so,ldd得到的路径会显示实际loader查找到的路径。如果找不到.so或者.so有问题,ldd也会报错。
(8) 编译目标代码时指定的动态库搜索路径"-Wl,-rpath,"
gcc提供了-Wl,-rpath,xxx来指定程序运行时候搜索动态库的路径,和-L没有任何关系,-Wl,-rpath是在编译时指定,但是其用于运行时的loader,所以是一个比较特殊的选项,容易和-L混淆。说明:-Wl,-rpath,xxx指定的路径,如果使用的是相对路径,那么运行的时候,也是根据相对路径来查找.so,所以其实和上面说的"原始"方式进行链接,应该本质上是一回事。
这个选项可以指定多个路径,用":"分开即可。如:gcc -Wl,-rpath,path1:path2:path3 main.cpp -ltest -Llib/等。
总结:
1. 对于静态库,可以使用"原始"方式(指定路径作为gcc输入参数)链接静态库,也可以使用-l&&-L的方式,但是,需要使用-static选项。另外,默认的gcc链接方式是动态的,所以使用了-static,会让gcc默认链接的库也链接静态版本。
2. 对于使用"原始"方式(指定路径作为gcc输入参数)链接动态库的情况,运行时候限制太多,必须是怎么编译链接的,就要保证运行时候按照当时的路径能找到动态链接库,当然,这种方式很少用,一般都是用-l&&-L来链接动态链接库。
3. 对于使用-l来链接.so的情况,编译器会到/lib,/usr/lib和-L指定的路径下查找-l指定的.so文件来进行链接。搜索先后顺序为:-L指定的路径;/usr/lib;/lib。
4. 对于使用-l链接.so的情况,在运行的时候。会根据相应的路径搜索.so文件来进行加载运行,加载的时候,动态库的搜索路径搜索的先后顺序是:编译目标代码时指定的动态库搜索路径;环境变量LD_LIBRARY_PATH指定的动态库搜索路径;配置文件/etc/ld.so.conf中指定的动态库搜索路径;默认的动态库搜索路径/lib;默认的动态库搜索路径/usr/lib。
5. -Wl,-rpath用于编译目标代码时指定的动态库搜索路径,和-L没有关系,一个指定的路径用于运行时加载.so的搜索,一个指定的路径用于编译时链接.so中符号的搜索。
(9) 链接多个库的问题(多个库都有同一函数的定义)
比如:两个.a文件都定义了函数foo,然后都参与链接,实际使用的是哪一个?如果是两个.so呢?一个.a和一个.so呢?
(10) 补充:
1. 关于搜索路径
上面已经总结了相关的头文件和库文件的搜索路径问题,需要说明的是,实际的GCC的搜索路径还可能会和其他因素有关,比如一些环境变量的设置,还有gcc会将自己的安装目录下的lib文件夹或者include文件夹等等加入搜索路径,包括可以通过sysroot等选项改变默认的设置,具体参考http://blog.csdn.net/gengshenghong/article/details/7108634的总结。当然,为了更好的了解这些信息,gcc也提供了几个相关命令来查看相关信息。
gcc -print-search-dirs:打印安装、程序和库的搜索路径。输出示例如下:
#gcc --print-search-dirs
安装:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/
程序:=/usr/libexec/gcc/x86_64-redhat-linux/4.6.0/:/usr/libexec/gcc/x86_64-redhat-linux/4.6.0/:/usr/libexec/gcc/x86_64-redhat-linux/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/:/usr/lib/gcc/x86_64-redhat-linux/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../../x86_64-redhat-linux/bin/x86_64-redhat-linux/4.6.0/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../../x86_64-redhat-linux/bin/
库:=/usr/lib/gcc/x86_64-redhat-linux/4.6.0/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../../x86_64-redhat-linux/lib/x86_64-redhat-linux/4.6.0/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../../x86_64-redhat-linux/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../x86_64-redhat-linux/4.6.0/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../../lib64/:/lib/x86_64-redhat-linux/4.6.0/:/lib/../lib64/:/usr/lib/x86_64-redhat-linux/4.6.0/:/usr/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../../x86_64-redhat-linux/lib/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../:/lib/:/usr/lib/
#
gcc -v file.c:显示编译file.c时候的详细信息(其中会显示一些搜索头文件的顺序的信息和LIBRARY_PATH、COMPILER_PATH等等。也可以直接gcc -v,显示的就是gcc的基本配置信息。
gcc -dumpspecs:显示所有内建 spec 字符串。
gcc -dM -E file.c:显示预处理file.c中所有的宏定义。
总之,gcc搜索路径还是有些复杂的,不需要所有细节全部记清楚,但是至少要知道会有哪些内容需要被考虑。
2. GCC的--sysroot选项
这个选项一般用于交叉编译,用于指定编译所需要的头文件,及链接库(的root目录)等。
3. GCC环境变量和配置选项等
http://www.linuxsir.org/bbs/thread304996.html
http://blog.csdn.net/yangruibao/article/details/6869323
http://jimobit.blog.163.com/blog/static/28325778200991524826935/