什么是库文件?
库文件是事先编译好的方法的合集。比如:我们提前写好一些数据公式的实现,将其打包成库文件,以后使用只需要库文件就可以,不需要重新编写。
Linux系统中:
1.静态库的扩展名为.a;
2.动态库的扩展名为.so;
源代码到可执行程序的转换时需要经历如下图所示的过程:
1.编译是指把用高级语言编写的程序转换成相应处理器的汇编语言程序的过程。
2.汇编是从汇编语言程序生成目标系统的二进制代码(机器代码)的过程。
3.链接是指将汇编生成的多段机器代码组合成一个可执行程序。
通过编译和汇编过程,每一个源文件将生成一个目标文件。连接器的作用就是将这些目标文件组合起来,组合的过程包括了代码段、数据段等部分的合并,以及添加相应的文件头。
最后得到的可执行文件如何作用的:
ELF文件格式包括三种主要的类型:可执行文件、可重定向文件、共享库。
1.可执行文件(应用程序)
可执行文件包含了代码和数据,是可以直接运行的程序。
2.可重定向文件(*.o)
可重定向文件又称为目标文件,它包含了代码和数据(这些数据是和其他重定位文件和共享的object文件一起连接时使用的)。
.o文件参与程序的连接(创建一个程序)和程序的执行(运行一个程序),它提供了一个方便有效的方法来用并行的视角看待文件的内容,这些.o文件的活动可以反映出不同的需要。
Linux下,我们可以用gcc -c编译源文件时可将其编译成*.o格式。
3.共享文件(*.so)
也称为动态库文件,它包含了代码和数据(这些数据是在连接时候被连接器ld和运行时动态连接器使用的)。动态连接器可能称为ld.so.1,libc.so.1或者 ld-linux.so.1。我的CentOS6.0系统中该文件为:/lib/ld-2.12.so
库是一种可执行代码的二进制格式,能够被载入到内存中执行,库分静态库和动态库两种:
静态库:这类库的名字一般是libxxx.a,xxx为库的名字。利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。
动态库:这类库的名字一般是libxxx.M.N.so,同样的xxx为库的名字,M是库的主版本号,N是库的副版本号。当然也可以不要版本号,但名字必须有。相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。linux系统有几个重要的目录存放相应的函数库,如/lib /usr/lib。
当要使用静态的程序库时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。然而,对动态库而言,就不是这样。动态库会在执行程序内留下一个标记指明当程序执行时,首先必须载入这个库。由于动态库节省空间,linux下进行连接的缺省操作是首先连接动态库,也就是说,如果同时存在静态和动态库,不特别指定的话,将与动态库相连接。
制作静态链接库:
1.准备两个源码文件st1.cpp和st2.cpp,用它们来制作库libmytest.a
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ cat st1.cpp
#include <iostream>
using namespace std;
void display1()
{
cout<<"This is my first static library!!!"<<endl;
}
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ cat st2.cpp
#include <iostream>
using namespace std;
void display2()
{
cout<<"This is my second static library"<<endl;
}
2.把两个源码文件生成目标文件
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ g++ -c st1.cpp st2.cpp
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ ll
总用量 24
drwxrwxr-x 2 xzj xzj 4096 7月 16 15:39 ./
drwxrwxr-x 7 xzj xzj 4096 7月 16 15:26 ../
-rw-rw-r-- 1 xzj xzj 115 7月 16 15:35 st1.cpp
-rw-rw-r-- 1 xzj xzj 2680 7月 16 15:39 st1.o
-rw-rw-r-- 1 xzj xzj 113 7月 16 15:35 st2.cpp
-rw-rw-r-- 1 xzj xzj 2680 7月 16 15:39 st2.o
3.使用ar -rsv libmytest.a st1.o st2.o制作静态库
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ ar -rsv libmytest.a st1.o st2.o
ar: 正在创建 libmytest.a
a - st1.o
a - st2.o
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ ll
总用量 32
drwxrwxr-x 2 xzj xzj 4096 7月 16 15:42 ./
drwxrwxr-x 7 xzj xzj 4096 7月 16 15:26 ../
-rw-rw-r-- 1 xzj xzj 5586 7月 16 15:42 libmytest.a
-rw-rw-r-- 1 xzj xzj 115 7月 16 15:35 st1.cpp
-rw-rw-r-- 1 xzj xzj 2680 7月 16 15:39 st1.o
-rw-rw-r-- 1 xzj xzj 113 7月 16 15:35 st2.cpp
-rw-rw-r-- 1 xzj xzj 2680 7月 16 15:39 st2.o
用file命令查看其属性,发现它确实是归档压缩文件
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ file libmytest.a
libmytest.a: current ar archive
用ar -t libmytest.a可以查看一个静态库包含了那些obj文件:
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ ar -t libmytest.a
st1.o
st2.o
4.写个测试程序来调用库libmytest.a中所提供的两个接口display1()和display2()。
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ cat main.cpp
void display1();
void display2();
int main()
{
display1();
display2();
return 0;
}
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ g++ -o run main.cpp -L./ -lmytest
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ ll
总用量 48
drwxrwxr-x 2 xzj xzj 4096 7月 16 15:54 ./
drwxrwxr-x 7 xzj xzj 4096 7月 16 15:26 ../
-rw-rw-r-- 1 xzj xzj 5586 7月 16 15:42 libmytest.a
-rw-rw-r-- 1 xzj xzj 95 7月 16 15:53 main.cpp
-rwxrwxr-x 1 xzj xzj 9424 7月 16 15:54 run*
-rw-rw-r-- 1 xzj xzj 115 7月 16 15:35 st1.cpp
-rw-rw-r-- 1 xzj xzj 2680 7月 16 15:39 st1.o
-rw-rw-r-- 1 xzj xzj 113 7月 16 15:35 st2.cpp
-rw-rw-r-- 1 xzj xzj 2680 7月 16 15:39 st2.o
结果调用成功:
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ ./run
This is my first static library!!!
This is my second static library
制作动态库
静态库*.a文件的存在主要是为了支持较老的a.out格式的可执行文件而存在的。目前用的最多的要数动态库了。
动态库的后缀为*.so。在Linux发行版中大多数的动态库基本都位于/usr/lib和/lib目录下。在开发和使用我们自己动态库之前,请容许我先落里罗嗦的跟大家唠叨唠叨Linux下和动态库相关的事儿吧。
有时候当我们的应用程序无法运行时,它会提示我们说它找不到什么样的库,或者哪个库的版本又不合它胃口了等等之类的话。那么应用程序它是怎么知道需要哪些库的呢?我们前面已几个学了个很棒的命令ldd,用就是用来查看一个文件到底依赖了那些so库文件。
Linux系统中动态链接库的配置文件一般在/etc/ld.so.conf文件内,它里面存放的内容是可以被Linux共享的动态联库所在的目录的名字。我的系统中,该文件的内容如下:
xzj@xzj-VirtualBox:/etc$ cat /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf
然后/etc/ld.so.conf.d/目录下存放了很多*.conf文件,如下:
xzj@xzj-VirtualBox:/etc$ ls /etc/ld.so.conf.d/
fakeroot-x86_64-linux-gnu.conf libc.conf x86_64-linux-gnu_GL.conf
i386-linux-gnu.conf x86_64-linux-gnu.conf zz_i386-biarch-compat.conf
i386-linux-gnu_GL.conf x86_64-linux-gnu_EGL.conf zz_x32-biarch-compat.conf
其中每个conf文件代表了一种应用的库配置内容,以libc为例:
xzj@xzj-VirtualBox:/etc$ cat /etc/ld.so.conf.d/libc.conf
# libc default configuration
/usr/local/lib
在/etc目录下还存在一个名叫ld.so.cache的文件。从名字来看,我们知道它肯定是动态链接库的什么缓存文件。
xzj@xzj-VirtualBox:/etc$ ls -l |grep ld.so.cache
-rw-r--r-- 1 root root 125054 7月 16 09:09 ld.so.cache
为了使得动态链接库可以被系统使用,当我们修改了/etc/ld.so.conf或/etc/ld.so.conf.d/目录下的任何文件,或者往那些目录下拷贝了新的动态链接库文件时,都需要运行一个很重要的命令:ldconfig,该命令位于/sbin目录下,主要的用途就是负责搜索/lib和/usr/lib,以及配置文件/etc/ld.so.conf里所列的目录下搜索可用的动态链接库文件,然后创建处动态加载程序/lib/ld-linux.so.2所需要的连接和(默认)缓存文件/etc/ld.so.cache(此文件里保存着已经排好序的动态链接库名字列表)。
也就是说:当用户在某个目录下面创建或拷贝了一个动态链接库,若想使其被系统共享,可以执行一下"ldconfig目录名"这个命令。此命令的功能在于让ldconfig将指定目录下的动态链接库被系统共享起来,即:在缓存文件/etc/ld.so.cache中追加进指定目录下的共享库。请注意:如果此目录不在/lib,/usr/lib及/etc/ld.so.conf文件所列的目录里面,则再次单独运行ldconfig时,此目录下的动态链接库可能不被系统共享了。单独运行ldconfig时,它只会搜索/lib、/usr/lib以及在/etc/ld.so.conf文件里所列的目录,用它们来重建/etc/ld.so.cache。
因此,等会儿我们自己开发的共享库就可以将其拷贝到/lib、/etc/lib目录里,又或者修改/etc/ld.so.conf文件将我们自己的库路径添加到该文件中,再执行ldconfig命令。
动态库的实战搞起来
我们有一个头文件my_so_test.h和三个源文件test_hubei.cpp、test_wuhan.cpp和test_xiaogan.cpp,将他们制作成一个名为libtest.so的动态链接库文件:
头文件:
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ cat my_so_test.h
#ifndef MY_SO_TEST_H
#define MY_SO_TEST_H
void test_hubei();
void test_wuhan();
void test_xiaogan();
#endif
三个源文件:
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ cat test_hubei.cpp
#include "my_so_test.h"
#include <iostream>
using namespace std;
void test_hubei()
{
cout<<"欢迎来到湖北"<<endl;
}
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ cat test_wuhan.cpp
#include "my_so_test.h"
#include <iostream>
using namespace std;
void test_wuhan()
{
cout<<"欢迎来到武汉"<<endl;
}
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ cat test_wuhan.cpp
#include "my_so_test.h"
#include <iostream>
using namespace std;
void test_wuhan()
{
cout<<"欢迎来到武汉"<<endl;
}
生产.so文件的方法:
方法一:
1、先生成目标.o文件:
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ g++ -c *.cpp
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ll
总用量 36
drwxrwxr-x 2 xzj xzj 4096 7月 16 17:21 ./
drwxrwxr-x 8 xzj xzj 4096 7月 16 16:26 ../
-rw-rw-r-- 1 xzj xzj 108 7月 16 17:14 my_so_test.h
-rw-rw-r-- 1 xzj xzj 125 7月 16 17:18 test_hubei.cpp
-rw-rw-r-- 1 xzj xzj 2664 7月 16 17:21 test_hubei.o
-rw-rw-r-- 1 xzj xzj 125 7月 16 17:19 test_wuhan.cpp
-rw-rw-r-- 1 xzj xzj 2664 7月 16 17:21 test_wuhan.o
-rw-rw-r-- 1 xzj xzj 127 7月 16 17:20 test_xiaogan.cpp
-rw-rw-r-- 1 xzj xzj 2672 7月 16 17:21 test_xiaogan.o
2、再生成so文件:
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ g++ -shared -fPCI -o libcity.so test_hubei.o test_wuhan.o test_xiaogan.o
g++: error: unrecognized command line option ‘-fPCI’
出现了错误,我使用了第二种方法。
方法二:
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ g++ test_hubei.cpp test_wuhan.cpp test_xiaogan.cpp -fPIC -shared -o libtest.so
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ll
总用量 48
drwxrwxr-x 2 xzj xzj 4096 7月 16 17:37 ./
drwxrwxr-x 8 xzj xzj 4096 7月 16 16:26 ../
-rwxrwxr-x 1 xzj xzj 9048 7月 16 17:37 libtest.so*
-rw-rw-r-- 1 xzj xzj 108 7月 16 17:14 my_so_test.h
-rw-rw-r-- 1 xzj xzj 125 7月 16 17:18 test_hubei.cpp
-rw-rw-r-- 1 xzj xzj 2664 7月 16 17:21 test_hubei.o
-rw-rw-r-- 1 xzj xzj 125 7月 16 17:19 test_wuhan.cpp
-rw-rw-r-- 1 xzj xzj 2664 7月 16 17:21 test_wuhan.o
-rw-rw-r-- 1 xzj xzj 127 7月 16 17:20 test_xiaogan.cpp
-rw-rw-r-- 1 xzj xzj 2672 7月 16 17:21 test_xiaogan.o
动态链接库的使用有两种方法:既可以在运行时对其进行动态链接,又可以动态加载在程序中是用它们
+++动态库的使用+++
用法一:动态链接。
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ g++ main.cpp -o run -L. -ltest
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ll
总用量 64
drwxrwxr-x 2 xzj xzj 4096 7月 16 18:14 ./
drwxrwxr-x 8 xzj xzj 4096 7月 16 16:26 ../
-rwxrwxr-x 1 xzj xzj 9048 7月 16 17:37 libtest.so*
-rw-rw-r-- 1 xzj xzj 130 7月 16 17:55 main.cpp
-rw-rw-r-- 1 xzj xzj 108 7月 16 17:14 my_so_test.h
-rwxrwxr-x 1 xzj xzj 8688 7月 16 18:14 run*
-rw-rw-r-- 1 xzj xzj 125 7月 16 17:18 test_hubei.cpp
-rw-rw-r-- 1 xzj xzj 2664 7月 16 17:21 test_hubei.o
-rw-rw-r-- 1 xzj xzj 125 7月 16 17:19 test_wuhan.cpp
-rw-rw-r-- 1 xzj xzj 2664 7月 16 17:21 test_wuhan.o
-rw-rw-r-- 1 xzj xzj 127 7月 16 17:20 test_xiaogan.cpp
-rw-rw-r-- 1 xzj xzj 2672 7月 16 17:21 test_xiaogan.o
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ LD_LIBRARY_PATH=. ./run
欢迎来到湖北
欢迎来到武汉
欢迎来到孝感
将main.cpp与libtest.so链接成一个可执行文件main。命令如下:
$ g++ main.cpp -o run -L. -ltest
测试可执行程序main是否已经链接的动态库libtest.so,如果列出了libtest.so,那么就说明正常链接了。可以执行以下命令:
$ ldd run
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ldd run
linux-vdso.so.1 => (0x00007ffe08fc7000)
libtest.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f685d1a2000)
/lib64/ld-linux-x86-64.so.2 (0x00007f685d56c000)
如果你直接执行可执行文件run的话就会出现以下错误:
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ./run
./run: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory
这里我们注意,ldd的输出表示我们的libtest.so动态库没有找到。因为我们的libtest.so既不在/etc/ld.so.cache里,又不在/lib、/usr/lib或/etc/ld.so.conf所指定的任何一个目录中
解决办法:
方法一:LD_LIBRARY_PATH=. 可执行文件
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ LD_LIBRARY_PATH=. ./run
欢迎来到湖北
欢迎来到武汉
欢迎来到孝感
方法二:如果你在开发一款软件,或者给自己的系统DIY一个非常有用的功能模块,那么建议你将libtest.so拷贝到/lib、/usr/lib目录下,
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ sudo cp libtest.so /lib/
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ cd /lib/
xzj@xzj-VirtualBox:/lib$ ls
apparmor hdparm ld-linux.so.2 modules terminfo
brltty i386-linux-gnu libtest.so recovery-mode udev
cpp ifupdown linux-sound-base resolvconf ufw
crda init lsb systemd x86_64-linux-gnu
firmware klibc-k3La8MUnuzHQ0_kG8hokcGAC0PA.so modprobe.d sysvinit xtables
之后在使用ldd命令来看是否使用动态库时,可以看到成功了
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ldd run
linux-vdso.so.1 => (0x00007ffe8b3fe000)
libtest.so => /lib/libtest.so (0x00007fe6af1f2000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe6aee28000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fe6aeaa6000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe6af3f4000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fe6ae79d000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fe6ae587000)
直接运行可执行文件时,就成功了!
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ./run
欢迎来到湖北
欢迎来到武汉
欢迎来到孝感
方法三:动态加载。
动态加载是非常灵活的,它依赖于一套Linux提供的标准API来完成。在源程序里,你可以很自如的运用API来加载、使用、释放so库资源。以下函数在代码中使用需要包含头文件:dlfcn.h
函数原型 | 说明 |
---|---|
const char *dlerror(void) | 当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。 |
void *dlopen(const char *filename, int flag) | 用于打开指定名字(filename)的动态链接库,并返回操作句柄。调用失败时,将返回NULL值,否则返回的是操作句柄。 |
void *dlsym(void *handle, char *symbol) | 根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的函数的执行代码地址。由此地址,可以带参数执行相应的函数。 |
int dlclose (void *handle) | 用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。2.2在程序中使用动态链接库函数。 |
dlsym(void *handle, char *symbol)
filename:如果名字不以“/”开头,则非绝对路径名,将按下列先后顺序查找该文件。
(1)用户环境变量中的LD_LIBRARY_PATH的值;
(2)动态链接缓冲文件/etc/ld.so.cache
(3)目录/lib,/usr/lib
flag表示在什么时候解决未定义的符号(调用)。取值有两个:
1) RTLD_LAZY : 表明在动态链接库的函数代码执行时解决。
2) RTLD_NOW :表明在dlopen返回前就解决所有未定义的符号,一旦未解决,dlopen将返回错误。
dlsym(void *handle, char *symbol)
dlsym()的用法一般如下:
void(add)(int x,int y); /说明一下要调用的动态函数add */
add=dlsym("xxx.so","add"); /* 打开xxx.so共享库,取add函数地址 */
add(89,369); /* 带两个参数89和369调用add函数 */
代码搞起
#include "my_so_test.h"
#include <stdio.h>
#include <dlfcn.h>
#include <cstdlib>
extern "C"
{
void (*fn)(void);
}
int main(int argc, char const *argv[])
{
void *handle = dlopen("./libtest.so",RTLD_LAZY);
/*const char *err = dlerror();
if(err !=NULL){
perror("could not open shared object!");
}*/
if (NULL != handle) {
/* code */
printf("nihao
");
}
fn = (void (*)(void))dlsym(handle,"test_hubei");
fn();
dlclose(handle);
return 0;
}
执行结果:
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ g++ test_hubei.cpp test_wuhan.cpp test_xiaogan.cpp -fPIC -shared -o libtest.so
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ g++ -o mmain_run mmain.cpp -rdynamic -ldl
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ./mmain_run
nihao
欢迎来到湖北!
欢迎来到武汉!!
欢迎来到孝感!
每次修改源文件,一定要重新生成动态文件.so,否则一直在用之前生产的文件。不利于调式出现的错误!
3、编译参数
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
-L.:表示要连接的库在当前目录中
-ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。
4、注意的问题
调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
在生成动态库时的参数-fPIC
-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),
则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意
位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
gcc -shared -fPIC -o 1.so 1.c
这里有一个-fPIC参数
PIC就是position independent code
PIC使.so文件的代码段变为真正意义上的共享
如果不加-fPIC,则加载.so文件的代码段时,代码段引用的数据对象需要重定位, 重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的copy.每个copy都不一样,取决于 这个.so文件代码段和数据段内存映射的位置.
不加fPIC编译出来的so,是要再加载时根据加载到的位置再次重定位的.(因为它里面的代码并不是位置无关代码)
如果被多个应用程序共同使用,那么它们必须每个程序维护一份so的代码副本了.(因为so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)
我们总是用fPIC来生成so,也从来不用fPIC来生成a.
fPIC与动态链接可以说基本没有关系,libc.so一样可以不用fPIC编译,只是这样的so必须要在加载到用户程序的地址空间时重定向所有表目.
PIC原理与意义
载入时重定位的缺点:
(1)动态库的代码段不能在进程间共享:多个进程加载同一个动态库到各自不同的地址空间,导致代码段需要不同的重定位,所以最终每个引用该动态库的进程拥有一份该动态库代码段的不同拷贝。
(2)代码段必须是可写的,增加了被攻击风险。
为了解决载入时重定位的问题,引入了PIC的概念,即位置无关代码。
PIC实现原理:
(1)GOT:在动态库的数据段增加GOT(Global Offset Table),该表的每一项是符号到地址的绝对映射。由于代码段到数据段的偏移是固定的,因此可以在编译时确定代码段中的某个符号到GOT特定项之间的偏移。这样,代码段中的符号偏移就可以在编译时确定了,在加载时也无需修改代码段的内容,只需要填写位于数据段的GOT的所有项的符号的绝对地址就完成了。因为数据段本来就是进程间不共享,每个进程独立的一份,因此GOT的设计完全解决了以上两个问题,从而达到两个目的:1,代码段可以在多进程间共享;2,代码段是只读的。
(2)PLT:PLT是 Program Linkage Table 的缩写,即程序链接表,PLT的出现是为了延时定位的目的。一个动态库中的函数往往要远多于全局变量,并且被调用的函数往往少于定义的函数。GOT中包含了该动态库中的所有的全局变量的映射,并且在连接器加载时解析所有的全局变量的地址。如果用同样的方式去处理函数调用符号,则开销会非常大。因此在代码段设计了一个PLT表,每一项其实是个代码段,用于执行如下逻辑:首次访问时,解析参数和向GOT填写函数地址,后续访问直接访问GOT中的函数地址。如此达到了延时定位的目的。
因此,一个PIC的动态库中,对全局变量使用GOT来映射,对函数调用使用PLT+GOT来映射,从而达到共享库代码段复用,代码段安全访问的目的。而这些就是 PIC 的意义。